From c5cb690d07b0d17f683fad07d3a00ab373f5cfb2 Mon Sep 17 00:00:00 2001
From: fjn1g13 <fjn1g13@ecs.soton.ac.uk>
Date: Thu, 27 May 2021 19:12:02 +0100
Subject: [PATCH] M4M O'clock

---
 M4MCode/M4MDenseDev/M4MDenseDev.sln           |   41 +
 M4MCode/M4MDenseDev/M4MDenseDev/App.config    |    6 +
 .../M4MDenseDev/BlockDenseView.Designer.cs    |  288 ++
 .../M4MDenseDev/M4MDenseDev/BlockDenseView.cs |  619 +++
 .../M4MDenseDev/BlockDenseView.resx           |  123 +
 .../M4MDenseDev/DeltaTestHelpers.cs           |   17 +
 .../M4MDenseDev/M4MDenseDev/DenseContext.cs   |  171 +
 .../M4MDenseDev/DenseTraceeInfo.cs            |  101 +
 .../M4MDenseDev/DenseTraceeView.Designer.cs   |  142 +
 .../M4MDenseDev/DenseTraceeView.cs            |  371 ++
 .../M4MDenseDev/DenseTraceeView.resx          |  120 +
 .../M4MDenseDev/DoubleBufferedPanel.cs        |   30 +
 .../M4MDenseDev/IndividualBlockPlot.cs        |  255 ++
 .../M4MDenseDev/IndividualNetworkPlot.cs      |  374 ++
 .../M4MDenseDev/IndividualSpringyPlot.cs      |  542 +++
 .../M4MDenseDev/M4MDenseDev.csproj            |  181 +
 .../M4MDenseDev/MainForm.Designer.cs          |  112 +
 M4MCode/M4MDenseDev/M4MDenseDev/MainForm.cs   |  163 +
 M4MCode/M4MDenseDev/M4MDenseDev/MainForm.resx |  120 +
 M4MCode/M4MDenseDev/M4MDenseDev/Program.cs    |   22 +
 .../M4MDenseDev/Properties/AssemblyInfo.cs    |   36 +
 .../Properties/Resources.Designer.cs          |   71 +
 .../M4MDenseDev/Properties/Resources.resx     |  117 +
 .../Properties/Settings.Designer.cs           |   30 +
 .../M4MDenseDev/Properties/Settings.settings  |    7 +
 .../M4MDenseDev/TestForm.Designer.cs          |  101 +
 M4MCode/M4MDenseDev/M4MDenseDev/TestForm.cs   |   86 +
 M4MCode/M4MDenseDev/M4MDenseDev/TestForm.resx |  120 +
 .../M4MDenseDev/TraceeForm.Designer.cs        |   63 +
 M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.cs |  118 +
 .../M4MDenseDev/M4MDenseDev/TraceeForm.resx   |  120 +
 .../M4MDenseDev/M4MDenseDev/TraceeWindow.cs   |   12 +
 .../M4MDenseDev/TrajectorySeries.cs           |  141 +
 .../M4MDenseDev/TrajectoryView.Designer.cs    |   45 +
 .../M4MDenseDev/M4MDenseDev/TrajectoryView.cs |   83 +
 .../M4MDenseDev/TrajectoryView.resx           |  120 +
 .../M4MDenseDev/M4MDenseDev/packages.config   |    6 +
 .../M4MDenseDev/MindlessPlotting/App.config   |   18 +
 .../MindlessPlotting/MindlessPlotting.csproj  |  109 +
 .../M4MDenseDev/MindlessPlotting/Program.cs   |  512 +++
 .../Properties/AssemblyInfo.cs                |   36 +
 .../MindlessPlotting/packages.config          |   14 +
 M4MCode/M4MDenseMod/M4MDenseMod.sln           |   37 +
 M4MCode/M4MDenseMod/M4MDenseMod/Annotation.cs |   63 +
 M4MCode/M4MDenseMod/M4MDenseMod/App.config    |    6 +
 M4MCode/M4MDenseMod/M4MDenseMod/Colours.cs    |   57 +
 .../M4MDenseMod/DoubleBufferedPanel.cs        |   30 +
 .../M4MDenseMod/M4MDenseMod/Form1.Designer.cs |  219 +
 M4MCode/M4MDenseMod/M4MDenseMod/Form1.cs      |  233 ++
 M4MCode/M4MDenseMod/M4MDenseMod/Form1.resx    |  123 +
 .../M4MDenseMod/M4MDenseMod.csproj            |  156 +
 .../M4MDenseMod/ObservableVector.cs           |   50 +
 M4MCode/M4MDenseMod/M4MDenseMod/Program.cs    |   22 +
 .../M4MDenseMod/Properties/AssemblyInfo.cs    |   36 +
 .../Properties/Resources.Designer.cs          |   63 +
 .../M4MDenseMod/Properties/Resources.resx     |  117 +
 .../Properties/Settings.Designer.cs           |   26 +
 .../M4MDenseMod/Properties/Settings.settings  |    7 +
 .../M4MDenseMod/VectorEditor.Designer.cs      |   43 +
 .../M4MDenseMod/M4MDenseMod/VectorEditor.cs   |  196 +
 .../M4MDenseMod/M4MDenseMod/VectorEditor.resx |  120 +
 .../M4MDenseMod/M4MDenseMod/packages.config   |    4 +
 M4MCode/M4MPlotting/M4MPlotting.sln           |   31 +
 M4MCode/M4MPlotting/M4MPlotting/App.config    |    6 +
 .../M4MPlotting/M4MPlotting/Form1.Designer.cs |  178 +
 M4MCode/M4MPlotting/M4MPlotting/Form1.cs      |  773 ++++
 M4MCode/M4MPlotting/M4MPlotting/Form1.resx    |  129 +
 .../M4MPlotting/LogViewer.Designer.cs         |   61 +
 M4MCode/M4MPlotting/M4MPlotting/LogViewer.cs  |   32 +
 .../M4MPlotting/M4MPlotting/LogViewer.resx    |  120 +
 .../M4MPlotting/M4MPlotting.csproj            |  142 +
 M4MCode/M4MPlotting/M4MPlotting/Program.cs    |   32 +
 .../M4MPlotting/Properties/AssemblyInfo.cs    |   36 +
 .../Properties/Resources.Designer.cs          |   71 +
 .../M4MPlotting/Properties/Resources.resx     |  117 +
 .../Properties/Settings.Designer.cs           |   30 +
 .../M4MPlotting/Properties/Settings.settings  |    7 +
 M4MCode/M4MPlotting/M4MPlotting/m4m.ico       |  Bin 0 -> 45451 bytes
 .../M4MPlotting/M4MPlotting/packages.config   |    6 +
 .../M4M_Ded.CoreRunner.csproj                 |   42 +
 M4MCode/M4M_Ded/M4M_Ded.CoreRunner/Program.cs |  253 ++
 M4MCode/M4M_Ded/M4M_Ded.sln                   |   31 +
 M4MCode/M4M_Ded/M4M_Ded/ExpInfo.cs            |   51 +
 M4MCode/M4M_Ded/M4M_Ded/GroupPlotting.cs      |   88 +
 M4MCode/M4M_Ded/M4M_Ded/M4M_Ded.csproj        |   39 +
 M4MCode/M4M_Ded/M4M_Ded/MiscPlotting.cs       |   43 +
 M4MCode/M4M_Ded/M4M_Ded/RunonAnalysis.cs      |   66 +
 M4MCode/M4M_Ded/M4M_Ded/SaturationAnalysis.cs |  109 +
 .../.tours/enumandstructstateproviders.tour   |  151 +
 M4MCode/M4M_MkI/.vscode/launch.json           |   70 +
 M4MCode/M4M_MkI/.vscode/tasks.json            |   27 +
 M4MCode/M4M_MkI/BuildTools/AutoVersion.dll    |  Bin 0 -> 8704 bytes
 .../BuildTools/AutoVersion.runtimeconfig.json |   10 +
 .../M4M.Benchmarks/DenseGenomeBenchmarks.cs   |   47 +
 .../M4M.Benchmarks/M4M.Benchmarks.csproj      |   24 +
 M4MCode/M4M_MkI/M4M.Benchmarks/Program.cs     |   18 +
 .../RegularisationBenchmarks.cs               |   97 +
 .../M4M.CoreRunner/M4M.CoreRunner.csproj      |   39 +
 M4MCode/M4M_MkI/M4M.CoreRunner/Program.cs     |   39 +
 M4MCode/M4M_MkI/M4M.Model/Analysis.cs         |  622 +++
 .../DataStructures/ComparableList.cs          |  155 +
 .../M4M.Model/DataStructures/DisjointSets.cs  |  123 +
 .../M4M_MkI/M4M.Model/DataStructures/Graph.cs |  217 +
 .../M4M.Model/DataStructures/PriorityQueue.cs |  364 ++
 .../M4M_MkI/M4M.Model/DeltaRule/DeltaRule.cs  |  118 +
 M4MCode/M4M_MkI/M4M.Model/DeltaTest.cs        |  195 +
 .../M4M.Model/DensePopulationFeedback.cs      |   37 +
 .../M4M_MkI/M4M.Model/DtmClassification.cs    |  534 +++
 M4MCode/M4M_MkI/M4M.Model/Epistatics/BBNK.cs  |  516 +++
 .../Epistatics/BinaryPuzzleTarget.cs          |  138 +
 .../Epistatics/CorrelationLandscape.cs        |  531 +++
 .../M4M.Model/Epistatics/Epistatics.cs        |  109 +
 M4MCode/M4M_MkI/M4M.Model/Epistatics/HTOP.cs  |   87 +
 M4MCode/M4M_MkI/M4M.Model/Epistatics/IVMC.cs  | 1490 +++++++
 M4MCode/M4M_MkI/M4M.Model/Epistatics/MC.cs    |  207 +
 .../M4M_MkI/M4M.Model/Epistatics/NQueens.cs   |  304 ++
 .../M4M_MkI/M4M.Model/Epistatics/Stacked.cs   |  138 +
 .../M4M_MkI/M4M.Model/Epistatics/Sudoku.cs    |  378 ++
 .../M4M.Model/ExperimentConfiguration.cs      |  272 ++
 M4MCode/M4M_MkI/M4M.Model/Hebbian/Hebbian.cs  |  202 +
 .../M4M_MkI/M4M.Model/Hopfield/Hopfield.cs    |  295 ++
 .../M4M.Model/LotkaVolterra/LotkaVolterra.cs  |  106 +
 M4MCode/M4M_MkI/M4M.Model/M4M.Model.csproj    |   30 +
 M4MCode/M4M_MkI/M4M.Model/MatrixPool.cs       |  146 +
 M4MCode/M4M_MkI/M4M.Model/Misc.cs             | 1945 +++++++++
 M4MCode/M4M_MkI/M4M.Model/Model.cs            | 3447 ++++++++++++++++
 .../M4M.Model/Modular/ModularTargets.cs       |  216 +
 M4MCode/M4M_MkI/M4M.Model/Modular/Modules.cs  |  386 ++
 M4MCode/M4M_MkI/M4M.Model/Population.cs       | 1947 +++++++++
 M4MCode/M4M_MkI/M4M.Model/Selection.cs        |  191 +
 .../M4M.Model/State/GraphSerialisation.cs     |  145 +
 .../M4M_MkI/M4M.Model/State/StateProviders.cs |  264 ++
 M4MCode/M4M_MkI/M4M.Model/Stats.cs            |  349 ++
 M4MCode/M4M_MkI/M4M.Model/Trace.cs            |  830 ++++
 M4MCode/M4M_MkI/M4M.Model/Typical.cs          |   74 +
 M4MCode/M4M_MkI/M4M.New/CLI/Cli.cs            |  792 ++++
 M4MCode/M4M_MkI/M4M.New/CLI/CliAnalyse.cs     |  387 ++
 M4MCode/M4M_MkI/M4M.New/CLI/CliBestiary.cs    |  735 ++++
 M4MCode/M4M_MkI/M4M.New/CLI/CliConcat.cs      |   94 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliExp.cs         |  495 +++
 M4MCode/M4M_MkI/M4M.New/CLI/CliExtract.cs     |  543 +++
 M4MCode/M4M_MkI/M4M.New/CLI/CliGen.cs         |   56 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliGroup.cs       |  269 ++
 M4MCode/M4M_MkI/M4M.New/CLI/CliGroupPlot.cs   |  298 ++
 M4MCode/M4M_MkI/M4M.New/CLI/CliHebbian.cs     |  309 ++
 M4MCode/M4M_MkI/M4M.New/CLI/CliMultiPlot.cs   |  106 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliPlot.cs        | 3539 +++++++++++++++++
 M4MCode/M4M_MkI/M4M.New/CLI/CliPlotOther.cs   | 2376 +++++++++++
 M4MCode/M4M_MkI/M4M.New/CLI/CliPresets.cs     |   46 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliProject.cs     |   81 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliReconfig.cs    |  297 ++
 .../M4M.New/CLI/CliSaturationAnalysis.cs      |   39 +
 .../M4M_MkI/M4M.New/CLI/CliSparseSwitchers.cs |  199 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliSpit.cs        |   31 +
 .../M4M.New/CLI/CliSwitchingModules.cs        |   94 +
 M4MCode/M4M_MkI/M4M.New/CLI/CliTraces.cs      |  140 +
 .../M4M.New/CommonPopulationFeedback.cs       |  471 +++
 M4MCode/M4M_MkI/M4M.New/Evolvability.cs       |  192 +
 .../ConfigurationComposer.cs                  |  327 ++
 .../ExperimentComposer.cs                     |  294 ++
 .../PopulationComposer.cs                     |  257 ++
 .../TargetPackageComposer.cs                  |  278 ++
 .../M4M.New/ExperimentGroups/ExpInfo.cs       |   93 +
 .../M4M.New/ExperimentGroups/GroupPlotting.cs |  127 +
 .../M4M.New/ExperimentGroups/GroupRunon.cs    |   68 +
 .../ExperimentGroups/GroupSaturation.cs       |  111 +
 M4MCode/M4M_MkI/M4M.New/ExperimentParsing.cs  |  816 ++++
 M4MCode/M4M_MkI/M4M.New/GenomeParsing.cs      |  175 +
 M4MCode/M4M_MkI/M4M.New/M4M.New.csproj        |   37 +
 M4MCode/M4M_MkI/M4M.New/OxyEx/Cursors.cs      |  287 ++
 .../M4M_MkI/M4M.New/OxyEx/CustomLabelAxis.cs  |   80 +
 M4MCode/M4M_MkI/M4M.New/Plotting.cs           | 1303 ++++++
 M4MCode/M4M_MkI/M4M.New/PopulationRunners.cs  |   66 +
 M4MCode/M4M_MkI/M4M.New/StringHelpers.cs      |  332 ++
 M4MCode/M4M_MkI/M4M.Old.sln                   |  137 +
 M4MCode/M4M_MkI/M4M.Old/Analysis.cs           |   85 +
 .../M4M.Old/DefaultDensePopulationFeedback.cs |  190 +
 .../M4M_MkI/M4M.Old/Epistatics/Epistatics.cs  |  113 +
 M4MCode/M4M_MkI/M4M.Old/Evolvability.cs       |  466 +++
 M4MCode/M4M_MkI/M4M.Old/Experiment.cs         |  390 ++
 M4MCode/M4M_MkI/M4M.Old/Extensions.cs         |   58 +
 M4MCode/M4M_MkI/M4M.Old/FearExperiments.cs    |  196 +
 M4MCode/M4M_MkI/M4M.Old/HModExperiments.cs    |  298 ++
 M4MCode/M4M_MkI/M4M.Old/M4M.Old.csproj        |  108 +
 M4MCode/M4M_MkI/M4M.Old/MiscPlotting.cs       |  159 +
 M4MCode/M4M_MkI/M4M.Old/OldPlotting.cs        |  240 ++
 M4MCode/M4M_MkI/M4M.Old/OldProgram.cs         | 2947 ++++++++++++++
 M4MCode/M4M_MkI/M4M.Old/OneModule.cs          |   28 +
 M4MCode/M4M_MkI/M4M.Old/OxyPlotting.cs        |  354 ++
 .../M4M.Old/Properties/AssemblyInfo.cs        |   36 +
 M4MCode/M4M_MkI/M4M.Old/Reporting/Basics.cs   |  149 +
 M4MCode/M4M_MkI/M4M.Old/Reporting/Figure.cs   |   58 +
 M4MCode/M4M_MkI/M4M.Old/Reporting/Report.cs   |  155 +
 .../M4M_MkI/M4M.Old/Reporting/Reporting.cs    |  248 ++
 M4MCode/M4M_MkI/M4M.Old/Sillyness.cs          |   71 +
 M4MCode/M4M_MkI/M4M.Old/Topology.cs           |  213 +
 M4MCode/M4M_MkI/M4M.Tests/AssertExtensions.cs |   20 +
 M4MCode/M4M_MkI/M4M.Tests/DenseGenomeTests.cs |   98 +
 M4MCode/M4M_MkI/M4M.Tests/DummyConsole.cs     |   12 +
 M4MCode/M4M_MkI/M4M.Tests/IvmcStackedTests.cs |  157 +
 M4MCode/M4M_MkI/M4M.Tests/M4M.Tests.csproj    |   31 +
 .../M4M_MkI/M4M.Tests/RegularisationTests.cs  |  100 +
 M4MCode/M4M_MkI/M4M.Tests/StateExtensions.cs  |   20 +
 M4MCode/M4M_MkI/M4M.sln                       |  181 +
 M4MCode/M4M_MkI/M4M/App.config                |    6 +
 M4MCode/M4M_MkI/M4M/M4M.csproj                |  190 +
 M4MCode/M4M_MkI/M4M/PdfExporter.cs            |  172 +
 M4MCode/M4M_MkI/M4M/Program.cs                |   41 +
 .../M4M_MkI/M4M/Properties/AssemblyInfo.cs    |   36 +
 M4MCode/M4M_MkI/M4M/packages.config           |    8 +
 M4MCode/M4M_MkI/ReadmeFigures/hebb.pdf.png    |  Bin 0 -> 12404 bytes
 M4MCode/M4M_MkI/ReadmeFigures/m4mdensedev.png |  Bin 0 -> 148533 bytes
 M4MCode/M4M_MkI/ReadmeFigures/m4mdensemod.png |  Bin 0 -> 15784 bytes
 M4MCode/M4M_MkI/ReadmeFigures/m4mplotting.png |  Bin 0 -> 111691 bytes
 M4MCode/M4M_MkI/ReadmeFigures/nb.png          |  Bin 0 -> 2654 bytes
 M4MCode/M4M_MkI/ReadmeFigures/rcs.pdf.png     |  Bin 0 -> 89001 bytes
 .../ReadmeFigures/reconfigedTraces.png        |  Bin 0 -> 60643 bytes
 .../M4M_MkI/ReadmeFigures/terminal.pdf.png    |  Bin 0 -> 12709 bytes
 .../ReadmeFigures/traceesSideBySide.png       |  Bin 0 -> 35071 bytes
 M4MCode/M4M_MkI/ReadmeFigures/traces.png      |  Bin 0 -> 53369 bytes
 M4MCode/M4M_MkI/m4minit.ps1                   |  173 +
 M4MCode/M4M_MkI/m4minit.sh                    |   11 +
 M4MCode/M4M_MkI/readme.md                     | 1367 +++++++
 M4MCode/NetState/NetState.sln                 |   25 +
 .../NetState/AutoState/AutoGraphProviders.cs  |  177 +
 .../AutoState/AutoGraphSerialisation.cs       |  624 +++
 .../NetState/AutoState/AutoSerialisation.cs   |  489 +++
 .../AutoState/AutoSerialisingStateProvider.cs |  210 +
 .../NetState/AutoState/AutoStateProviders.cs  |   71 +
 M4MCode/NetState/NetState/Memory/Buffer.cs    |  170 +
 .../NetState/NetState/Memory/MemoryPool.cs    |  171 +
 M4MCode/NetState/NetState/NetState.csproj     |   17 +
 .../Serialisation/SerialisationUtils.cs       |  834 ++++
 .../NetState/Serialisation/StateProviders.cs  | 1217 ++++++
 .../Serialisation/StreamReaderWriter.cs       | 1046 +++++
 .../NetState/Serialisation/StringTree.cs      |  209 +
 .../NetState/NetState/SoftState/SoftState.cs  |  381 ++
 .../NetState/SoftState/SoftStateStringTree.cs |  168 +
 M4MCode/NetState/NetState/State/State.cs      |  128 +
 .../NetState/NetState/State/StateFactory.cs   |  256 ++
 M4MCode/NetState/NetState/State/StateLock.cs  |   40 +
 M4MStuff/genSpread.ps1                        |   23 +-
 M4MStuff/readme.md                            |   17 +
 readme.md                                     |   14 +-
 244 files changed, 59337 insertions(+), 6 deletions(-)
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev.sln
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/App.config
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DeltaTestHelpers.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DenseContext.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeInfo.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/DoubleBufferedPanel.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/IndividualBlockPlot.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/IndividualNetworkPlot.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/IndividualSpringyPlot.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/M4MDenseDev.csproj
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/MainForm.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/MainForm.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/MainForm.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/Program.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/Properties/AssemblyInfo.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.settings
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TestForm.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TestForm.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TestForm.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TraceeWindow.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TrajectorySeries.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.Designer.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.cs
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.resx
 create mode 100644 M4MCode/M4MDenseDev/M4MDenseDev/packages.config
 create mode 100644 M4MCode/M4MDenseDev/MindlessPlotting/App.config
 create mode 100644 M4MCode/M4MDenseDev/MindlessPlotting/MindlessPlotting.csproj
 create mode 100644 M4MCode/M4MDenseDev/MindlessPlotting/Program.cs
 create mode 100644 M4MCode/M4MDenseDev/MindlessPlotting/Properties/AssemblyInfo.cs
 create mode 100644 M4MCode/M4MDenseDev/MindlessPlotting/packages.config
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod.sln
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Annotation.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/App.config
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Colours.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/DoubleBufferedPanel.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Form1.Designer.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Form1.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Form1.resx
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/M4MDenseMod.csproj
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/ObservableVector.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Program.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Properties/AssemblyInfo.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.Designer.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.resx
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.Designer.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.settings
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.Designer.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.cs
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.resx
 create mode 100644 M4MCode/M4MDenseMod/M4MDenseMod/packages.config
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting.sln
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/App.config
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Form1.Designer.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Form1.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Form1.resx
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/LogViewer.Designer.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/LogViewer.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/LogViewer.resx
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/M4MPlotting.csproj
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Program.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Properties/AssemblyInfo.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.Designer.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.resx
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.Designer.cs
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.settings
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/m4m.ico
 create mode 100644 M4MCode/M4MPlotting/M4MPlotting/packages.config
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded.CoreRunner/M4M_Ded.CoreRunner.csproj
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded.CoreRunner/Program.cs
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded.sln
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded/ExpInfo.cs
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded/GroupPlotting.cs
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded/M4M_Ded.csproj
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded/MiscPlotting.cs
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded/RunonAnalysis.cs
 create mode 100644 M4MCode/M4M_Ded/M4M_Ded/SaturationAnalysis.cs
 create mode 100644 M4MCode/M4M_MkI/.tours/enumandstructstateproviders.tour
 create mode 100644 M4MCode/M4M_MkI/.vscode/launch.json
 create mode 100644 M4MCode/M4M_MkI/.vscode/tasks.json
 create mode 100644 M4MCode/M4M_MkI/BuildTools/AutoVersion.dll
 create mode 100644 M4MCode/M4M_MkI/BuildTools/AutoVersion.runtimeconfig.json
 create mode 100644 M4MCode/M4M_MkI/M4M.Benchmarks/DenseGenomeBenchmarks.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Benchmarks/M4M.Benchmarks.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M.Benchmarks/Program.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Benchmarks/RegularisationBenchmarks.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M.CoreRunner/Program.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Analysis.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DataStructures/ComparableList.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DataStructures/DisjointSets.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DataStructures/Graph.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DataStructures/PriorityQueue.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DeltaRule/DeltaRule.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DeltaTest.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DensePopulationFeedback.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/DtmClassification.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/BBNK.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/BinaryPuzzleTarget.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/CorrelationLandscape.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/Epistatics.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/HTOP.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/IVMC.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/MC.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/NQueens.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/Stacked.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Epistatics/Sudoku.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/ExperimentConfiguration.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Hebbian/Hebbian.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Hopfield/Hopfield.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/LotkaVolterra/LotkaVolterra.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/M4M.Model.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/MatrixPool.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Misc.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Model.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Modular/ModularTargets.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Modular/Modules.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Population.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Selection.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/State/GraphSerialisation.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/State/StateProviders.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Stats.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Trace.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Model/Typical.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/Cli.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliAnalyse.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliBestiary.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliConcat.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliExp.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliExtract.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliGen.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliGroup.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliGroupPlot.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliHebbian.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliMultiPlot.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliPlot.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliPlotOther.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliPresets.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliProject.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliReconfig.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliSaturationAnalysis.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliSparseSwitchers.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliSpit.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliSwitchingModules.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CLI/CliTraces.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/CommonPopulationFeedback.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/Evolvability.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ConfigurationComposer.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ExperimentComposer.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentComposition/PopulationComposer.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentComposition/TargetPackageComposer.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentGroups/ExpInfo.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupPlotting.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupRunon.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupSaturation.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/ExperimentParsing.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/GenomeParsing.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/M4M.New.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M.New/OxyEx/Cursors.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/OxyEx/CustomLabelAxis.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/Plotting.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/PopulationRunners.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.New/StringHelpers.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old.sln
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Analysis.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/DefaultDensePopulationFeedback.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Epistatics/Epistatics.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Evolvability.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Experiment.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Extensions.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/FearExperiments.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/HModExperiments.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/M4M.Old.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/MiscPlotting.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/OldPlotting.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/OldProgram.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/OneModule.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/OxyPlotting.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Properties/AssemblyInfo.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Reporting/Basics.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Reporting/Figure.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Reporting/Report.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Reporting/Reporting.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Sillyness.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Old/Topology.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/AssertExtensions.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/DenseGenomeTests.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/DummyConsole.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/IvmcStackedTests.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/M4M.Tests.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/RegularisationTests.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.Tests/StateExtensions.cs
 create mode 100644 M4MCode/M4M_MkI/M4M.sln
 create mode 100644 M4MCode/M4M_MkI/M4M/App.config
 create mode 100644 M4MCode/M4M_MkI/M4M/M4M.csproj
 create mode 100644 M4MCode/M4M_MkI/M4M/PdfExporter.cs
 create mode 100644 M4MCode/M4M_MkI/M4M/Program.cs
 create mode 100644 M4MCode/M4M_MkI/M4M/Properties/AssemblyInfo.cs
 create mode 100644 M4MCode/M4M_MkI/M4M/packages.config
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/hebb.pdf.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/m4mdensedev.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/m4mdensemod.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/m4mplotting.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/nb.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/rcs.pdf.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/reconfigedTraces.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/terminal.pdf.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/traceesSideBySide.png
 create mode 100644 M4MCode/M4M_MkI/ReadmeFigures/traces.png
 create mode 100644 M4MCode/M4M_MkI/m4minit.ps1
 create mode 100644 M4MCode/M4M_MkI/m4minit.sh
 create mode 100644 M4MCode/M4M_MkI/readme.md
 create mode 100644 M4MCode/NetState/NetState.sln
 create mode 100644 M4MCode/NetState/NetState/AutoState/AutoGraphProviders.cs
 create mode 100644 M4MCode/NetState/NetState/AutoState/AutoGraphSerialisation.cs
 create mode 100644 M4MCode/NetState/NetState/AutoState/AutoSerialisation.cs
 create mode 100644 M4MCode/NetState/NetState/AutoState/AutoSerialisingStateProvider.cs
 create mode 100644 M4MCode/NetState/NetState/AutoState/AutoStateProviders.cs
 create mode 100644 M4MCode/NetState/NetState/Memory/Buffer.cs
 create mode 100644 M4MCode/NetState/NetState/Memory/MemoryPool.cs
 create mode 100644 M4MCode/NetState/NetState/NetState.csproj
 create mode 100644 M4MCode/NetState/NetState/Serialisation/SerialisationUtils.cs
 create mode 100644 M4MCode/NetState/NetState/Serialisation/StateProviders.cs
 create mode 100644 M4MCode/NetState/NetState/Serialisation/StreamReaderWriter.cs
 create mode 100644 M4MCode/NetState/NetState/Serialisation/StringTree.cs
 create mode 100644 M4MCode/NetState/NetState/SoftState/SoftState.cs
 create mode 100644 M4MCode/NetState/NetState/SoftState/SoftStateStringTree.cs
 create mode 100644 M4MCode/NetState/NetState/State/State.cs
 create mode 100644 M4MCode/NetState/NetState/State/StateFactory.cs
 create mode 100644 M4MCode/NetState/NetState/State/StateLock.cs
 create mode 100644 M4MStuff/readme.md

diff --git a/M4MCode/M4MDenseDev/M4MDenseDev.sln b/M4MCode/M4MDenseDev/M4MDenseDev.sln
new file mode 100644
index 0000000..312e046
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev.sln
@@ -0,0 +1,41 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4MDenseDev", "M4MDenseDev\M4MDenseDev.csproj", "{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MindlessPlotting", "MindlessPlotting\MindlessPlotting.csproj", "{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Debug|x64.ActiveCfg = Debug|x64
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Debug|x64.Build.0 = Debug|x64
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Release|x64.ActiveCfg = Release|x64
+		{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}.Release|x64.Build.0 = Release|x64
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Debug|x64.Build.0 = Debug|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Release|x64.ActiveCfg = Release|Any CPU
+		{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}.Release|x64.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {E250D9EC-4B5B-4EF5-9D28-4272DDA04CF6}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/App.config b/M4MCode/M4MDenseDev/M4MDenseDev/App.config
new file mode 100644
index 0000000..88fa402
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/App.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+    </startup>
+</configuration>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.Designer.cs
new file mode 100644
index 0000000..d8b2e7d
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.Designer.cs
@@ -0,0 +1,288 @@
+namespace M4MDenseDev
+{
+    partial class BlockDenseView
+    {
+        /// <summary> 
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Component Designer generated code
+
+        /// <summary> 
+        /// Required method for Designer support - do not modify 
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SaveFileDialog = new System.Windows.Forms.SaveFileDialog();
+            this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
+            this.NetworkCheck = new System.Windows.Forms.CheckBox();
+            this.MultiCheck = new System.Windows.Forms.CheckBox();
+            this.DeclutterCheck = new System.Windows.Forms.CheckBox();
+            this.AddCheck = new System.Windows.Forms.CheckBox();
+            this.DeltaCheck = new System.Windows.Forms.CheckBox();
+            this.AdditiveBox = new System.Windows.Forms.NumericUpDown();
+            this.TargetDropdown = new System.Windows.Forms.ComboBox();
+            this.DeltaModeDropdown = new System.Windows.Forms.ComboBox();
+            this.DeltaResultModeDropdown = new System.Windows.Forms.ComboBox();
+            this.FitnessLabel = new System.Windows.Forms.Label();
+            this.MouseLabel = new System.Windows.Forms.Label();
+            this.SaveBtn = new System.Windows.Forms.Button();
+            this.DtmLabel = new System.Windows.Forms.Label();
+            this.SpringyCheck = new System.Windows.Forms.CheckBox();
+            this.InnerPanel = new M4MDenseDev.DoubleBufferedPanel();
+            this.flowLayoutPanel1.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.AdditiveBox)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // SaveFileDialog
+            // 
+            this.SaveFileDialog.DefaultExt = "dat";
+            // 
+            // flowLayoutPanel1
+            // 
+            this.flowLayoutPanel1.AutoScroll = true;
+            this.flowLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+            this.flowLayoutPanel1.Controls.Add(this.SpringyCheck);
+            this.flowLayoutPanel1.Controls.Add(this.NetworkCheck);
+            this.flowLayoutPanel1.Controls.Add(this.MultiCheck);
+            this.flowLayoutPanel1.Controls.Add(this.DeclutterCheck);
+            this.flowLayoutPanel1.Controls.Add(this.AddCheck);
+            this.flowLayoutPanel1.Controls.Add(this.DeltaCheck);
+            this.flowLayoutPanel1.Controls.Add(this.AdditiveBox);
+            this.flowLayoutPanel1.Controls.Add(this.TargetDropdown);
+            this.flowLayoutPanel1.Controls.Add(this.DeltaModeDropdown);
+            this.flowLayoutPanel1.Controls.Add(this.DeltaResultModeDropdown);
+            this.flowLayoutPanel1.Controls.Add(this.FitnessLabel);
+            this.flowLayoutPanel1.Controls.Add(this.MouseLabel);
+            this.flowLayoutPanel1.Controls.Add(this.SaveBtn);
+            this.flowLayoutPanel1.Controls.Add(this.DtmLabel);
+            this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Left;
+            this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
+            this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 0);
+            this.flowLayoutPanel1.Name = "flowLayoutPanel1";
+            this.flowLayoutPanel1.Size = new System.Drawing.Size(124, 209);
+            this.flowLayoutPanel1.TabIndex = 1;
+            this.flowLayoutPanel1.WrapContents = false;
+            // 
+            // NetworkCheck
+            // 
+            this.NetworkCheck.AutoSize = true;
+            this.NetworkCheck.Location = new System.Drawing.Point(3, 26);
+            this.NetworkCheck.Name = "NetworkCheck";
+            this.NetworkCheck.Size = new System.Drawing.Size(92, 17);
+            this.NetworkCheck.TabIndex = 4;
+            this.NetworkCheck.Text = "Network View";
+            this.NetworkCheck.UseVisualStyleBackColor = true;
+            this.NetworkCheck.CheckedChanged += new System.EventHandler(this.NetworkCheck_CheckedChanged);
+            // 
+            // MultiCheck
+            // 
+            this.MultiCheck.AutoSize = true;
+            this.MultiCheck.Enabled = false;
+            this.MultiCheck.Location = new System.Drawing.Point(3, 49);
+            this.MultiCheck.Name = "MultiCheck";
+            this.MultiCheck.Size = new System.Drawing.Size(88, 17);
+            this.MultiCheck.TabIndex = 5;
+            this.MultiCheck.Text = "Multi-Arrange";
+            this.MultiCheck.UseVisualStyleBackColor = true;
+            this.MultiCheck.CheckedChanged += new System.EventHandler(this.MultiCheck_CheckedChanged);
+            // 
+            // DeclutterCheck
+            // 
+            this.DeclutterCheck.AutoSize = true;
+            this.DeclutterCheck.Enabled = false;
+            this.DeclutterCheck.Location = new System.Drawing.Point(3, 72);
+            this.DeclutterCheck.Name = "DeclutterCheck";
+            this.DeclutterCheck.Size = new System.Drawing.Size(69, 17);
+            this.DeclutterCheck.TabIndex = 6;
+            this.DeclutterCheck.Text = "Declutter";
+            this.DeclutterCheck.UseVisualStyleBackColor = true;
+            this.DeclutterCheck.CheckedChanged += new System.EventHandler(this.DeclutterCheck_CheckedChanged);
+            // 
+            // AddCheck
+            // 
+            this.AddCheck.AutoSize = true;
+            this.AddCheck.Location = new System.Drawing.Point(3, 95);
+            this.AddCheck.Name = "AddCheck";
+            this.AddCheck.Size = new System.Drawing.Size(99, 17);
+            this.AddCheck.TabIndex = 0;
+            this.AddCheck.Text = "Additive Mouse";
+            this.AddCheck.UseVisualStyleBackColor = true;
+            this.AddCheck.CheckedChanged += new System.EventHandler(this.AddCheck_CheckedChanged);
+            // 
+            // DeltaCheck
+            // 
+            this.DeltaCheck.AutoSize = true;
+            this.DeltaCheck.Enabled = false;
+            this.DeltaCheck.Location = new System.Drawing.Point(3, 118);
+            this.DeltaCheck.Name = "DeltaCheck";
+            this.DeltaCheck.Size = new System.Drawing.Size(86, 17);
+            this.DeltaCheck.TabIndex = 7;
+            this.DeltaCheck.Text = "Delta Mouse";
+            this.DeltaCheck.UseVisualStyleBackColor = true;
+            // 
+            // AdditiveBox
+            // 
+            this.AdditiveBox.DecimalPlaces = 3;
+            this.AdditiveBox.Enabled = false;
+            this.AdditiveBox.Increment = new decimal(new int[] {
+            1,
+            0,
+            0,
+            65536});
+            this.AdditiveBox.Location = new System.Drawing.Point(3, 141);
+            this.AdditiveBox.Name = "AdditiveBox";
+            this.AdditiveBox.Size = new System.Drawing.Size(115, 20);
+            this.AdditiveBox.TabIndex = 0;
+            this.AdditiveBox.Value = new decimal(new int[] {
+            1,
+            0,
+            0,
+            65536});
+            // 
+            // TargetDropdown
+            // 
+            this.TargetDropdown.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
+            this.TargetDropdown.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
+            this.TargetDropdown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.TargetDropdown.DropDownWidth = 300;
+            this.TargetDropdown.FormattingEnabled = true;
+            this.TargetDropdown.Location = new System.Drawing.Point(3, 167);
+            this.TargetDropdown.Name = "TargetDropdown";
+            this.TargetDropdown.Size = new System.Drawing.Size(115, 21);
+            this.TargetDropdown.TabIndex = 0;
+            this.TargetDropdown.Format += new System.Windows.Forms.ListControlConvertEventHandler(this.TargetDropdown_Format);
+            this.TargetDropdown.SelectedValueChanged += new System.EventHandler(this.TargetDropdown_SelectedValueChanged);
+            // 
+            // DeltaModeDropdown
+            // 
+            this.DeltaModeDropdown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.DeltaModeDropdown.FormattingEnabled = true;
+            this.DeltaModeDropdown.Location = new System.Drawing.Point(3, 194);
+            this.DeltaModeDropdown.Name = "DeltaModeDropdown";
+            this.DeltaModeDropdown.Size = new System.Drawing.Size(115, 21);
+            this.DeltaModeDropdown.TabIndex = 0;
+            this.DeltaModeDropdown.SelectedIndexChanged += new System.EventHandler(this.DeltaModeDropdown_SelectedIndexChanged);
+            this.DeltaModeDropdown.Format += new System.Windows.Forms.ListControlConvertEventHandler(this.DeltaModeDropdown_Format);
+            // 
+            // DeltaResultModeDropdown
+            // 
+            this.DeltaResultModeDropdown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.DeltaResultModeDropdown.FormattingEnabled = true;
+            this.DeltaResultModeDropdown.Location = new System.Drawing.Point(3, 221);
+            this.DeltaResultModeDropdown.Name = "DeltaResultModeDropdown";
+            this.DeltaResultModeDropdown.Size = new System.Drawing.Size(115, 21);
+            this.DeltaResultModeDropdown.TabIndex = 8;
+            this.DeltaResultModeDropdown.SelectedIndexChanged += new System.EventHandler(this.DeltaResultModeDropdown_SelectedIndexChanged);
+            // 
+            // FitnessLabel
+            // 
+            this.FitnessLabel.AutoSize = true;
+            this.FitnessLabel.Location = new System.Drawing.Point(3, 245);
+            this.FitnessLabel.Name = "FitnessLabel";
+            this.FitnessLabel.Size = new System.Drawing.Size(0, 13);
+            this.FitnessLabel.TabIndex = 0;
+            // 
+            // MouseLabel
+            // 
+            this.MouseLabel.AutoSize = true;
+            this.MouseLabel.Location = new System.Drawing.Point(3, 258);
+            this.MouseLabel.Name = "MouseLabel";
+            this.MouseLabel.Size = new System.Drawing.Size(0, 13);
+            this.MouseLabel.TabIndex = 0;
+            // 
+            // SaveBtn
+            // 
+            this.SaveBtn.Location = new System.Drawing.Point(3, 274);
+            this.SaveBtn.Name = "SaveBtn";
+            this.SaveBtn.Size = new System.Drawing.Size(115, 34);
+            this.SaveBtn.TabIndex = 2;
+            this.SaveBtn.Text = "Save Genome";
+            this.SaveBtn.UseVisualStyleBackColor = true;
+            this.SaveBtn.Click += new System.EventHandler(this.SaveBtn_Click);
+            // 
+            // DtmLabel
+            // 
+            this.DtmLabel.AutoSize = true;
+            this.DtmLabel.Location = new System.Drawing.Point(3, 311);
+            this.DtmLabel.Name = "DtmLabel";
+            this.DtmLabel.Size = new System.Drawing.Size(0, 13);
+            this.DtmLabel.TabIndex = 3;
+            // 
+            // SpringyCheck
+            // 
+            this.SpringyCheck.AutoSize = true;
+            this.SpringyCheck.Location = new System.Drawing.Point(3, 3);
+            this.SpringyCheck.Name = "SpringyCheck";
+            this.SpringyCheck.Size = new System.Drawing.Size(61, 17);
+            this.SpringyCheck.TabIndex = 9;
+            this.SpringyCheck.Text = "Springy";
+            this.SpringyCheck.UseVisualStyleBackColor = true;
+            this.SpringyCheck.CheckedChanged += new System.EventHandler(this.SpringyCheck_CheckedChanged);
+            // 
+            // InnerPanel
+            // 
+            this.InnerPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.InnerPanel.DoubleBuffered = false;
+            this.InnerPanel.Location = new System.Drawing.Point(124, 0);
+            this.InnerPanel.Name = "InnerPanel";
+            this.InnerPanel.Size = new System.Drawing.Size(213, 209);
+            this.InnerPanel.TabIndex = 0;
+            this.InnerPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.InnerPanel_Paint);
+            this.InnerPanel.MouseDown += new System.Windows.Forms.MouseEventHandler(this.InnerPanel_MouseDown);
+            this.InnerPanel.MouseLeave += new System.EventHandler(this.InnerPanel_MouseLeave);
+            this.InnerPanel.MouseMove += new System.Windows.Forms.MouseEventHandler(this.InnerPanel_MouseMove);
+            this.InnerPanel.MouseUp += new System.Windows.Forms.MouseEventHandler(this.InnerPanel_MouseUp);
+            this.InnerPanel.Resize += new System.EventHandler(this.InnerPanel_Resize);
+            // 
+            // BlockDenseView
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.InnerPanel);
+            this.Controls.Add(this.flowLayoutPanel1);
+            this.DoubleBuffered = true;
+            this.Name = "BlockDenseView";
+            this.Size = new System.Drawing.Size(337, 209);
+            this.flowLayoutPanel1.ResumeLayout(false);
+            this.flowLayoutPanel1.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.AdditiveBox)).EndInit();
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+        private DoubleBufferedPanel InnerPanel;
+        private System.Windows.Forms.CheckBox AddCheck;
+        private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
+        private System.Windows.Forms.Button SaveBtn;
+        private System.Windows.Forms.SaveFileDialog SaveFileDialog;
+        private System.Windows.Forms.Label FitnessLabel;
+        private System.Windows.Forms.Label MouseLabel;
+        private System.Windows.Forms.Label DtmLabel;
+        private System.Windows.Forms.CheckBox NetworkCheck;
+        private System.Windows.Forms.CheckBox MultiCheck;
+        private System.Windows.Forms.CheckBox DeclutterCheck;
+        private System.Windows.Forms.ComboBox TargetDropdown;
+        private System.Windows.Forms.NumericUpDown AdditiveBox;
+        private System.Windows.Forms.ComboBox DeltaModeDropdown;
+        private System.Windows.Forms.CheckBox DeltaCheck;
+        private System.Windows.Forms.ComboBox DeltaResultModeDropdown;
+        private System.Windows.Forms.CheckBox SpringyCheck;
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.cs b/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.cs
new file mode 100644
index 0000000..aa04173
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.cs
@@ -0,0 +1,619 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using M4M;
+using MathNet.Numerics.Random;
+
+namespace M4MDenseDev
+{
+    public partial class BlockDenseView : UserControl
+    {
+        private class NullDeltaMode : IDeltaMode
+        {
+            private NullDeltaMode()
+            {
+                // nix
+            }
+
+            public static readonly NullDeltaMode Instance = new NullDeltaMode();
+
+            public string Name => "(none)";
+
+            public void Apply(DenseGenome genome, int r, int c, double delta)
+            {
+                throw new InvalidOperationException();
+            }
+
+            public double Judge(ModelExecutionContext context, DenseGenome genome, DevelopmentRules drules, JudgementRules jrules, ITarget target, int r, int c, double delta)
+            {
+                throw new InvalidOperationException();
+            }
+        }
+
+        private class NullTarget : M4M.ITarget
+        {
+            private NullTarget()
+            {
+                // nix
+            }
+
+            public static readonly NullTarget Instance = new NullTarget();
+
+            public int Size => throw new InvalidOperationException();
+
+            public string FriendlyName => "";
+
+            public string FullName => "(none)";
+
+            public string Description => throw new InvalidOperationException();
+
+            public double Judge(Phenotype p)
+            {
+                throw new InvalidOperationException();
+            }
+
+            public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+            {
+                throw new InvalidOperationException();
+            }
+
+            public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+            {
+                throw new InvalidOperationException();
+            }
+        }
+
+        private DenseContext _denseContext;
+        public DenseContext DenseContext
+        {
+            get => _denseContext;
+            set
+            {
+                if (_denseContext != value)
+                {
+                    if (_denseContext != null)
+                        _denseContext.DenseContextUpdated -= DenseContextChanged;
+
+                    _denseContext = value;
+
+                    if (_denseContext != null)
+                        _denseContext.DenseContextUpdated += DenseContextChanged;
+
+                    ContextChanged();
+                }
+            }
+        }
+
+        public DeltaTest DeltaTest { get; private set; } = null;
+
+        private IIndividualPlot _individualPlot = new IndividualBlockPlot();
+        public IIndividualPlot IndividualPlot
+        {
+            get => _individualPlot;
+            set
+            {
+                if (value != _individualPlot)
+                {
+                    _individualPlot = value;
+                    InnerPanel.Invalidate();
+                }
+            }
+        }
+
+        private M4M.ITarget _selectedTarget = null;
+        public M4M.ITarget SelectedTarget
+        {
+            get => _selectedTarget;
+            set
+            {
+                value = value == NullTarget.Instance ? null : value;
+                if (_selectedTarget != value)
+                {
+                    _selectedTarget = value;
+                    TargetDropdown.SelectedItem = _selectedTarget ?? NullTarget.Instance;
+
+                    RefreshFitness();
+                    InnerPanel.Invalidate();
+                }
+            }
+        }
+
+        private IDeltaMode _deltaMode = null;
+        public IDeltaMode DeltaMode
+        {
+            get => _deltaMode;
+            set
+            {
+                value = value == NullDeltaMode.Instance ? null : value;
+                if (value != _deltaMode)
+                {
+                    _deltaMode = value;
+                    DeltaModeDropdown.SelectedItem = _deltaMode ?? NullDeltaMode.Instance;
+
+                    RefreshFitness();
+                    InnerPanel.Invalidate();
+                }
+            }
+        }
+
+        private DeltaResultMode _deltaResultMode = DeltaResultMode.Beneficial;
+        public DeltaResultMode DeltaResultMode
+        {
+            get => _deltaResultMode;
+            set
+            {
+                if (value != _deltaResultMode)
+                {
+                    _deltaResultMode = value;
+                    DeltaResultModeDropdown.SelectedItem = _deltaResultMode;
+                    IndividualPlot.DeltaResultMode = _deltaResultMode;
+
+                    RefreshFitness();
+                    InnerPanel.Invalidate();
+                }
+            }
+        }
+
+        public BlockDenseView()
+        {
+            this.InitializeComponent();
+            InnerPanel.DoubleBuffered = true;
+            InnerPanel.MouseWheel += InnerPanel_MouseWheel;
+
+            DeltaModeDropdown.Items.Add(NullDeltaMode.Instance);
+            DeltaModeDropdown.Items.Add(CellDeltaMode.Instance);
+            DeltaModeDropdown.Items.Add(ColumnDeltaMode.Instance);
+            DeltaModeDropdown.SelectedItem = NullDeltaMode.Instance;
+
+            DeltaResultModeDropdown.Items.Add(DeltaResultMode.Beneficial);
+            DeltaResultModeDropdown.Items.Add(DeltaResultMode.Best);
+            DeltaResultModeDropdown.SelectedItem = DeltaResultMode.Beneficial;
+
+            RefreshTargets();
+        }
+
+        private void DenseContextChanged(DenseContextThing things)
+        {
+            ContextChanged();
+        }
+
+        private void RefreshFitness()
+        {
+            if (DenseContext?.Individual?.Phenotype != null)
+            {
+                try
+                {
+                    if (SelectedTarget != null && DeltaMode != null)
+                    {
+                        DeltaTest = DeltaTestHelpers.Test(DenseContext, SelectedTarget, DenseContext.ExperimentConfiguration.ReproductionRules.DevelopmentalTransformationMatrixMutationSize, DeltaMode);
+                    }
+                    else
+                    {
+                        DeltaTest = null;
+                    }
+                }
+                catch
+                {
+                    DeltaTest = null;
+                }
+
+                try
+                {
+                    var g = DenseContext.Individual.Genome;
+                    var p = DenseContext.Individual.Phenotype;
+
+                    StringBuilder sb = new StringBuilder();
+                    int i = 0;
+                    foreach (var t in DenseContext.ExperimentConfiguration.Targets)
+                    {
+                        if (SelectedTarget == t)
+                            sb.Append("> ");
+
+                        var j = M4M.MultiMeasureJudgement.Judge(g, p, DenseContext.ExperimentConfiguration.JudgementRules, t);
+                        sb.AppendLine($"f_{i} = {j.CombinedFitness:G5}");
+                        i++;
+                    }
+
+                    FitnessLabel.Text = sb.ToString().Trim();
+                }
+                catch
+                {
+                    FitnessLabel.Text = "(cannot measure fitness)";
+                }
+            }
+            else
+            {
+                FitnessLabel.Text = "";
+                DeltaTest = null;
+            }
+        }
+
+        private void RefreshClassification()
+        {
+            try
+            {
+                int N = DenseContext.ExperimentConfiguration.Size;
+                var dtm = DenseContext.Genome.TransMat;
+                var threshold = M4M.DtmClassification.ComputeAutoThreshold(dtm, 10);
+
+                // classify
+                var dtmInfo = new M4M.DtmInfo(dtm, M4M.DtmClassification.DiscerneTrueModules(dtm, threshold));
+                var desc = M4M.Module.DescribeUnsortedCompact(M4M.Module.OrderByTraits(dtmInfo.Modules));
+
+                try
+                {
+                    // we don't trust this
+                    var clas = M4M.DtmClassification.Type1Classify(dtmInfo, threshold);
+                    DtmLabel.Text = desc + "\n" + clas;
+                }
+                catch
+                {
+                    DtmLabel.Text = desc;
+                }
+            }
+            catch
+            {
+                DtmLabel.Text = "(cannot classify dtm)";
+            }
+        }
+
+        private void RefreshTargets()
+        {
+            TargetDropdown.Items.Clear();
+            if (DenseContext == null)
+                return;
+
+            try
+            {
+                TargetDropdown.Items.Add(NullTarget.Instance);
+                foreach (var t in DenseContext.ExperimentConfiguration.Targets)
+                    TargetDropdown.Items.Add(t);
+
+                if (SelectedTarget != null && DenseContext.ExperimentConfiguration.Targets.Contains(SelectedTarget))
+                {
+                    TargetDropdown.SelectedItem = SelectedTarget;
+                }
+                else
+                {
+                    SelectedTarget = null;
+                    TargetDropdown.SelectedItem = NullTarget.Instance;
+                }
+            }
+            catch
+            {
+            }
+        }
+
+        private void ContextChanged()
+        {
+            RefreshFitness();
+            RefreshClassification();
+            RefreshTargets();
+
+            InnerPanel.Invalidate();
+        }
+
+        private void InnerPanel_Paint(object sender, PaintEventArgs e)
+        {
+            var g = e.Graphics;
+            var b = InnerPanel.ClientRectangle;
+            g.Clear(Color.FromArgb(130, 130, 90));
+
+            if (DenseContext == null)
+            {
+                DrawNullContext(g, b);
+            }
+            else
+            {
+                DrawBlockContext(g, b);
+            }
+        }
+
+        private void DrawNullContext(Graphics g, Rectangle b)
+        {
+            using (var font = new Font(FontFamily.GenericMonospace, 10))
+            {
+                g.DrawString("No Dense Context associated with BlockDenseView.", font, Brushes.Black, InnerPanel.ClientRectangle);
+            }
+        }
+
+        private void DrawBlockContext(Graphics g, Rectangle b)
+        {
+            IndividualPlot.Draw(g, b, DenseContext.Individual, DeltaTest);
+        }
+
+        private void InnerPanel_Resize(object sender, EventArgs e)
+        {
+            InnerPanel.Invalidate();
+        }
+        
+        private bool IsMouseDown = false;
+        public double LeftDrawValue = 1.0;
+        public double RightDrawValue = -1.0;
+        public double MiddleDrawValue = 0.0;
+        private ContextElementInfo MouseLast = ContextElementInfo.None;
+        private void InnerPanel_MouseDown(object sender, MouseEventArgs e)
+        {
+            IsMouseDown = true;
+            MouseLast = ContextElementInfo.None;
+            MouseApply(e);
+            InnerPanel.Focus();
+        }
+
+        private void InnerPanel_MouseWheel(object sender, MouseEventArgs e)
+        {
+            //IndividualBlockPlot.DtmColours = LerpColours.WHot(-5.0, 5.0);
+            //InnerPanel.Invalidate();
+        }
+
+        private void InnerPanel_MouseMove(object sender, MouseEventArgs e)
+        {
+            if (IsMouseDown)
+                MouseApply(e);
+            else
+                MouseOver(e);
+        }
+
+        private void MouseOver(MouseEventArgs e)
+        {
+            var element = IndividualPlot.HitTest(new PointF((float)e.Location.X, (float)e.Location.Y));
+            MouseOver(element);
+        }
+
+        private void MouseOver(ContextElementInfo element)
+        {
+            if (element.ContextElement == ContextElement.InitialState)
+            {
+                MouseLabel.Text = $"G[{element.IndexI}] = {DenseContext.Genome.InitialState[element.IndexI]:G5}";
+            }
+            else if (element.ContextElement == ContextElement.DevelopmentalTransformationMatrix)
+            {
+                MouseLabel.Text = $"B[{element.IndexI}, {element.IndexJ}] = {DenseContext.Genome.TransMat[element.IndexI, element.IndexJ]:G5}";
+            }
+            else if (element.ContextElement == ContextElement.Phenotype)
+            {
+                MouseLabel.Text = $"P[{element.IndexI}] = {DenseContext.Individual.Phenotype[element.IndexI]:G5}";
+            }
+            else
+            {
+                MouseLabel.Text = "";
+            }
+        }
+
+        private void MouseApply(MouseEventArgs e)
+        {
+            var element = IndividualPlot.HitTest(new PointF((float)e.Location.X, (float)e.Location.Y));
+            if (element == MouseLast || (e.Button != MouseButtons.Left && e.Button != MouseButtons.Right && e.Button != MouseButtons.Middle))
+                return;
+
+            bool addMode = AddCheck.Checked;
+            bool deltaMode = AddCheck.Checked && DeltaCheck.Checked;
+            double addModifier = (double)AdditiveBox.Value;
+
+            var DrawValue = e.Button == MouseButtons.Left ? LeftDrawValue
+                : e.Button == MouseButtons.Right ? RightDrawValue
+                : MiddleDrawValue;
+
+            if (element.ContextElement == ContextElement.InitialState)
+            {
+                //DenseContext.Genome.InitialState[element.IndexI] = DrawValue;
+                
+                if (addMode && DrawValue != 0.0)
+                    DenseContext.Genome.InitialState[element.IndexI] += DrawValue * addModifier;
+                else
+                    DenseContext.Genome.InitialState[element.IndexI] = DrawValue;
+                DenseContext.NotifyChange(DenseContextThing.Genome);
+            }
+
+            if (element.ContextElement == ContextElement.DevelopmentalTransformationMatrix)
+            {
+                if (deltaMode && DrawValue != 0.0 && DeltaMode != null)
+                    DeltaMode.Apply(DenseContext.Genome, element.IndexI, element.IndexJ, DrawValue * addModifier);
+                else if (addMode && DrawValue != 0.0)
+                    DenseContext.Genome.TransMat[element.IndexI, element.IndexJ] += DrawValue * addModifier;
+                else
+                    DenseContext.Genome.TransMat[element.IndexI, element.IndexJ] = DrawValue;
+                DenseContext.NotifyChange(DenseContextThing.Genome);
+            }
+
+            MouseLast = element;
+            MouseOver(element);
+        }
+
+        private void InnerPanel_MouseUp(object sender, MouseEventArgs e)
+        {
+            IsMouseDown = false;
+        }
+
+        private void InnerPanel_MouseLeave(object sender, EventArgs e)
+        {
+            IsMouseDown = false;
+            MouseLabel.Text = "";
+        }
+
+        private void SaveBtn_Click(object sender, EventArgs e)
+        {
+            var res = SaveFileDialog.ShowDialog();
+            if (res == DialogResult.OK)
+            {
+                var filename = SaveFileDialog.FileName;
+                M4M.Analysis.SaveGenome(filename, DenseContext.Genome);
+            }
+            //M4M.Analysis.SaveGenome(filename, DenseContext.Genome);
+            //DenseContext.ExperimentConfiguration;
+        }
+
+        private void PreparePlot()
+        {
+            if (SpringyCheck.Checked)
+            {
+                IndividualPlot = PrepareIndividualSpringyPlot();
+            }
+            else if (NetworkCheck.Checked)
+            {
+                IndividualPlot = PrepareIndividualNetworkPlot();
+            }
+            else
+            {
+                IndividualPlot = PrepareIndividualBlockPlot();
+            }
+        }
+
+        private IndividualSpringyPlot PrepareIndividualSpringyPlot()
+        {
+            return new IndividualSpringyPlot();
+        }
+
+        private IndividualNetworkPlot PrepareIndividualNetworkPlot()
+        {
+            var plot = new IndividualNetworkPlot()
+            {
+                NodeArranger = MultiCheck.Checked
+                    ? new MultiArranger(new CircleArranger() { ZeroAngle = (float)Math.PI / -4f })
+                    : (INodeArranger)new CircleArranger(),
+                DeltaResultMode = DeltaResultMode
+            };
+
+            plot.DeclutterThresholdFactor = DeclutterCheck.Checked ? 10 : -1;
+
+            return plot;
+        }
+
+        private IndividualBlockPlot PrepareIndividualBlockPlot()
+        {
+            var plot = new IndividualBlockPlot()
+            {
+                DeltaResultMode = DeltaResultMode
+            };
+
+            return plot;
+        }
+
+        private void NetworkCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            PreparePlot();
+
+            MultiCheck.Enabled = NetworkCheck.Checked;
+            DeclutterCheck.Enabled = NetworkCheck.Checked;
+        }
+
+        private void MultiCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            PreparePlot();
+        }
+
+        private void DeclutterCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            PreparePlot();
+        }
+
+        private void AddCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            AdditiveBox.Enabled = AddCheck.Checked;
+            DeltaCheck.Enabled = AddCheck.Checked;
+        }
+
+        private void TargetDropdown_SelectedValueChanged(object sender, EventArgs e)
+        {
+            SelectedTarget = (M4M.ITarget)TargetDropdown.SelectedItem;
+        }
+
+        private void TargetDropdown_Format(object sender, ListControlConvertEventArgs e)
+        {
+            var t = (M4M.ITarget)e.ListItem;
+            e.Value = t.FriendlyName + ": " + t.FullName;
+        }
+
+        private void DeltaModeDropdown_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            DeltaMode = (IDeltaMode)DeltaModeDropdown.SelectedItem;
+        }
+
+        private void DeltaModeDropdown_Format(object sender, ListControlConvertEventArgs e)
+        {
+            var t = (IDeltaMode)e.ListItem;
+            e.Value = t.Name;
+        }
+
+        private void DeltaResultModeDropdown_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            DeltaResultMode = (DeltaResultMode)DeltaResultModeDropdown.SelectedItem;
+        }
+
+        private void SpringyCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            PreparePlot();
+        }
+    }
+
+    public enum ContextElement
+    {
+        None,
+        InitialState,
+        DevelopmentalTransformationMatrix,
+        Phenotype,
+    }
+
+    public struct ContextElementInfo
+    {
+        public static readonly ContextElementInfo None = new ContextElementInfo(ContextElement.None, -1, -1);
+
+        public ContextElementInfo(ContextElement contextElement, int indexI, int indexJ) : this()
+        {
+            ContextElement = contextElement;
+            IndexI = indexI;
+            IndexJ = indexJ;
+        }
+
+        public ContextElement ContextElement { get; }
+        public int IndexI { get; }
+        public int IndexJ { get; } // only applicable to Dtm
+
+        public static bool operator ==(ContextElementInfo l, ContextElementInfo r)
+        {
+            return l.ContextElement == r.ContextElement &&
+                   l.IndexI == r.IndexI &&
+                   l.IndexJ == r.IndexJ;
+        }
+
+        public static bool operator !=(ContextElementInfo l, ContextElementInfo r)
+        {
+            return !(l == r);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is ContextElementInfo))
+            {
+                return false;
+            }
+
+            var info = (ContextElementInfo)obj;
+            return ContextElement == info.ContextElement &&
+                   IndexI == info.IndexI &&
+                   IndexJ == info.IndexJ;
+        }
+
+        public override int GetHashCode()
+        {
+            var hashCode = 1638630285;
+            hashCode = hashCode * -1521134295 + ContextElement.GetHashCode();
+            hashCode = hashCode * -1521134295 + IndexI.GetHashCode();
+            hashCode = hashCode * -1521134295 + IndexJ.GetHashCode();
+            return hashCode;
+        }
+    }
+
+    public interface IIndividualPlot
+    {
+        ContextElementInfo HitTest(PointF p);
+        DeltaResultMode DeltaResultMode { get; set; }
+        void Draw(Graphics g, Rectangle b, M4M.DenseIndividual individual, DeltaTest optionalDeltaTest = null);
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.resx b/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.resx
new file mode 100644
index 0000000..8029080
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/BlockDenseView.resx
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="SaveFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DeltaTestHelpers.cs b/M4MCode/M4MDenseDev/M4MDenseDev/DeltaTestHelpers.cs
new file mode 100644
index 0000000..5e56148
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DeltaTestHelpers.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using M4M;
+
+namespace M4MDenseDev
+{
+    public static class DeltaTestHelpers
+    {
+        public static DeltaTest Test(DenseContext denseContext, ITarget target, double deltaMag, IDeltaMode deltaMode)
+        {
+            return DeltaTest.Test(denseContext.Clone().Genome, denseContext.ExperimentConfiguration.DevelopmentRules, denseContext.ExperimentConfiguration.JudgementRules, target, deltaMag, deltaMode);
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DenseContext.cs b/M4MCode/M4MDenseDev/M4MDenseDev/DenseContext.cs
new file mode 100644
index 0000000..2d4b6d9
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DenseContext.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using M4M;
+
+namespace M4MDenseDev
+{
+    [Flags]
+    public enum DenseContextThing
+    {
+        Individual = 1,
+        ExperimentConfiguration = 2,
+        Seed = 4,
+        Epigenetic = 8,
+        Genome = 16,
+        Everything = Individual | ExperimentConfiguration | Seed | Epigenetic | Genome
+    }
+
+    public delegate void DenseContextUpdated(DenseContextThing things);
+
+    public class DenseContext
+    {
+        public static DenseContext FromPopulationExperiment(M4M.PopulationExperiment<M4M.DenseIndividual> exp)
+        {
+            var rand = new MathNet.Numerics.Random.MersenneTwister();
+            var example = exp.Population.PeekRandom(rand);
+
+            // HACK: reset targets if epoch == 0
+            if (exp.Epoch == 0)
+            {
+                foreach (var t in exp.PopulationConfig.ExperimentConfiguration.Targets)
+                {
+                    ExposureInformation exposureInformation = new ExposureInformation(exp.PopulationConfig.ExperimentConfiguration.GenerationsPerTargetPerEpoch); // ignored
+                    t.NextExposure(rand, exp.PopulationConfig.ExperimentConfiguration.JudgementRules, exp.Epoch, ref exposureInformation);
+                }
+            }
+            
+            foreach (var t in exp.PopulationConfig.ExperimentConfiguration.Targets)
+                t.NextGeneration(rand, exp.PopulationConfig.ExperimentConfiguration.JudgementRules);
+
+            return new DenseContext(example.Epigenetic, 0, exp.PopulationConfig.ExperimentConfiguration, example.Genome);
+        }
+
+        private bool _epigenetic;
+        public bool Epigenetic
+        {
+            get => _epigenetic;
+            set
+            {
+                if (_epigenetic != value)
+                {
+                    _epigenetic = value;
+                    Redevelop(DenseContextThing.Epigenetic);
+                }
+            }
+        }
+
+        private int _seed;
+        public int Seed
+        {
+            get => _seed;
+            set
+            {
+                if (_seed != value)
+                {
+                    _seed = value;
+                    Redevelop(DenseContextThing.Seed);
+                }
+            }
+        }
+
+        private M4M.ExperimentConfiguration _experimentConfiguration;
+        public M4M.ExperimentConfiguration ExperimentConfiguration
+        {
+            get => _experimentConfiguration;
+            set
+            {
+                if (_experimentConfiguration != value)
+                {
+                    _experimentConfiguration = value;
+                    Redevelop(DenseContextThing.ExperimentConfiguration);
+                }
+            }
+        }
+
+        public bool _computeTrajectories = true;
+        public bool ComputeTrajectories
+        {
+            get => _computeTrajectories;
+            set
+            {
+                if (_computeTrajectories != value)
+                {
+                    _computeTrajectories = value;
+                    if (_computeTrajectories)
+                        Redevelop(DenseContextThing.Seed); // eh, good enough
+                }
+            }
+        }
+        
+        private M4M.DenseGenome _genome;
+
+        public double[][] Trajectories { get; private set; }
+
+        public DenseContext(bool epigenetic, int seed, ExperimentConfiguration experimentConfiguration, DenseGenome genome)
+        {
+            _epigenetic = epigenetic;
+            _seed = seed;
+            _experimentConfiguration = experimentConfiguration;
+            _genome = genome;
+
+            Redevelop(DenseContextThing.Everything);
+        }
+
+        public M4M.DenseGenome Genome
+        {
+            get => _genome;
+            set
+            {
+                if (_genome != value)
+                {
+                    _genome = value;
+                    Redevelop(DenseContextThing.Genome);
+                }
+            }
+        }
+
+        public void NotifyChange(DenseContextThing changes = DenseContextThing.Everything)
+        {
+            Redevelop(changes);
+        }
+
+        public M4M.DenseIndividual Individual { get; private set; }
+
+        public event DenseContextUpdated DenseContextUpdated;
+
+        private void Redevelop(DenseContextThing changes)
+        {
+            var rand1 = new MathNet.Numerics.Random.MersenneTwister(Seed);
+            var modelExecutionContext = new ModelExecutionContext(rand1);
+            Individual = M4M.DenseIndividual.Develop(Genome.Clone(modelExecutionContext), modelExecutionContext, ExperimentConfiguration.DevelopmentRules, Epigenetic);
+            
+            var rand2 = new MathNet.Numerics.Random.MersenneTwister(Seed);
+
+            if (ComputeTrajectories)
+            {
+                double[][] trajectories = null;
+                Genome.DevelopWithTrajectories(rand2, ExperimentConfiguration.DevelopmentRules, ref trajectories);
+                Trajectories = trajectories;
+            }
+            else
+            {
+                Trajectories = null;
+            }
+
+            DenseContextUpdated?.Invoke(changes | DenseContextThing.Individual);
+        }
+
+        public DenseContext Clone()
+        {
+            var rand1 = new MathNet.Numerics.Random.MersenneTwister(Seed);
+            var modelExecutionContext = new ModelExecutionContext(rand1);
+            var clone = new DenseContext(Epigenetic, Seed, ExperimentConfiguration, Genome.Clone(modelExecutionContext));
+
+            clone.ComputeTrajectories = ComputeTrajectories;
+            return clone;
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeInfo.cs b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeInfo.cs
new file mode 100644
index 0000000..faeac73
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeInfo.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4MDenseDev
+{
+    public interface ITimeMeasure
+    {
+        string Name { get; }
+    }
+
+    public class Generations : ITimeMeasure
+    {
+        public string Name => "Generations";
+    }
+
+    public class Epochs : ITimeMeasure
+    {
+        public string Name => "Epochs";
+    }
+
+    public struct TraceeSample
+    {
+        public readonly long Time;
+        public readonly double[] InitialState;
+        public readonly double[] DevelopmentalTransformationMatrix;
+
+        public TraceeSample(long time, double[] initialState, double[] developmentalTransformationMatrix)
+        {
+            Time = time;
+            InitialState = initialState;
+            DevelopmentalTransformationMatrix = developmentalTransformationMatrix;
+        }
+    }
+
+    public class DenseTraceeInfo
+    {
+        public int Size { get; }
+        public TraceeSample[] Samples { get; }
+        public ITimeMeasure TimeMeasure { get; }
+
+        public bool HasRegCoefs { get; }
+        public bool HasInitialStates { get; }
+
+        public DenseTraceeInfo(int size, TraceeSample[] samples, ITimeMeasure measure)
+        {
+            Size = size;
+            Samples = samples;
+            TimeMeasure = measure;
+            
+            HasRegCoefs = samples.Any(ts => ts.DevelopmentalTransformationMatrix != null);
+            HasInitialStates = samples.Any(ts => ts.InitialState != null);
+        }
+
+        public static DenseTraceeInfo FromTraceInfo(M4M.TraceInfo<M4M.DenseIndividual> traceInfo, bool epochsx)
+        {
+            return FromWholeSamples(traceInfo.TraceWholeSamples, epochsx);
+        }
+
+        public static DenseTraceeInfo FromWholeSamples(List<M4M.WholeSample<M4M.DenseIndividual>> wholesamples, bool epochsx)
+        {
+            int size = wholesamples[0].Target.Size;
+
+            var samples = wholesamples.Select(ws =>
+                new TraceeSample(
+                    epochsx ? ws.Epoch : ws.Generations,
+                    ws.Judgements[0].Individual.Genome.InitialState.ToArray(),
+                    ws.Judgements[0].Individual.Genome.TransMat.ToRowMajorArray()
+                    )).OrderBy(ts => ts.Time).ToArray();
+
+            var measure = epochsx ? (ITimeMeasure)new Epochs() : new Generations();
+
+            return new DenseTraceeInfo(size, samples, measure);
+        }
+
+        public static DenseTraceeInfo FromRcsInfo(int samplePeriod, double[][] rcsTrajectories)
+        {
+            int size = (int)Math.Round(Math.Sqrt(rcsTrajectories.Length));
+            var baseRcsSamples = M4M.Misc.TrajectoriesToSamples(rcsTrajectories);
+            var samples = baseRcsSamples.Select((dtm, i) => new TraceeSample(i * samplePeriod, null, dtm)).ToArray();
+
+            var measure = new Epochs();
+
+            return new DenseTraceeInfo(size, samples, measure);
+        }
+
+        public static DenseTraceeInfo FromRcsAndIstInfo(int samplePeriod, double[][] rcsTrajectories, double[][] istTrajectories)
+        {
+            int size = (int)Math.Round(Math.Sqrt(rcsTrajectories.Length));
+            var baseRcsSamples = M4M.Misc.TrajectoriesToSamples(rcsTrajectories);
+            var baseIstSamples = M4M.Misc.TrajectoriesToSamples(istTrajectories);
+            var samples = baseRcsSamples.Select((dtm, i) => new TraceeSample(i * samplePeriod, baseIstSamples[i], dtm)).ToArray();
+
+            var measure = new Epochs();
+
+            return new DenseTraceeInfo(size, samples, measure);
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.Designer.cs
new file mode 100644
index 0000000..440a076
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.Designer.cs
@@ -0,0 +1,142 @@
+namespace M4MDenseDev
+{
+    partial class DenseTraceeView
+    {
+        /// <summary> 
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Component Designer generated code
+
+        /// <summary> 
+        /// Required method for Designer support - do not modify 
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.ControlPanel = new System.Windows.Forms.Panel();
+            this.RcsCheck = new System.Windows.Forms.CheckBox();
+            this.IsCheck = new System.Windows.Forms.CheckBox();
+            this.MeanDownsampleCheck = new System.Windows.Forms.CheckBox();
+            this.HideTrackerCheck = new System.Windows.Forms.CheckBox();
+            this.groupBox1 = new System.Windows.Forms.GroupBox();
+            this.ControlPanel.SuspendLayout();
+            this.groupBox1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // ControlPanel
+            // 
+            this.ControlPanel.AutoSize = true;
+            this.ControlPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+            this.ControlPanel.Controls.Add(this.groupBox1);
+            this.ControlPanel.Controls.Add(this.MeanDownsampleCheck);
+            this.ControlPanel.Controls.Add(this.HideTrackerCheck);
+            this.ControlPanel.Dock = System.Windows.Forms.DockStyle.Left;
+            this.ControlPanel.Location = new System.Drawing.Point(0, 0);
+            this.ControlPanel.Name = "ControlPanel";
+            this.ControlPanel.Size = new System.Drawing.Size(101, 212);
+            this.ControlPanel.TabIndex = 2;
+            // 
+            // RcsCheck
+            // 
+            this.RcsCheck.AutoSize = true;
+            this.RcsCheck.Checked = true;
+            this.RcsCheck.CheckState = System.Windows.Forms.CheckState.Checked;
+            this.RcsCheck.Location = new System.Drawing.Point(6, 19);
+            this.RcsCheck.Name = "RcsCheck";
+            this.RcsCheck.Size = new System.Drawing.Size(76, 17);
+            this.RcsCheck.TabIndex = 0;
+            this.RcsCheck.Text = "Reg Coefs";
+            this.RcsCheck.UseVisualStyleBackColor = true;
+            this.RcsCheck.CheckedChanged += new System.EventHandler(this.RcsCheck_CheckedChanged);
+            // 
+            // IsCheck
+            // 
+            this.IsCheck.AutoSize = true;
+            this.IsCheck.Location = new System.Drawing.Point(6, 42);
+            this.IsCheck.Name = "IsCheck";
+            this.IsCheck.Size = new System.Drawing.Size(83, 17);
+            this.IsCheck.TabIndex = 1;
+            this.IsCheck.Text = "Initial States";
+            this.IsCheck.UseVisualStyleBackColor = true;
+            this.IsCheck.CheckedChanged += new System.EventHandler(this.IsCheck_CheckedChanged);
+            // 
+            // MeanDownsampleCheck
+            // 
+            this.MeanDownsampleCheck.AutoSize = true;
+            this.MeanDownsampleCheck.Location = new System.Drawing.Point(6, 100);
+            this.MeanDownsampleCheck.Name = "MeanDownsampleCheck";
+            this.MeanDownsampleCheck.Size = new System.Drawing.Size(83, 17);
+            this.MeanDownsampleCheck.TabIndex = 1;
+            this.MeanDownsampleCheck.Text = "Mean Downsample";
+            this.MeanDownsampleCheck.UseVisualStyleBackColor = true;
+            this.MeanDownsampleCheck.Checked = true;
+            this.MeanDownsampleCheck.CheckedChanged += new System.EventHandler(this.MeanDownsampleCheck_CheckedChanged);
+            // 
+            // HideTrackerCheck
+            // 
+            this.HideTrackerCheck.AutoSize = true;
+            this.HideTrackerCheck.Location = new System.Drawing.Point(6, 120);
+            this.HideTrackerCheck.Name = "HideTrackerCheck";
+            this.HideTrackerCheck.Size = new System.Drawing.Size(83, 17);
+            this.HideTrackerCheck.TabIndex = 1;
+            this.HideTrackerCheck.Text = "Hide Tracker";
+            this.HideTrackerCheck.UseVisualStyleBackColor = true;
+            this.HideTrackerCheck.Checked = false;
+            this.HideTrackerCheck.CheckedChanged += new System.EventHandler(this.HideTrackerCheck_CheckedChanged);
+            this.HideTrackerCheck.Enabled = false;
+            // 
+            // groupBox1
+            // 
+            this.groupBox1.AutoSize = true;
+            this.groupBox1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+            this.groupBox1.Controls.Add(this.RcsCheck);
+            this.groupBox1.Controls.Add(this.IsCheck);
+            this.groupBox1.Location = new System.Drawing.Point(3, 3);
+            this.groupBox1.Name = "groupBox1";
+            this.groupBox1.Size = new System.Drawing.Size(95, 78);
+            this.groupBox1.TabIndex = 3;
+            this.groupBox1.TabStop = false;
+            this.groupBox1.Text = "Lines";
+            // 
+            // DenseTraceeView
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.ControlPanel);
+            this.Name = "DenseTraceeView";
+            this.Size = new System.Drawing.Size(369, 212);
+            this.Load += new System.EventHandler(this.DenseTraceeView_Load);
+            this.ControlPanel.ResumeLayout(false);
+            this.ControlPanel.PerformLayout();
+            this.groupBox1.ResumeLayout(false);
+            this.groupBox1.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Panel ControlPanel;
+        private System.Windows.Forms.CheckBox RcsCheck;
+        private System.Windows.Forms.GroupBox groupBox1;
+        private System.Windows.Forms.CheckBox IsCheck;
+        private System.Windows.Forms.CheckBox MeanDownsampleCheck;
+        private System.Windows.Forms.CheckBox HideTrackerCheck;
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.cs b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.cs
new file mode 100644
index 0000000..b6dd352
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.cs
@@ -0,0 +1,371 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using OxyPlot;
+
+namespace M4MDenseDev
+{
+    public delegate void DenseTraceeViewMoused(long time, TraceeSample closestSample);
+
+    public partial class DenseTraceeView : UserControl
+    {
+        public bool ShowControls
+        {
+            get => ControlPanel.Visible;
+            set => ControlPanel.Visible = value;
+        }
+
+        private DenseTraceeInfo _denseTraceeInfo;
+        public DenseTraceeInfo DenseTraceeInfo
+        {
+            get => _denseTraceeInfo;
+            set
+            {
+                if (_denseTraceeInfo != value)
+                {
+                    _denseTraceeInfo = value;
+                    InfoChanged();
+                }
+            }
+        }
+        
+        public OxyPlot.WindowsForms.PlotView PlotView { get; private set; }
+        private PlotModel PlotModel;
+        private OxyPlot.Axes.LinearAxis XAxis;
+        private OxyPlot.Axes.LinearAxis InitialStateAxis;
+        private OxyPlot.Axes.LinearAxis RegCoefAxis;
+        private TrajectorySeries[] InitialStateSeries;
+        private TrajectorySeries[] RegCoefSeries;
+        
+        public DenseTraceeView()
+        {
+            InitializeComponent();
+        }
+
+        private void DenseTraceeView_Load(object sender, EventArgs e)
+        {
+            DoLoad();
+        }
+
+        private void DoLoad()
+        {
+            if (PlotView == null)
+            {
+                PlotView = new OxyPlot.WindowsForms.PlotView();
+                PlotView.Dock = DockStyle.Fill;
+                this.Controls.Add(PlotView);
+                PlotView.BringToFront();
+            }
+        }
+
+        private void UpdateUi()
+        {
+            if (DenseTraceeInfo == null)
+            {
+                // ui
+                IsCheck.Enabled = false;
+                RcsCheck.Enabled = false;
+            }
+            else
+            {
+                // ui
+                IsCheck.Enabled = DenseTraceeInfo.HasInitialStates;
+                RcsCheck.Enabled = DenseTraceeInfo.HasRegCoefs;
+            }
+
+            if (ShowControls)
+                ControlPanel.Invalidate(true);
+        }
+
+        private void InfoChanged()
+        {
+            UpdateUi();
+            UpdatePlot();
+        }
+
+        private void UpdatePlot()
+        {
+            DoLoad();
+            
+            if (DenseTraceeInfo == null)
+            {
+                // plot
+                PlotView.Model = null;
+            }
+            else
+            {
+                // plot
+                PlotView.Model = null; // important
+                PlotNow();
+                PlotView.Model = PlotModel;
+                PlotView.MouseDown += PlotView_MouseDown;
+                PlotView.MouseUp += PlotView_MouseUp;
+                PlotView.MouseLeave += PlotView_MouseLeave;
+                PlotView.MouseMove += PlotView_MouseMove;
+            }
+
+            PlotView.Invalidate();
+        }
+
+        private void PlotView_MouseMove(object sender, MouseEventArgs e)
+        {
+            if (IsMouseDown)
+                PlotMouseEvent(e);
+        }
+
+        private void PlotView_MouseLeave(object sender, EventArgs e)
+        {
+            IsMouseDown = false;
+        }
+
+        private void PlotView_MouseUp(object sender, MouseEventArgs e)
+        {
+            IsMouseDown = false;
+        }
+
+        public event DenseTraceeViewMoused Moused;
+        private bool IsMouseDown = false;
+        private void PlotView_MouseDown(object sender, MouseEventArgs e)
+        {
+            IsMouseDown = true;
+            PlotMouseEvent(e);
+        }
+
+        private void PlotMouseEvent(MouseEventArgs e)
+        {
+            if (e.Button != MouseButtons.Left)
+                return;
+
+            var time = (long)XAxis.InverseTransform(e.X);
+
+            if (!M4M.Misc.FirstIndex(DenseTraceeInfo.Samples, ts => ts.Time >= time, out var index, out var _))
+            {
+                index = DenseTraceeInfo.Samples.Length - 1;
+            }
+
+            if (index > 0 && (time - DenseTraceeInfo.Samples[index - 1].Time) < (DenseTraceeInfo.Samples[index].Time - time))
+                index--;
+
+            var closestSample = DenseTraceeInfo.Samples[index];
+
+            Moused?.Invoke(time, closestSample);
+        }
+
+        private void PlotNow()
+        {
+            PlotModel = new PlotModel();
+
+            XAxis = new OxyPlot.Axes.LinearAxis() { Title = DenseTraceeInfo.TimeMeasure.Name, Position = OxyPlot.Axes.AxisPosition.Bottom, Key = "x" };
+            int ytier = 0;
+            InitialStateAxis =
+                DenseTraceeInfo.HasInitialStates == false ? null :
+                new OxyPlot.Axes.LinearAxis() { Title = "Initial State Expression", Position = OxyPlot.Axes.AxisPosition.Left, Key = "is", PositionTier = ytier++ };
+            RegCoefAxis =
+                DenseTraceeInfo.HasRegCoefs == false ? null :
+                new OxyPlot.Axes.LinearAxis() { Title = "Regulation Coeficients", Position = OxyPlot.Axes.AxisPosition.Left, Key = "rcs", PositionTier = ytier++ };
+            
+            PlotModel.Axes.Add(XAxis);
+            if (InitialStateAxis != null)
+                PlotModel.Axes.Add(InitialStateAxis);
+            if (RegCoefAxis != null)
+                PlotModel.Axes.Add(RegCoefAxis);
+
+            if (DenseTraceeInfo.HasInitialStates)
+            {
+                InitialStateSeries = new TrajectorySeries[DenseTraceeInfo.Size];
+                var colours = Colours1D(DenseTraceeInfo.Size, OxyColors.DarkViolet, OxyColors.Violet);
+                for (int i = 0; i < DenseTraceeInfo.Size; i++)
+                {
+                    var trajs = new TrajectorySeries(DenseTraceeInfo.Samples.Where(s => s.InitialState != null).Select(s => new DataPoint(s.Time, s.InitialState[i])).ToList(), TrajectoryDownsampleMode, Math.Max((int)PlotView.ClientArea.Width / 2 + 2, 10));
+                    trajs.Color = colours[i];
+                    trajs.XAxisKey = "x";
+                    trajs.YAxisKey = "is";
+                    trajs.Tag = "is" + i;
+                    trajs.CanTrackerInterpolatePoints = false;
+                    InitialStateSeries[i] = trajs;
+                    PlotModel.Series.Add(trajs);
+                }
+            }
+            else
+            {
+                InitialStateSeries = null;
+            }
+
+            if (DenseTraceeInfo.HasRegCoefs)
+            {
+                var c00 = OxyColors.DarkCyan;
+                var c10 = OxyColors.LightCyan;
+                var c01 = OxyColors.DarkGreen;
+                var c11 = OxyColors.LightGreen;
+
+                RegCoefSeries = new TrajectorySeries[DenseTraceeInfo.Size * DenseTraceeInfo.Size];
+                var colours = Colours2D(DenseTraceeInfo.Size, c00, c10, c01, c11);
+                for (int i = 0; i < DenseTraceeInfo.Size * DenseTraceeInfo.Size; i++)
+                {
+                    var trajs = new TrajectorySeries(DenseTraceeInfo.Samples.Where(s => s.DevelopmentalTransformationMatrix != null).Select(s => new DataPoint(s.Time, s.DevelopmentalTransformationMatrix[i])).ToList(), TrajectoryDownsampleMode, Math.Max((int)PlotView.ClientArea.Width / 2 + 2, 10));
+                    trajs.Color = colours[i];
+                    trajs.XAxisKey = "x";
+                    trajs.YAxisKey = "rcs";
+                    trajs.Tag = "rcs" + i;
+                    trajs.CanTrackerInterpolatePoints = false;
+                    RegCoefSeries[i] = trajs;
+                    PlotModel.Series.Add(trajs);
+                }
+            }
+            else
+            {
+                RegCoefSeries = null;
+            }
+
+            PlotModel.InvalidatePlot(true);
+            RefreshSeriesVisibilty();
+        }
+
+        public static OxyColor[] Colours1D(int n, OxyColor c0, OxyColor c1)
+        {
+            return M4M.TrajectoryPlotting.Colours1D(n, c0, c1);
+            // OxyColor[] res = new OxyColor[n];
+
+            // for (int i = 0; i < n; i++)
+            // {
+            //     var z = (double)i / (n - 1);
+            //     res[i] = OxyColor.Interpolate(c0, c1, z);
+            // }
+
+            // return res;
+        }
+
+        public static OxyColor[] Colours2D(int n, OxyColor c00, OxyColor c10, OxyColor c01, OxyColor c11)
+        {
+            return M4M.TrajectoryPlotting.Colours2D(n, c00, c10, c01, c11);
+            // OxyColor[] res = new OxyColor[n * n];
+
+            // for (int i = 0; i < n; i++)
+            // {
+            //     for (int j = 0; j < n; j++)
+            //     {
+            //         var iz = (double)i / (n - 1);
+            //         var jz = (double)j / (n - 1);
+            //         res[i * n + j] = OxyColor.Interpolate(OxyColor.Interpolate(c00, c10, iz), OxyColor.Interpolate(c01, c11, iz), jz);
+            //     }
+            // }
+
+            // return res;
+        }
+
+        private bool _showRegCoefs = true;
+        public bool ShowRegCoefs
+        {
+            get => _showRegCoefs;
+            set
+            {
+                _showRegCoefs = RcsCheck.Checked = value;
+                RefreshSeriesVisibilty();
+            }
+        }
+
+        private bool _showInitialStates = false;
+        public bool ShowInitialStates
+        {
+            get => _showInitialStates;
+            set
+            {
+                _showInitialStates = IsCheck.Checked = value;
+                RefreshSeriesVisibilty();
+            }
+        }
+
+        private TrajectoryDownsampleMode _trajectoryDownsampleMode = TrajectoryDownsampleMode.Mean;
+        public TrajectoryDownsampleMode TrajectoryDownsampleMode
+        {
+            get => _trajectoryDownsampleMode;
+            set
+            {
+                _trajectoryDownsampleMode = value;
+                this.MeanDownsampleCheck.Checked = _trajectoryDownsampleMode == TrajectoryDownsampleMode.Mean;
+
+                RefreshSeriesDownsampling();
+            }
+        }
+
+        private bool _trackerHidden = false;
+        public bool TrackerHidden
+        {
+            get => _trackerHidden;
+            set
+            {
+                if (_trackerHidden != value)
+                {
+                    _trackerHidden = value;
+                    // TODO: apply to series
+                    HideTrackerCheck.Checked = _trackerHidden;
+                }
+            }
+        }
+
+        private void RefreshSeriesVisibilty()
+        {
+            if (RegCoefSeries != null)
+            {
+                RegCoefAxis.IsAxisVisible = _showRegCoefs;
+                foreach (var s in RegCoefSeries)
+                    s.IsVisible = _showRegCoefs;
+            }
+
+            if (InitialStateSeries != null)
+            {
+                InitialStateAxis.IsAxisVisible = _showInitialStates;
+                foreach (var s in InitialStateSeries)
+                    s.IsVisible = _showInitialStates;
+            }
+
+            PlotView.Invalidate();
+        }
+
+        private void RefreshSeriesDownsampling()
+        {
+            if (RegCoefSeries != null)
+            {
+                RegCoefAxis.IsAxisVisible = _showRegCoefs;
+                foreach (var s in RegCoefSeries)
+                    s.DownsampleMode = _trajectoryDownsampleMode;
+            }
+
+            if (InitialStateSeries != null)
+            {
+                InitialStateAxis.IsAxisVisible = _showInitialStates;
+                foreach (var s in InitialStateSeries)
+                    s.DownsampleMode = _trajectoryDownsampleMode;
+            }
+
+            PlotView.Invalidate(true);
+        }
+        
+        private void RcsCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            ShowRegCoefs = RcsCheck.Checked;
+        }
+
+        private void IsCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            ShowInitialStates = IsCheck.Checked;
+        }
+
+        private void MeanDownsampleCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            var downsampleMode = this.MeanDownsampleCheck.Checked ? TrajectoryDownsampleMode.Mean : TrajectoryDownsampleMode.None;
+            TrajectoryDownsampleMode = downsampleMode;
+        }
+
+        private void HideTrackerCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            TrackerHidden = HideTrackerCheck.Checked;
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.resx b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DenseTraceeView.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/DoubleBufferedPanel.cs b/M4MCode/M4MDenseDev/M4MDenseDev/DoubleBufferedPanel.cs
new file mode 100644
index 0000000..6d8a6de
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/DoubleBufferedPanel.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseDev
+{
+    public class DoubleBufferedPanel : Panel
+    {
+        public DoubleBufferedPanel()
+        {
+            SetStyle(ControlStyles.Selectable, true);
+        }
+        
+        public new bool DoubleBuffered
+        {
+            get
+            {
+                return base.DoubleBuffered;
+            }
+            set
+            {
+                base.DoubleBuffered = value;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/IndividualBlockPlot.cs b/M4MCode/M4MDenseDev/M4MDenseDev/IndividualBlockPlot.cs
new file mode 100644
index 0000000..d206386
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/IndividualBlockPlot.cs
@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Drawing;
+using M4M;
+
+namespace M4MDenseDev
+{
+    public class IndividualBlockPlot : IIndividualPlot
+    {
+        public float FontSize { get; set; } = 10;
+        public float Gap { get; set; } = 0.5f;
+        public float BlockDim { get; set; }
+        public int GenomeSize { get; private set; } = -1;
+        public Rectangle Bounds { get; set; }
+        public IColours InitialStateColours { get; set; } = LerpColours.WHot(-1.0, 1.0);
+        public IColours DtmColours { get; set; } = LerpColours.WHot(-5.0, 5.0);
+        public IColours PhenotypeColours { get; set; } = LerpColours.WHot(-1.0, 1.0);
+        public DeltaResultMode DeltaResultMode { get; set; } = DeltaResultMode.Beneficial;
+
+        private PointF GTopLeft;
+        private PointF BTopLeft;
+        private PointF PTopLeft;
+
+        public void PerformLayout(Rectangle b, int genomeSize)
+        {
+            Bounds = b;
+            GenomeSize = genomeSize;
+
+            float padLeft = 0.0f;
+            float padTop = FontSize;
+            float PadRight = 15f;
+            float padBottom = 0.0f;
+
+            // square blocks
+            float blocksX = genomeSize + 2 + Gap * 2;
+            float blocksY = genomeSize;
+
+            float dim = Math.Min((b.Width - padLeft - PadRight) / blocksX, (b.Height - padTop - padBottom) / blocksY);
+            BlockDim = dim;
+
+            // position
+            var topLeft = new PointF(b.X + padLeft + (b.Width - padLeft - PadRight - (blocksX * dim)) / 2, b.Y + padTop + (b.Height - padTop - padBottom - (blocksY * dim)) / 2);
+            
+            GTopLeft = new PointF(topLeft.X, topLeft.Y);
+            BTopLeft = new PointF(topLeft.X + (1.0f + Gap) * BlockDim, topLeft.Y);
+            PTopLeft = new PointF(topLeft.X + (GenomeSize + 1.0f + Gap * 2f) * BlockDim, topLeft.Y);
+        }
+        
+        public RectangleF InitialStateBlock(int i)
+        {
+            return new RectangleF(
+                GTopLeft.X,
+                GTopLeft.Y + i * BlockDim,
+                BlockDim,
+                BlockDim);
+        }
+
+        public RectangleF DtmBlock(int i, int j)
+        {
+            return new RectangleF(
+                BTopLeft.X + j * BlockDim,
+                BTopLeft.Y + i * BlockDim,
+                BlockDim,
+                BlockDim);
+        }
+
+        public RectangleF PhenotypeBlock(int i)
+        {
+            return new RectangleF(
+                PTopLeft.X,
+                PTopLeft.Y + i * BlockDim,
+                BlockDim,
+                BlockDim);
+        }
+
+        public void Draw(Graphics g, Rectangle b, M4M.DenseIndividual individual, DeltaTest optionalDeltaTest = null)
+        {
+            PerformLayout(b, individual.Genome.Size);
+            Draw(g, individual, optionalDeltaTest);
+        }
+
+        public void Draw(Graphics g, M4M.DenseIndividual individual, DeltaTest optionalDeltaTest = null)
+        {
+            if (individual.Genome.Size != GenomeSize)
+                throw new ArgumentException("Individual is not of the correct size; perform layout first, or use a different overload for Draw", nameof(individual));
+
+            using (var brushCache = new DictionaryCache<Color, Brush>(c => new SolidBrush(c)))
+            {
+                // draw headings
+                using (var font = CreateFont())
+                {
+                    int N = individual.Genome.Size;
+                    g.DrawString("G", font, Brushes.Black, new RectangleF(GTopLeft.X, GTopLeft.Y - FontSize, BlockDim, FontSize));
+                    g.DrawString("B", font, Brushes.Black, new RectangleF(BTopLeft.X, PTopLeft.Y - FontSize, BlockDim, FontSize));
+                    g.DrawString("P", font, Brushes.Black, new RectangleF(PTopLeft.X, BTopLeft.Y - FontSize, BlockDim, FontSize));
+
+                    // draw blocks
+                    for (int i = 0; i < GenomeSize; i++)
+                    {
+                        g.FillRectangle(brushCache.Grab(InitialStateColours.GetColour(individual.Genome.InitialState[i])), InitialStateBlock(i));
+
+                        for (int j = 0; j < GenomeSize; j++)
+                        {
+                            var bBlock = DtmBlock(i, j);
+                            g.FillRectangle(brushCache.Grab(DtmColours.GetColour(individual.Genome.TransMat[i, j])), bBlock);
+
+                            if (optionalDeltaTest != null)
+                            {
+                                var deltaResult = DeltaResultMode == DeltaResultMode.Beneficial
+                                    ? optionalDeltaTest.GetBeneficial(i, j)
+                                    : optionalDeltaTest.GetBest(i, j);
+                                string deltaStr = deltaResult.ToSymbol();
+
+                                g.DrawString(deltaStr, font, Brushes.Purple, bBlock.Left, bBlock.Top);
+                            }
+                        }
+
+                        g.FillRectangle(brushCache.Grab(PhenotypeColours.GetColour(individual.Phenotype[i])), PhenotypeBlock(i));
+                    }
+
+                    // draw colour axis
+                    float caTop = 5;
+                    float caBottom = Bounds.Y + Bounds.Height - 5;
+                    float caLeft = Bounds.X + Bounds.Width - 5;
+
+                    float min = -6.0f;
+                    float max = 6.0f;
+                    int c = 200;
+                    for (int di = 0; di <= c; di++)
+                    {
+                        float dl = (float)di / c;
+                        float d = max + dl * (min - max);
+                        g.FillRectangle(brushCache.Grab(DtmColours.GetColour(d)), new RectangleF(caLeft, caTop + (caBottom - caTop) * dl, 5, (float)(caBottom - caTop) / c));
+                    }
+                }
+            }
+        }
+
+        private Font CreateFont()
+        {
+            return new Font(FontFamily.GenericMonospace, FontSize, FontStyle.Regular, GraphicsUnit.Pixel);
+        }
+
+        public ContextElementInfo HitTest(PointF p)
+        {
+            if (GenomeSize <= 0)
+                return ContextElementInfo.None;
+
+            // naive & inefficient implementation for now
+            for (int i = 0; i < GenomeSize; i++)
+            {
+                if (InitialStateBlock(i).Contains(p))
+                    return new ContextElementInfo(ContextElement.InitialState, i, -1);
+
+                for (int j = 0; j < GenomeSize; j++)
+                {
+                    if (DtmBlock(i, j).Contains(p))
+                        return new ContextElementInfo(ContextElement.DevelopmentalTransformationMatrix, i, j);
+                }
+                
+                if (PhenotypeBlock(i).Contains(p))
+                    return new ContextElementInfo(ContextElement.Phenotype, i, -1);
+            }
+
+            return ContextElementInfo.None;
+        }
+    }
+
+    public interface IColours
+    {
+        System.Drawing.Color GetColour(double value);
+    }
+
+    public delegate TOut Creator<TIn, TOut>(TIn input);
+
+    public class DictionaryCache<TIn, TOut> : IDisposable where TOut : IDisposable
+    {
+        private Dictionary<TIn, TOut> Table = new Dictionary<TIn, TOut>();
+        public Creator<TIn, TOut> Creator { get; }
+
+        public DictionaryCache(Creator<TIn, TOut> creator)
+        {
+            Creator = creator;
+        }
+
+        public TOut Grab(TIn input)
+        {
+            if (Table.TryGetValue(input, out var b))
+                return b;
+            else
+            {
+                return Table[input] = Creator(input);
+            }
+        }
+
+        public void Dispose()
+        {
+            if (Table == null)
+                return;
+
+            foreach (var b in Table.Values)
+            {
+                b.Dispose();
+            }
+
+            Table = null;
+        }
+    }
+
+    public class LerpColours : IColours
+    {
+        public static LerpColours WHot(double min, double max) => new LerpColours(min, max, Color.Black, Color.White, Color.Blue, Color.Red);
+        public static LerpColours BHot(double min, double max) => new LerpColours(min, max, Color.White, Color.Black, Color.Blue, Color.Red);
+
+        public LerpColours(double min, double max, Color minColour, Color maxColour, Color underMinColour, Color overMaxColour)
+        {
+            if (max <= min)
+                throw new ArgumentException("max must be strictly greater than min");
+
+            Min = min;
+            Max = max;
+            MinColour = minColour;
+            MaxColour = maxColour;
+            UnderMinColour = underMinColour;
+            OverMaxColour = overMaxColour;
+        }
+
+        public double Min { get; }
+        public double Max { get; }
+        public Color MinColour { get; }
+        public Color MaxColour { get; }
+        public Color UnderMinColour { get; }
+        public Color OverMaxColour { get; }
+
+        public Color GetColour(double value)
+        {
+            if (value > Max)
+                return OverMaxColour;
+            if (value < Min)
+                return UnderMinColour;
+
+            double x = (value - Min) / (Max - Min);
+            
+            return Color.FromArgb(LerpInt(MinColour.R, MaxColour.R, x), LerpInt(MinColour.G, MaxColour.G, x), LerpInt(MinColour.B, MaxColour.B, x));
+        }
+
+        private int LerpInt(int a, int b, double f)
+        {
+            return (int)(a * (1-f) + b * f);
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/IndividualNetworkPlot.cs b/M4MCode/M4MDenseDev/M4MDenseDev/IndividualNetworkPlot.cs
new file mode 100644
index 0000000..7d67d7e
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/IndividualNetworkPlot.cs
@@ -0,0 +1,374 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Drawing;
+using M4M;
+
+namespace M4MDenseDev
+{
+    public struct NodeArrangement
+    {
+        public NodeArrangement(PointF position, PointF focus) : this()
+        {
+            Position = position;
+            Focus = focus;
+        }
+
+        public PointF Position { get; }
+        public PointF Focus { get; }
+    }
+
+    public interface INodeArranger
+    {
+        NodeArrangement[] Arrange(int genomeSize, float arrowOutset, float arrowPadding, RectangleF b, out float nodeRadius);
+    }
+
+    public class CircleArranger : INodeArranger
+    {
+        public float ZeroAngle { get; set; } = 0.0f;
+
+        public NodeArrangement[] Arrange(int genomeSize, float arrowOutset, float arrowPadding, RectangleF b, out float nodeRadius)
+        {
+            var centre = new PointF(b.X + b.Width / 2f, b.Y + b.Height / 2f);
+            var outerRadius = Math.Min(b.Width, b.Height) / 2f;
+            nodeRadius = outerRadius / genomeSize;
+            outerRadius -= nodeRadius * arrowOutset + arrowPadding; // so we don't go over the edges
+            
+            NodeArrangement node(int i)
+            {
+                float radians = (float)(((float)i / genomeSize) * Math.PI * 2) + ZeroAngle;
+                float ox = (float)Math.Sin(radians) * outerRadius;
+                float oy = -(float)Math.Cos(radians) * outerRadius;
+                return new NodeArrangement(new PointF(centre.X + ox, centre.Y + oy), centre);
+            }
+
+            return Enumerable.Range(0, genomeSize).Select(node).ToArray();
+        }
+    }
+
+    public class MultiArranger : INodeArranger
+    {
+        public MultiArranger(INodeArranger arranger)
+        {
+            Arranger = arranger ?? throw new ArgumentNullException(nameof(arranger));
+        }
+
+        public INodeArranger Arranger { get; } = new CircleArranger();
+
+        public NodeArrangement[] Arrange(int genomeSize, float arrowOutset, float arrowPadding, RectangleF b, out float nodeRadius)
+        {
+            var sqrt2 = (int)Math.Sqrt(Math.Sqrt(genomeSize));
+            if (sqrt2 * sqrt2 * sqrt2 * sqrt2 == genomeSize)
+            {
+                nodeRadius = -1; // hopefully this will tell us if we mess up
+                
+                var tall = sqrt2;
+                var wide = sqrt2;
+                int subSize = genomeSize / (tall * wide);
+
+                List<NodeArrangement> res = new List<NodeArrangement>();
+
+                for (int i = 0; i < tall; i++)
+                {
+                    for (int j = 0; j < wide; j++)
+                    {
+                        var subb = new RectangleF(b.X + b.Width / wide * j, b.Y + b.Height / tall * i, b.Width / wide, b.Height / tall);
+                        res.AddRange(Arranger.Arrange(subSize, arrowOutset, arrowPadding, subb, out nodeRadius));
+                    }
+                }
+
+                return res.ToArray();
+            }
+            else
+            {
+                return Arranger.Arrange(genomeSize, arrowOutset, arrowPadding, b, out nodeRadius);
+            }
+        }
+    }
+
+    public class IndividualNetworkPlot : IIndividualPlot
+    {
+        public float FontSize { get; set; } = 10;
+        public double DeclutterThresholdFactor { get; set; } = -1;
+
+        public int GenomeSize { get; private set; } = -1;
+        public Rectangle Bounds { get; set; }
+        public IColours InitialStateColours { get; set; } = LerpColours.WHot(-1.0, 1.0);
+        public IColours DtmColours { get; set; } = LerpColours.WHot(-5.0, 5.0);
+        public IColours PhenotypeColours { get; set; } = LerpColours.WHot(-1.0, 1.0);
+        public DeltaResultMode DeltaResultMode { get; set; } = DeltaResultMode.Beneficial;
+
+        private PointF Centre;
+        //private float OuterRadius;
+        private float NodeRadius;
+
+        public INodeArranger NodeArranger { get; set; } = new MultiArranger(new CircleArranger());
+        private NodeArrangement[] NodeArrangements { get; set; }
+
+        public void PerformLayout(Rectangle b, int genomeSize)
+        {
+            Bounds = b;
+            GenomeSize = genomeSize;
+
+            float padLeft = 0.0f;
+            float padTop = 0.0f;
+            float PadRight = 15f;
+            float padBottom = 0.0f;
+
+            // need to fit n circles in a big space
+            float width = b.Width - padLeft - PadRight;
+            float height = b.Height - padTop - padBottom;
+
+            Centre = new PointF(b.X + padLeft + width / 2f, b.Y + padTop + height / 2f);
+
+            NodeArrangements = NodeArranger.Arrange(GenomeSize, ArrowOutset, ArrowSelfDim + ArrowForkLength, new RectangleF(b.X + padLeft, b.Y + padTop, width, height), out NodeRadius);
+        }
+
+        public PointF NodePosition(int i)
+        {
+            return NodeArrangements[i].Position;
+        }
+
+        public PointF NodeFocus(int i)
+        {
+            return NodeArrangements[i].Focus;
+        }
+        
+        private const float ArrowSplit = 3;
+        private const float ArrowForkLength = 4;
+        private const float ArrowForkTightness = 1;
+        private const float ArrowSpread = 6;
+        private const float ArrowOutset = 1.2f;
+        private const float ArrowSelfDim = 10f;
+        public PointF[] ArrowBetween(int i, int j)
+        {
+            if (i == j)
+            {
+                var ci = NodePosition(i);
+                var fi = NodeFocus(i);
+                return ArrowSelf(ci, fi);
+            }
+            else
+            {
+                var ci = NodePosition(i);
+                var cj = NodePosition(j);
+                return ArrowBetween(ci, cj);
+            }
+        }
+
+        public PointF[] ArrowSelf(PointF ci, PointF fi)
+        {
+            ci.Fork(fi, -NodeRadius * ArrowOutset, 5f, out var ka1, out var kb1);
+            ci.Fork(fi, -NodeRadius * ArrowOutset - ArrowSelfDim, 5f, out var ka2, out var kb2);
+
+            return new[] { ka1, ka2, kb2, kb1 };
+        }
+
+        public PointF[] ArrowBetween(PointF ci, PointF cj)
+        {
+            var dist = ci.Dist(cj);
+
+            var mid = ci.Mid(cj);
+            var dx = ci.X - cj.X;
+            var dy = ci.Y - cj.Y;
+            
+            ci = ci.Offset((-dx / dist) * NodeRadius * 1.2f, (-dy / dist) * NodeRadius * ArrowOutset);
+            cj = cj.Offset((dx / dist) * NodeRadius * 1.2f, (dy / dist) * NodeRadius * ArrowOutset);
+            
+            mid = mid.Offset((dy / dist) * ArrowSpread, (-dx / dist) * ArrowSpread);
+            ci = ci.Offset((dy / dist) * ArrowSplit, (-dx / dist) * ArrowSplit);
+            cj = cj.Offset((dy / dist) * ArrowSplit, (-dx / dist) * ArrowSplit);
+
+            return new PointF[] { ci, mid, cj };
+        }
+
+        public RectangleF CircleAround(PointF centre, float radius)
+        {
+            return new RectangleF(
+                centre.X - radius,
+                centre.Y - radius,
+                radius * 2,
+                radius * 2);
+        }
+
+        public RectangleF EllipseAround(PointF centre, float halfWidth, float halfHeight)
+        {
+            return new RectangleF(
+                centre.X - halfWidth,
+                centre.Y - halfHeight,
+                halfWidth * 2,
+                halfHeight * 2);
+        }
+
+        public void Draw(Graphics g, Rectangle b, M4M.DenseIndividual individual, DeltaTest optionalDeltaTest = null)
+        {
+            PerformLayout(b, individual.Genome.Size);
+            Draw(g, individual, optionalDeltaTest);
+        }
+
+        public void Draw(Graphics g, M4M.DenseIndividual individual, DeltaTest optionalDeltaTest = null)
+        {
+            if (individual.Genome.Size != GenomeSize)
+                throw new ArgumentException("Individual is not of the correct size; perform layout first, or use a different overload for Draw", nameof(individual));
+            
+            using (var brushCache = new DictionaryCache<Color, Brush>(c => new SolidBrush(c)))
+            using (var penCache = new DictionaryCache<Color, Pen>(c => new Pen(c, 1)))
+            using (var font = CreateFont())
+            {
+                // draw nodes
+                for (int i = 0; i < GenomeSize; i++)
+                {
+                    var ci = NodePosition(i);
+                    var rect = CircleAround(ci, NodeRadius);
+                    g.DrawEllipse(Pens.Black, rect);
+                    var gBrush = brushCache.Grab(InitialStateColours.GetColour(individual.Genome.InitialState[i]));
+                    var pBrush = brushCache.Grab(PhenotypeColours.GetColour(individual.Phenotype[i]));
+                    g.FillPie(gBrush, rect.X, rect.Y, rect.Width, rect.Height, 90, 180);
+                    g.FillPie(pBrush, rect.X, rect.Y, rect.Width, rect.Height, 270, 180);
+
+                    var idStr = "" + i;
+                    var idSize = g.MeasureString(idStr, font);
+                    var idRect = EllipseAround(ci, idSize.Width / 2, idSize.Height / 2);
+                    g.DrawString(idStr, font, Brushes.Purple, idRect);
+                }
+
+                var declutterThreshold = DeclutterThresholdFactor < 0
+                    ? -1
+                    : M4M.DtmClassification.ComputeAutoThreshold(individual.Genome.TransMat, DeclutterThresholdFactor);
+
+                // draw connections
+                for (int i = 0; i < GenomeSize; i++)
+                {
+                    for (int j = 0; j < GenomeSize; j++)
+                    {
+                        var arrowPoints = ArrowBetween(j, i);
+                        var weight = individual.Genome.TransMat[i, j];
+
+                        if (Math.Abs(weight) < declutterThreshold)
+                            continue;
+                        
+                        var bPen = penCache.Grab(DtmColours.GetColour(weight));
+                        g.DrawCurve(bPen, arrowPoints);
+
+                        int li = arrowPoints.Length - 1;
+                        var l = arrowPoints[li];
+                        var m = arrowPoints[li - 1];
+                        
+                        l.Fork(m, ArrowForkLength, ArrowForkTightness, out var ka, out var kb);
+                        g.DrawLine(bPen, ka, l);
+                        g.DrawLine(bPen, kb, l);
+                        
+                        if (optionalDeltaTest != null)
+                        {
+                            var deltaResult = DeltaResultMode == DeltaResultMode.Beneficial
+                                ? optionalDeltaTest.GetBeneficial(i, j)
+                                : optionalDeltaTest.GetBest(i, j);
+                            string deltaStr = deltaResult.ToSymbol();
+
+                            var deltaSize = g.MeasureString(deltaStr, font);
+                            var deltaRect = EllipseAround(m, deltaSize.Width / 2, deltaSize.Height / 2);
+                            g.DrawString(deltaStr, font, Brushes.Purple, deltaRect);
+                        }
+                    }
+                }
+
+                // draw colour axis
+                float caTop = 5;
+                float caBottom = Bounds.Y + Bounds.Height - 5;
+                float caLeft = Bounds.X + Bounds.Width - 5;
+
+                float min = -6.0f;
+                float max = 6.0f;
+                int c = 200;
+                for (int di = 0; di <= c; di++)
+                {
+                    float dl = (float)di / c;
+                    float d = max + dl * (min - max);
+                    g.FillRectangle(brushCache.Grab(DtmColours.GetColour(d)), new RectangleF(caLeft, caTop + (caBottom - caTop) * dl, 5, (float)(caBottom - caTop) / c));
+                }
+            }
+        }
+
+        private Font CreateFont()
+        {
+            return new Font(FontFamily.GenericMonospace, FontSize, FontStyle.Regular, GraphicsUnit.Pixel);
+        }
+
+        public ContextElementInfo HitTest(PointF p)
+        {
+            if (GenomeSize <= 0)
+                return ContextElementInfo.None;
+
+            // naive & inefficient implementation for now
+            for (int i = 0; i < GenomeSize; i++)
+            {
+                var centre = NodePosition(i);
+
+                if (centre.Dist(p) < NodeRadius)
+                {
+                    if (p.X < centre.X)
+                        return new ContextElementInfo(ContextElement.InitialState, i, -1);
+                    else
+                        return new ContextElementInfo(ContextElement.Phenotype, i, -1);
+                }
+
+                int bestArrow = -1;
+                float bestArrowDist = float.MaxValue;
+                for (int j = 0; j < GenomeSize; j++)
+                {
+                    var arrow = ArrowBetween(j, i);
+                    var dist = arrow.Last().Dist(p);
+                    if (dist < bestArrowDist)
+                    {
+                        bestArrowDist = dist;
+                        bestArrow = j;
+                    }
+                }
+
+                if (bestArrowDist < ArrowSplit + ArrowForkLength)
+                    return new ContextElementInfo(ContextElement.DevelopmentalTransformationMatrix, i, bestArrow);
+            }
+
+            return ContextElementInfo.None;
+        }
+    }
+
+    public static class PointExtensions
+    {
+        public static float DistSqr(this PointF l, PointF r)
+        {
+            float dx = l.X - r.X;
+            float dy = l.Y - r.Y;
+            return dx * dx + dy * dy;
+        }
+
+        public static float Dist(this PointF l, PointF r)
+        {
+            float dx = l.X - r.X;
+            float dy = l.Y - r.Y;
+            return (float)Math.Sqrt(dx * dx + dy * dy);
+        }
+
+        public static PointF Mid(this PointF l, PointF r)
+        {
+            return new PointF((l.X + r.X) / 2f, (l.Y + r.Y) / 2f);
+        }
+
+        public static PointF Offset(this PointF p, float dx, float dy)
+        {
+            return new PointF(p.X + dx, p.Y + dy);
+        }
+
+        public static void Fork(this PointF l, PointF m, float length, float tighness, out PointF ka, out PointF kb)
+        {
+            var dx = l.X - m.X;
+            var dy = l.Y - m.Y;
+            var dist = m.Dist(l);
+
+            ka = l.Offset(((-dx - dy / tighness) / dist) * length, ((-dy + dx / tighness) / dist) * length);
+            kb = l.Offset(((-dx + dy / tighness) / dist) * length, ((-dy - dx / tighness) / dist) * length);
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/IndividualSpringyPlot.cs b/M4MCode/M4MDenseDev/M4MDenseDev/IndividualSpringyPlot.cs
new file mode 100644
index 0000000..0d959b0
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/IndividualSpringyPlot.cs
@@ -0,0 +1,542 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Drawing;
+using M4M;
+using MathNet.Numerics.LinearAlgebra;
+
+namespace M4MDenseDev
+{
+    public struct Vec2
+    {
+        public Vec2(double x, double y)
+        {
+            X = x;
+            Y = y;
+        }
+
+        public double X { get; }
+        public double Y { get; }
+
+        public static Vec2 operator +(Vec2 l, Vec2 r)
+        {
+            return new Vec2(l.X + r.X, l.Y + r.Y);
+        }
+
+        public static Vec2 operator -(Vec2 l, Vec2 r)
+        {
+            return new Vec2(l.X - r.X, l.Y - r.Y);
+        }
+
+        public static Vec2 operator *(Vec2 v, double c)
+        {
+            return new Vec2(v.X * c, v.Y * c);
+        }
+
+        public static Vec2 operator /(Vec2 v, double c)
+        {
+            return new Vec2(v.X / c, v.Y / c);
+        }
+
+        public double LengthSqr => X * X + Y * Y;
+        public double Length => Math.Sqrt(LengthSqr);
+        public Vec2 Normalise() => this / Length;
+        public Vec2 Rot90() => new Vec2(-Y, X);
+
+        public Vec2 Rotate(double radians)
+        {
+            var c = Math.Cos(radians);
+            var s = Math.Sin(radians);
+            return new Vec2(c * X - s * Y, s * X + c * Y);
+        }
+
+        public static Vec2 Polar(double radians, double length)
+        {
+            return new Vec2(Math.Cos(radians), Math.Sin(radians)) * length;
+        }
+
+        public double Dot(Vec2 other)
+        {
+            return X * other.X + Y * other.Y;
+        }
+    }
+
+    public class Connection
+    {
+        public Connection(SpringyNode other, double strength)
+        {
+            Other = other ?? throw new ArgumentNullException(nameof(other));
+            Strength = strength;
+        }
+
+        public SpringyNode Other { get; }
+        public double Strength { get; set; }
+    }
+
+    public class RotatrySubNode
+    {
+        public RotatrySubNode(double weight, double distance, double angle)
+        {
+            Weight = weight;
+            Distance = distance;
+            Angle = angle;
+            AngularVelocity = 0;
+        }
+
+        public double Weight { get; set; }
+        public double Distance { get; set; }
+        public double AngularVelocity { get; set; }
+        public double Angle { get; set; }
+
+        public Vec2 Offset => Vec2.Polar(Angle, Distance);
+        public Vec2 DirectionOfTravel => Vec2.Polar(Angle + Math.PI / 2, 1);
+    }
+
+    public class SpringyNode
+    {
+        public SpringyNode(int id, double weight, double selfConnectionNodeOffset, double selfConnectionNodeWeight)
+        {
+            Id = id;
+            Weight = weight;
+
+            Connections = new List<Connection>();
+            SelfConnection = new RotatrySubNode(selfConnectionNodeWeight, selfConnectionNodeOffset, 0);
+        }
+
+        public List<Connection> Connections { get; }
+        public int Id { get; }
+        public double Weight { get; set; }
+        public Vec2 Location { get; set; }
+        public Vec2 Velocity { get; set; }
+        public double InitialExpression { get; set; }
+        public double PhenotypicExpression { get; set; }
+
+        public RotatrySubNode SelfConnection { get; set; }
+    }
+
+    public class Springy
+    {
+        public bool FancyArrows { get; set; } = true;
+
+        public float NodeRadius { get; set; } = 15;
+        public float ArrowSize { get; set; } = 5;
+        public IColours InitialStateColours { get; set; } = LerpColours.WHot(-1.0, 1.0);
+        public IColours DtmColours { get; set; } = LerpColours.WHot(-5.0, 5.0);
+        public IColours PhenotypeColours { get; set; } = LerpColours.WHot(-1.0, 1.0);
+
+        public List<SpringyNode> Nodes { get; } = new List<SpringyNode>();
+        public int Size => Nodes.Count;
+
+        public double RepulsionStrength { get; set; } = 800;
+        public double AttrationStrength { get; set; } = 0.1;
+
+        public bool Springness { get; set; } = true;
+        public bool Momentum { get; set; } = true;
+        public double Damping { get; set; } = 0.001;
+        public double RsnDamping { get; set; } = 0.002;
+        public double CentralAttraction = 0.1;
+
+        public List<int> Highlights { get; set; } = new List<int>();
+        public Vector<double> Target { get; set; }
+        public bool ShowG { get; set; } = true;
+        public bool Shapes { get; set; } = true;
+
+        public Springy(int size)
+        {
+            for (int i = 0; i < size; i++)
+            {
+                Nodes.Add(new SpringyNode(i, 1, NodeRadius * 2, 1));
+            }
+
+            for (int i = 0; i < size; i++)
+            {
+                var n = Nodes[i];
+
+                for (int j = 0; j < size; j++)
+                {
+                    n.Connections.Add(new Connection(Nodes[j], 1));
+                }
+            }
+        }
+
+        public Springy(Matrix<double> transMat, Vector<double> g, Vector<double> p)
+            : this(g.Count)
+        {
+            Update(transMat, g, p);
+        }
+
+        public Springy(DenseIndividual individual)
+            : this(individual.Genome.Size)
+        {
+            Update(individual);
+        }
+
+        public void Update(DenseIndividual individual, double strengthRescale = 1.0)
+        {
+            Update(individual.Genome.TransMat, individual.Genome.InitialState, individual.Phenotype.Vector, strengthRescale);
+        }
+
+        public void Update(Matrix<double> transMat, Vector<double> g, Vector<double> p, double strengthRescale = 1.0)
+        {
+            foreach (var n in Nodes)
+            {
+                n.InitialExpression = g[n.Id];
+                n.PhenotypicExpression = p[n.Id];
+
+                foreach (var c in n.Connections)
+                {
+                    c.Strength = transMat[c.Other.Id, n.Id] * strengthRescale;
+                }
+            }
+        }
+
+        public void Step(double dt)
+        {
+            var force = new Vec2[Nodes.Count];
+            var rsnforce = new Vec2[Nodes.Count];
+            foreach (var n in Nodes)
+            {
+                // attraction to centre
+                force[n.Id] -= n.Location * CentralAttraction;
+
+                // attration to other nodes
+                foreach (var c in n.Connections)
+                {
+                    if (c.Other == n)
+                        continue;
+
+                    var mag = AttrationStrength * Math.Abs(c.Strength);
+                    var dir = Springness
+                        ? (c.Other.Location - n.Location)
+                        : (c.Other.Location - n.Location).Normalise();
+                    force[n.Id] += dir * mag;
+                    force[c.Other.Id] -= dir * mag;
+                }
+
+                // repulsion from other nodes
+                foreach (var o in Nodes)
+                {
+                    if (o == n)
+                        continue;
+
+                    var dir = o.Location - n.Location;
+                    var l2 = dir.LengthSqr / RepulsionStrength;
+                    force[n.Id] -= dir / l2;
+
+                    // rsn
+                    var rsndir = o.Location - (n.Location + n.SelfConnection.Offset);
+                    l2 = rsndir.LengthSqr / RepulsionStrength;
+                    rsnforce[n.Id] -= dir / l2;
+                }
+            }
+
+            Random rnd = null;
+            foreach (var n in Nodes)
+            {
+                if (double.IsNaN(force[n.Id].X) || double.IsInfinity(force[n.Id].X) || double.IsNaN(n.Location.X) || double.IsInfinity(n.Location.X)
+                    || double.IsNaN(force[n.Id].Y) || double.IsInfinity(force[n.Id].Y) || double.IsNaN(n.Location.Y) || double.IsInfinity(n.Location.Y))
+                {
+                    rnd = rnd ?? new Random();
+                    n.Location = new Vec2(rnd.NextDouble() - 0.5, rnd.NextDouble() - 0.5) * 100;
+                    n.Velocity = new Vec2(0, 0);
+                    n.SelfConnection.AngularVelocity = 0;
+                    n.SelfConnection.Angle = rnd.NextDouble() * Math.PI * 2;
+                    continue;
+                }
+
+                if (double.IsNaN(n.SelfConnection.AngularVelocity))
+                {
+                    n.SelfConnection.AngularVelocity = 0;
+                    continue;
+                }
+
+                if (Momentum)
+                {
+                    n.Velocity = n.Velocity * (1 - Damping) + force[n.Id] / n.Weight * dt;
+                    n.Location += n.Velocity * dt;
+                }
+                else
+                {
+                    n.Location += force[n.Id] / n.Weight * dt;
+                }
+
+                // rsn
+                var angularAcc = (rsnforce[n.Id] - force[n.Id]).Dot(n.SelfConnection.DirectionOfTravel) / n.SelfConnection.Weight / n.SelfConnection.Distance;
+                if (Momentum)
+                {
+                    n.SelfConnection.AngularVelocity = (1 - RsnDamping) * n.SelfConnection.AngularVelocity + angularAcc * dt;
+                    n.SelfConnection.Angle += n.SelfConnection.AngularVelocity * dt;
+                }
+                else
+                {
+                    n.SelfConnection.Angle += angularAcc * dt;
+                }
+            }
+        }
+
+        public void Draw(Graphics g, Point origin, double scale)
+        {
+            PointF map(Vec2 loc) => new PointF((float)(loc.X * scale + origin.X), (float)(loc.Y * scale + origin.Y));
+            int alpha(double x) => Math.Max(0, Math.Min(255, (int)(Math.Abs(x) * 255)));
+
+            var ah = new ArrowHelper() { NodeRadius = NodeRadius };
+
+            // highlights
+            var highlighter = Brushes.Yellow;
+            foreach (var hn in Highlights)
+            {
+                var n = Nodes[hn];
+                var centre = map(n.Location);
+                var rect = new RectangleF(centre.X - NodeRadius * 1.5f, centre.Y - NodeRadius * 1.5f, NodeRadius * 3, NodeRadius * 3);
+                g.FillEllipse(highlighter, rect);
+            }
+
+            // arrows
+            using (var penCacheA = new DictionaryCache<double, Pen>(c => new Pen(Color.FromArgb(Math.Abs(c) < 1 ? 0 : 255, (c < 0 ? Color.Red : Color.Blue)), (float)Math.Floor(c))))
+            using (var penCacheB = new DictionaryCache<double, Pen>(c => new Pen(Color.FromArgb(alpha(Math.Abs(c) - Math.Truncate(Math.Abs(c))), (c < 0 ? Color.Red : Color.Blue)), (float)Math.Ceiling(c))))
+                foreach (var n in Nodes)
+                {
+                    foreach (var c in n.Connections)
+                    {
+                        var penA = penCacheA.Grab(c.Strength);
+                        var penB = penCacheB.Grab(c.Strength);
+
+                        if (FancyArrows)
+                        {
+                            ah.DrawArrow(g, map(n.Location), map(c.Other.Location), penA, n.SelfConnection.Angle);
+                            ah.DrawArrow(g, map(n.Location), map(c.Other.Location), penB, n.SelfConnection.Angle);
+                        }
+                        else
+                        {
+                            if (c.Other == n)
+                                continue;
+
+                            var dir = (c.Other.Location - n.Location).Normalise();
+                            var s = n.Location + dir * NodeRadius;
+                            var e = c.Other.Location - dir * NodeRadius;
+                            var avec = dir * ArrowSize;
+                            var a0 = e - avec - avec.Rot90();
+                            var a1 = e - avec + avec.Rot90();
+
+                            g.DrawLine(penA, map(s), map(e));
+                            g.DrawLine(penB, map(s), map(e));
+                            g.DrawLines(penA, new PointF[] { map(a0), map(e), map(a1) });
+                            g.DrawLines(penB, new PointF[] { map(a0), map(e), map(a1) });
+                        }
+                    }
+                }
+
+            // nodes
+            using (var npen = new Pen(Color.Black, 2))
+            using (var brushCache = new DictionaryCache<Color, Brush>(c => new SolidBrush(c)))
+            using (var penCache = new DictionaryCache<Color, Pen>(c => new Pen(c, 2)))
+                foreach (var n in Nodes)
+                {
+                    var centre = map(n.Location);
+
+                    var rect = new RectangleF(centre.X - NodeRadius, centre.Y - NodeRadius, NodeRadius * 2, NodeRadius * 2);
+                    
+                    if (Shapes)
+                    {
+                        if (Target != null)
+                        {
+                            var tup = Target[n.Id] > 0;
+                            var ts = Math.Abs(Target[n.Id]);
+
+                            var pup = n.PhenotypicExpression > 0;
+                            var ps = Math.Abs(n.PhenotypicExpression);
+
+                            var pen = tup == pup
+                                ? penCache.Grab(Color.Green)
+                                : penCache.Grab(Color.Red);
+
+                            DrawPolygon(g, centre, 3, NodeRadius * (float)ts, tup ? 0f : 0.5f, pen);
+                            DrawPolygon(g, centre, 3, NodeRadius * (float)ps, pup ? 0f : 0.5f, pen);
+                        }
+                        else
+                        {
+                            var pup = n.PhenotypicExpression > 0;
+                            var ps = Math.Abs(n.PhenotypicExpression) * NodeRadius;
+
+                            var pen = penCache.Grab(Color.Black);
+
+                            DrawPolygon(g, centre, 3, NodeRadius * (float)ps, pup ? 0f : 0.5f, pen);
+                        }
+                    }
+                    else
+                    {
+                        g.DrawEllipse(npen, rect);
+                        if (Target != null)
+                        {
+                            var tBrush = brushCache.Grab(InitialStateColours.GetColour(Target[n.Id]));
+                            g.FillEllipse(tBrush, rect.X, rect.Y, rect.Width, rect.Height);
+                            rect.Inflate(-NodeRadius / 4, -NodeRadius / 4);
+                        }
+
+                        var pBrush = brushCache.Grab(PhenotypeColours.GetColour(n.PhenotypicExpression));
+                        if (ShowG)
+                        {
+                            var gBrush = brushCache.Grab(InitialStateColours.GetColour(n.InitialExpression));
+                            g.FillPie(gBrush, rect.X, rect.Y, rect.Width, rect.Height, 90, 180);
+                            g.FillPie(pBrush, rect.X, rect.Y, rect.Width, rect.Height, 270, 180);
+                        }
+                        else
+                        {
+                            g.FillEllipse(pBrush, rect.X, rect.Y, rect.Width, rect.Height);
+                        }
+
+                        if (Target != null)
+                        {
+                            var tAntiPen = penCache.Grab(InitialStateColours.GetColour(-Math.Sign(Target[n.Id])));
+                            g.DrawEllipse(tAntiPen, rect);
+                        }
+                    }
+                }
+        }
+
+        private static void DrawPolygon(Graphics g, PointF loc, int n, float size, float phase, Pen pen)
+        {
+            if (n < 3)
+                throw new ArgumentOutOfRangeException(nameof(n), n, "n must be no less than 3");
+
+            var points = new PointF[n];
+            for (int i = 0; i < n; i++)
+            {
+                var t = Math.PI * 2.0 * (phase + (double)i / n);
+                var dx = Math.Sin(t) * size;
+                var dy = Math.Cos(t) * size;
+                points[i] = new PointF(loc.X + (float)dx, loc.Y + (float)dy);
+            }
+            g.DrawPolygon(pen, points);
+        }
+    }
+
+    public class ArrowHelper
+    {
+        public float ArrowSplit { get; set; }  = 3;
+        public float ArrowForkLength { get; set; }  = 4;
+        public float ArrowForkTightness { get; set; }  = 1;
+        public float ArrowSpread { get; set; }  = 6;
+        public float ArrowOutset { get; set; } = 1.2f;
+        public float ArrowSelfDim { get; set; } = 10f;
+        public float NodeRadius { get; set; } = 15f;
+
+        public void DrawArrow(Graphics g, PointF from, PointF to, Pen pen, double selfAngle)
+        {
+            var arrowPoints = Arrow(from, to, selfAngle);
+            g.DrawCurve(pen, arrowPoints);
+
+            int li = arrowPoints.Length - 1;
+            var l = arrowPoints[li];
+            var m = arrowPoints[li - 1];
+
+            l.Fork(m, ArrowForkLength, ArrowForkTightness, out var ka, out var kb);
+            g.DrawLine(pen, ka, l);
+            g.DrawLine(pen, kb, l);
+        }
+
+        public PointF[] Arrow(PointF ci, PointF cj, double selfAngle)
+        {
+            if (ci == cj)
+            {
+                return ArrowSelf(ci, new PointF(ci.X + NodeRadius * (float)Math.Cos(selfAngle), ci.Y + NodeRadius * (float)Math.Sin(selfAngle)));
+            }
+            else
+            {
+                return ArrowBetween(ci, cj);
+            }
+        }
+
+        public PointF[] ArrowSelf(PointF ci, PointF fi)
+        {
+            ci.Fork(fi, NodeRadius * ArrowOutset, 5f, out var ka1, out var kb1);
+            ci.Fork(fi, NodeRadius * ArrowOutset + ArrowSelfDim, 5f, out var ka2, out var kb2);
+
+            return new[] { ka1, ka2, kb2, kb1 };
+        }
+
+        public PointF[] ArrowBetween(PointF ci, PointF cj)
+        {
+            var dist = ci.Dist(cj);
+
+            var mid = ci.Mid(cj);
+            var dx = ci.X - cj.X;
+            var dy = ci.Y - cj.Y;
+
+            ci = ci.Offset((-dx / dist) * NodeRadius * 1.2f, (-dy / dist) * NodeRadius * ArrowOutset);
+            cj = cj.Offset((dx / dist) * NodeRadius * 1.2f, (dy / dist) * NodeRadius * ArrowOutset);
+
+            mid = mid.Offset((dy / dist) * ArrowSpread, (-dx / dist) * ArrowSpread);
+            ci = ci.Offset((dy / dist) * ArrowSplit, (-dx / dist) * ArrowSplit);
+            cj = cj.Offset((dy / dist) * ArrowSplit, (-dx / dist) * ArrowSplit);
+
+            return new PointF[] { ci, mid, cj };
+        }
+
+        public static void Fork(PointF l, PointF m, float length, float tighness, out PointF ka, out PointF kb)
+        {
+            var dx = l.X - m.X;
+            var dy = l.Y - m.Y;
+            var dist = m.Dist(l);
+
+            ka = l.Offset(((-dx - dy / tighness) / dist) * length, ((-dy + dx / tighness) / dist) * length);
+            kb = l.Offset(((-dx + dy / tighness) / dist) * length, ((-dy - dx / tighness) / dist) * length);
+        }
+    }
+
+    public class IndividualSpringyPlot : IIndividualPlot
+    {
+        public DeltaResultMode DeltaResultMode { get; set; }
+
+        public Springy Springy { get; set; }
+
+        void IIndividualPlot.Draw(Graphics g, Rectangle b, DenseIndividual individual, DeltaTest optionalDeltaTest)
+        {
+            UpdateStepAndDraw(g, b, individual, 500);
+        }
+
+        public void UpdateStepAndDraw(Graphics g, Rectangle b, DenseIndividual individual, int steps, double strengthRescale = 1.0, Action<Springy> configurator = null)
+        {
+            Update(individual, strengthRescale, configurator);
+            Step(steps);
+            Draw(g, b);
+        }
+
+        public void Update(DenseIndividual individual, double strengthRescale = 1.0, Action<Springy> configurator = null)
+        {
+            Update(individual.Genome.TransMat, individual.Genome.InitialState, individual.Phenotype.Vector, strengthRescale, configurator);
+        }
+
+        public void Update(Matrix<double> transMat, Vector<double> g, Vector<double> p, double strengthRescale = 1.0, Action<Springy> configurator = null)
+        {
+            if (Springy == null || g.Count != Springy.Size)
+            {
+                Springy = new Springy(transMat, g, p);
+                configurator?.Invoke(Springy); // well this is hideous
+            }
+
+            Springy.Update(transMat, g, p, strengthRescale);
+        }
+
+        public void Step(int steps)
+        {
+            for (int i = 0; i < steps; i++)
+            {
+                Springy.Step(0.001);
+            }
+        }
+
+        public void Draw(Graphics g, Rectangle b)
+        {
+            Springy.Draw(g, new Point(b.X + b.Width / 2, b.Y + b.Height / 2), 1);
+        }
+
+        public ContextElementInfo HitTest(PointF p)
+        {
+            // TODO: implement
+            return ContextElementInfo.None;
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/M4MDenseDev.csproj b/M4MCode/M4MDenseDev/M4MDenseDev/M4MDenseDev.csproj
new file mode 100644
index 0000000..8211bab
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/M4MDenseDev.csproj
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{11DA11E0-0D2C-4C35-B74E-BA8926D27B9D}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>M4MDenseDev</RootNamespace>
+    <AssemblyName>M4MDenseDev</AssemblyName>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="M4M.Model">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.Model.dll</HintPath>
+    </Reference>
+    <Reference Include="M4M.New">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.New.dll</HintPath>
+    </Reference>
+    <Reference Include="MathNet.Numerics, Version=4.9.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MathNet.Numerics.4.9.1\lib\net40\MathNet.Numerics.dll</HintPath>
+    </Reference>
+    <Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
+    <Reference Include="NetState">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot, Version=2.0.0.0, Culture=neutral, PublicKeyToken=638079a8f0bd61e9, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.Core.2.0.0\lib\net45\OxyPlot.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot.WindowsForms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=245eacd6b5d2d338, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.WindowsForms.2.0.0\lib\net45\OxyPlot.WindowsForms.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BlockDenseView.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="BlockDenseView.Designer.cs">
+      <DependentUpon>BlockDenseView.cs</DependentUpon>
+    </Compile>
+    <Compile Include="DeltaTestHelpers.cs" />
+    <Compile Include="DenseContext.cs" />
+    <Compile Include="DenseTraceeInfo.cs" />
+    <Compile Include="DenseTraceeView.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="DenseTraceeView.Designer.cs">
+      <DependentUpon>DenseTraceeView.cs</DependentUpon>
+    </Compile>
+    <Compile Include="DoubleBufferedPanel.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="IndividualBlockPlot.cs" />
+    <Compile Include="IndividualSpringyPlot.cs" />
+    <Compile Include="IndividualNetworkPlot.cs" />
+    <Compile Include="MainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="MainForm.Designer.cs">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TestForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="TestForm.Designer.cs">
+      <DependentUpon>TestForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="TraceeForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="TraceeForm.Designer.cs">
+      <DependentUpon>TraceeForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="TraceeWindow.cs" />
+    <Compile Include="TrajectorySeries.cs" />
+    <Compile Include="TrajectoryView.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="TrajectoryView.Designer.cs">
+      <DependentUpon>TrajectoryView.cs</DependentUpon>
+    </Compile>
+    <EmbeddedResource Include="BlockDenseView.resx">
+      <DependentUpon>BlockDenseView.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="DenseTraceeView.resx">
+      <DependentUpon>DenseTraceeView.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="MainForm.resx">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <EmbeddedResource Include="TestForm.resx">
+      <DependentUpon>TestForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="TraceeForm.resx">
+      <DependentUpon>TraceeForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="TrajectoryView.resx">
+      <DependentUpon>TrajectoryView.cs</DependentUpon>
+    </EmbeddedResource>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.Designer.cs
new file mode 100644
index 0000000..7b6837e
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.Designer.cs
@@ -0,0 +1,112 @@
+namespace M4MDenseDev
+{
+    partial class MainForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.button1 = new System.Windows.Forms.Button();
+            this.EmptySize = new System.Windows.Forms.NumericUpDown();
+            this.ComposeArgs = new System.Windows.Forms.TextBox();
+            this.button2 = new System.Windows.Forms.Button();
+            ((System.ComponentModel.ISupportInitialize)(this.EmptySize)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // button1
+            // 
+            this.button1.Location = new System.Drawing.Point(12, 38);
+            this.button1.Name = "button1";
+            this.button1.Size = new System.Drawing.Size(109, 23);
+            this.button1.TabIndex = 0;
+            this.button1.Text = "Create";
+            this.button1.UseVisualStyleBackColor = true;
+            this.button1.Click += new System.EventHandler(this.button1_Click);
+            // 
+            // EmptySize
+            // 
+            this.EmptySize.Location = new System.Drawing.Point(12, 12);
+            this.EmptySize.Name = "EmptySize";
+            this.EmptySize.Size = new System.Drawing.Size(109, 20);
+            this.EmptySize.TabIndex = 1;
+            this.EmptySize.Value = new decimal(new int[] {
+            8,
+            0,
+            0,
+            0});
+            // 
+            // ComposeArgs
+            // 
+            this.ComposeArgs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.ComposeArgs.Location = new System.Drawing.Point(12, 84);
+            this.ComposeArgs.Multiline = true;
+            this.ComposeArgs.Name = "ComposeArgs";
+            this.ComposeArgs.Size = new System.Drawing.Size(259, 71);
+            this.ComposeArgs.TabIndex = 2;
+            this.ComposeArgs.Text = "m4m gen=composedense targetpackage=ivmc:4 Z=0.25 lambda=0.1 name=ivmcexample pops" +
+    "ize=1 epochs=1000 regfunc=l1\r\n";
+            // 
+            // button2
+            // 
+            this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.button2.Location = new System.Drawing.Point(12, 161);
+            this.button2.Name = "button2";
+            this.button2.Size = new System.Drawing.Size(109, 23);
+            this.button2.TabIndex = 3;
+            this.button2.Text = "Compose";
+            this.button2.UseVisualStyleBackColor = true;
+            this.button2.Click += new System.EventHandler(this.button2_Click);
+            // 
+            // MainForm
+            // 
+            this.AllowDrop = true;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(283, 196);
+            this.Controls.Add(this.button2);
+            this.Controls.Add(this.ComposeArgs);
+            this.Controls.Add(this.EmptySize);
+            this.Controls.Add(this.button1);
+            this.Name = "MainForm";
+            this.Text = "M4M Dense Dev";
+            this.DragDrop += new System.Windows.Forms.DragEventHandler(this.MainForm_DragDrop);
+            this.DragEnter += new System.Windows.Forms.DragEventHandler(this.MainForm_DragEnter);
+            ((System.ComponentModel.ISupportInitialize)(this.EmptySize)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.Button button1;
+        private System.Windows.Forms.NumericUpDown EmptySize;
+        private System.Windows.Forms.TextBox ComposeArgs;
+        private System.Windows.Forms.Button button2;
+    }
+}
+
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.cs b/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.cs
new file mode 100644
index 0000000..4fb4207
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.cs
@@ -0,0 +1,163 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseDev
+{
+    public partial class MainForm : Form
+    {
+        public MainForm()
+        {
+            InitializeComponent();
+        }
+
+        private void MainForm_DragDrop(object sender, DragEventArgs e)
+        {
+            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+            foreach (string file in files)
+            {
+                try
+                {
+                    if (System.IO.File.Exists(file))
+                    {
+                        var fileName = System.IO.Path.GetFileName(file);
+                        
+                        if (fileName.StartsWith("ist"))
+                        {
+                            continue; // we will be handled by someone else
+                        }
+                        else if (fileName.StartsWith("genome"))
+                        {
+                            var genome = M4M.Analysis.LoadGenome(file);
+                            var exampleContext = Examples.CreateDefaultContext(genome.Size);
+                            exampleContext.Genome = genome;
+                            Forms.ShowDenseContext(file, exampleContext);
+                        }
+                        else if (fileName.StartsWith("rcs"))
+                        {
+                            var istFile = files.FirstOrDefault(f => System.IO.Path.GetFileName(f).StartsWith("ist"));
+                            var rcs = M4M.Analysis.LoadTrajectories(file, out int samplePeriod);
+
+                            if (istFile != null)
+                            {
+                                var ist = M4M.Analysis.LoadTrajectories(istFile, out int _istSamplePeriod);
+                                if (_istSamplePeriod != samplePeriod)
+                                    throw new Exception("Ist and Rcs sampleperiods must match");
+
+                                Forms.ShowTraceeInfo(file, DenseTraceeInfo.FromRcsAndIstInfo(samplePeriod, rcs, ist));
+                            }
+                            else
+                            {
+                                Forms.ShowTraceeInfo(file, DenseTraceeInfo.FromRcsInfo(samplePeriod, rcs));
+                            }
+                        }
+                        else
+                        {
+                            var data = M4M.State.GraphSerialisation.Read<object>(file);
+
+                            if (data is M4M.PopulationExperiment<M4M.DenseIndividual> denseExp)
+                            {
+                                Forms.ShowDenseContext(file, DenseContext.FromPopulationExperiment(denseExp));
+                            }
+                            else if (data is M4M.TraceInfo<M4M.DenseIndividual> traceInfo)
+                            {
+                                Forms.ShowTraceeInfo(file, DenseTraceeInfo.FromTraceInfo(traceInfo, false));
+                            }
+                            else if (data is List<M4M.WholeSample<M4M.DenseIndividual>> wholesamples)
+                            {
+                                Forms.ShowTraceeInfo(file, DenseTraceeInfo.FromWholeSamples(wholesamples, false));
+                            }
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace);
+                }
+            }
+        }
+
+        private void MainForm_DragEnter(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                e.Effect = DragDropEffects.Copy;
+            }
+        }
+
+        private void button1_Click(object sender, EventArgs e)
+        {
+            int size = (int)EmptySize.Value;
+
+            var context = Examples.CreateDefaultContext(size);
+
+            Forms.ShowDenseContext("Example of Size " + size, context);
+        }
+
+        private void button2_Click(object sender, EventArgs e)
+        {
+            var argLine = ComposeArgs.Text.Replace("\r", "").Replace("\n", " ");
+
+            var clips = new M4M.CliParams(false);
+            clips.ConsumeLine(argLine);
+            Cli.LoadParamFiles(clips);
+
+            var exp = M4M.ExperimentComposition.Typical.DefaultDenseExperimentComposer.Compose("", TextWriter.Null, clips);
+
+            var context = DenseContext.FromPopulationExperiment(exp);
+
+            Forms.ShowDenseContext(argLine, context);
+        }
+    }
+
+    public static class Examples
+    {
+        public static DenseContext CreateDefaultContext(int size)
+        {
+            var config = M4M.TypicalConfiguration.CreateConfig(
+                size,
+                M4M.TypicalConfiguration.CreateStandardDevelopmentRules(M4M.DevelopmentRules.TanhHalf),
+                M4M.TypicalConfiguration.CreateStandardReproductionRules(2, 2E-5, 1.0, gMutationType: M4M.NoiseType.Binary),
+                M4M.TypicalConfiguration.CreateStandardJudgementRules(0.1, M4M.JudgementRules.L1Equivalent),
+                null,
+                1000,
+                1000,
+                0,
+                new M4M.Misc.Range(-1, 1.0),
+                null);
+
+            var genome = M4M.DenseGenome.CreateDefaultGenome(size);
+
+            return new DenseContext(false, 0, config, genome);
+        }
+    }
+
+    public static class Forms
+    {
+        public static TestForm ShowDenseContext(string title, DenseContext denceContext)
+        {
+            TestForm tf = new TestForm();
+            tf.Text = title;
+            tf.DenseContext = denceContext;
+            tf.Show();
+            return tf;
+        }
+
+        public static TraceeForm ShowTraceeInfo(string title, DenseTraceeInfo denseTraceeInfo)
+        {
+            TraceeForm tf = new TraceeForm();
+            tf.Text = title;
+            tf.DenseTraceeInfo = denseTraceeInfo;
+            tf.Show();
+            return tf;
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.resx b/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/MainForm.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/Program.cs b/M4MCode/M4MDenseDev/M4MDenseDev/Program.cs
new file mode 100644
index 0000000..1739685
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseDev
+{
+    static class Program
+    {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new MainForm());
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/Properties/AssemblyInfo.cs b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..105c1a7
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("M4MDenseDev")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("M4MDenseDev")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("11da11e0-0d2c-4c35-b74e-ba8926d27b9d")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..7365dbc
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace M4MDenseDev.Properties
+{
+
+
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("M4MDenseDev.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.resx b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..17ce8bf
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace M4MDenseDev.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.settings b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.Designer.cs
new file mode 100644
index 0000000..a2457af
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.Designer.cs
@@ -0,0 +1,101 @@
+namespace M4MDenseDev
+{
+    partial class TestForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+            this.BlockView = new M4MDenseDev.BlockDenseView();
+            this.TrajectoryView = new M4MDenseDev.TrajectoryView();
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
+            this.splitContainer1.Panel1.SuspendLayout();
+            this.splitContainer1.Panel2.SuspendLayout();
+            this.splitContainer1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // splitContainer1
+            // 
+            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.splitContainer1.Location = new System.Drawing.Point(0, 0);
+            this.splitContainer1.Name = "splitContainer1";
+            // 
+            // splitContainer1.Panel1
+            // 
+            this.splitContainer1.Panel1.Controls.Add(this.BlockView);
+            // 
+            // splitContainer1.Panel2
+            // 
+            this.splitContainer1.Panel2.Controls.Add(this.TrajectoryView);
+            this.splitContainer1.Size = new System.Drawing.Size(800, 450);
+            this.splitContainer1.SplitterDistance = 413;
+            this.splitContainer1.TabIndex = 1;
+            // 
+            // BlockView
+            // 
+            this.BlockView.DenseContext = null;
+            this.BlockView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.BlockView.Location = new System.Drawing.Point(0, 0);
+            this.BlockView.Name = "BlockView";
+            this.BlockView.Size = new System.Drawing.Size(413, 450);
+            this.BlockView.TabIndex = 0;
+            // 
+            // TrajectoryView
+            // 
+            this.TrajectoryView.DenseContext = null;
+            this.TrajectoryView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.TrajectoryView.Location = new System.Drawing.Point(0, 0);
+            this.TrajectoryView.Name = "TrajectoryView";
+            this.TrajectoryView.Size = new System.Drawing.Size(383, 450);
+            this.TrajectoryView.TabIndex = 0;
+            // 
+            // TestForm
+            // 
+            this.AllowDrop = true;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(800, 450);
+            this.Controls.Add(this.splitContainer1);
+            this.DoubleBuffered = true;
+            this.Name = "TestForm";
+            this.Text = "TestForm";
+            this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TestForm_DragDrop);
+            this.DragEnter += new System.Windows.Forms.DragEventHandler(this.TestForm_DragEnter);
+            this.splitContainer1.Panel1.ResumeLayout(false);
+            this.splitContainer1.Panel2.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
+            this.splitContainer1.ResumeLayout(false);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private BlockDenseView BlockView;
+        private System.Windows.Forms.SplitContainer splitContainer1;
+        private TrajectoryView TrajectoryView;
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.cs
new file mode 100644
index 0000000..f3c7b82
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseDev
+{
+    public partial class TestForm : Form
+    {
+        public TestForm()
+        {
+            InitializeComponent();
+        }
+        
+        public DenseContext DenseContext
+        {
+            get => BlockView.DenseContext;
+            set
+            {
+                BlockView.DenseContext = value;
+                TrajectoryView.DenseContext = value;
+            }
+        }
+
+        private void TestForm_DragDrop(object sender, DragEventArgs e)
+        {
+            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+            foreach (string file in files)
+            {
+                try
+                {
+                    if (System.IO.File.Exists(file))
+                    {
+                        var fileName = System.IO.Path.GetFileName(file);
+
+                        if (fileName.StartsWith("ist"))
+                        {
+                            continue; // ignore
+                        }
+                        else if (fileName.StartsWith("rcs"))
+                        {
+                            continue; // ignore
+                        }
+                        else if (fileName.StartsWith("genome"))
+                        {
+                            var genome = M4M.Analysis.LoadGenome(file);
+                            DenseContext.Genome = genome;
+                        }
+                        else
+                        {
+                            var data = M4M.State.GraphSerialisation.Read<object>(file);
+
+                            if (data is M4M.PopulationExperiment<M4M.DenseIndividual> denseExp)
+                            {
+                                var oldContext = DenseContext;
+                                DenseContext = DenseContext.FromPopulationExperiment(denseExp);
+
+                                if (oldContext != null)
+                                {
+                                    DenseContext.Genome = oldContext.Genome;
+                                }
+                            }
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace);
+                }
+            }
+        }
+
+        private void TestForm_DragEnter(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                e.Effect = DragDropEffects.Copy;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.resx b/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TestForm.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.Designer.cs
new file mode 100644
index 0000000..944a5e9
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.Designer.cs
@@ -0,0 +1,63 @@
+namespace M4MDenseDev
+{
+    partial class TraceeForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.TraceeView = new M4MDenseDev.DenseTraceeView();
+            this.SuspendLayout();
+            // 
+            // TraceeView
+            // 
+            this.TraceeView.DenseTraceeInfo = null;
+            this.TraceeView.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.TraceeView.Location = new System.Drawing.Point(0, 0);
+            this.TraceeView.Name = "TraceeView";
+            this.TraceeView.ShowControls = true;
+            this.TraceeView.Size = new System.Drawing.Size(800, 450);
+            this.TraceeView.TabIndex = 0;
+            // 
+            // TraceeForm
+            // 
+            this.AllowDrop = true;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(800, 450);
+            this.Controls.Add(this.TraceeView);
+            this.Name = "TraceeForm";
+            this.Text = "TraceeForm";
+            this.DragDrop += new System.Windows.Forms.DragEventHandler(this.TraceeForm_DragDrop);
+            this.DragEnter += new System.Windows.Forms.DragEventHandler(this.TraceeForm_DragEnter);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private DenseTraceeView TraceeView;
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.cs
new file mode 100644
index 0000000..b40e8e4
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseDev
+{
+    public partial class TraceeForm : Form
+    {
+        public TraceeForm()
+        {
+            InitializeComponent();
+            TraceeView.Moused += TraceeView_Moused;
+        }
+
+        private DenseContext CurrentContext = null;
+        private TestForm ContextForm = null;
+
+        private void TraceeView_Moused(long time, TraceeSample closestSample)
+        {
+            DisplayGenome(time, closestSample);
+        }
+
+        private void DisplayGenome(long time, TraceeSample closestSample)
+        {
+            //this.Text = $"Time: {time}; {closestSample.Time}";
+
+            int size = DenseTraceeInfo.Size;
+
+            this.Text = closestSample.Time + ";" + closestSample.DevelopmentalTransformationMatrix.Length;
+
+            if (CurrentContext == null || ContextForm.IsDisposed)
+            {
+                CurrentContext = Examples.CreateDefaultContext(size);
+                ContextForm = Forms.ShowDenseContext("Default of Size " + size, CurrentContext);
+            }
+            
+            if (TraceeView.ShowRegCoefs && closestSample.DevelopmentalTransformationMatrix != null)
+            {
+                var dtm = MathNet.Numerics.LinearAlgebra.CreateMatrix.DenseOfMatrix(MathNet.Numerics.LinearAlgebra.CreateMatrix.SparseOfRowMajor(size, size, closestSample.DevelopmentalTransformationMatrix));
+                CurrentContext.Genome.CopyOverTransMat(dtm);
+            }
+
+            if (TraceeView.ShowInitialStates && closestSample.InitialState != null)
+            {
+                var isv = MathNet.Numerics.LinearAlgebra.CreateVector.DenseOfArray(closestSample.InitialState);
+                CurrentContext.Genome.CopyOverInitialState(isv);
+            }
+            
+            CurrentContext.NotifyChange(DenseContextThing.Genome);
+        }
+
+        private void TraceeForm_DragDrop(object sender, DragEventArgs e)
+        {
+            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+            foreach (string file in files)
+            {
+                try
+                {
+                    if (System.IO.File.Exists(file))
+                    {
+                        var fileName = System.IO.Path.GetFileName(file);
+
+                        if (fileName.StartsWith("ist"))
+                        {
+                            continue; // ignore
+                        }
+                        if (fileName.StartsWith("rcs"))
+                        {
+                            continue; // ignore
+                        }
+                        else
+                        {
+                            var data = M4M.State.GraphSerialisation.Read<object>(file);
+
+                            if (data is M4M.PopulationExperiment<M4M.DenseIndividual> denseExp)
+                            {
+                                if (CurrentContext == null)
+                                    DisplayGenome(TraceeView.DenseTraceeInfo.Samples[0].Time, TraceeView.DenseTraceeInfo.Samples[0]);
+
+                                var oldContext = CurrentContext;
+                                CurrentContext = DenseContext.FromPopulationExperiment(denseExp);
+                                CurrentContext.Genome = oldContext.Genome;
+                                ContextForm.DenseContext = CurrentContext;
+                            }
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace);
+                }
+            }
+        }
+
+        private void TraceeForm_DragEnter(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                e.Effect = DragDropEffects.Copy;
+            }
+        }
+
+        public DenseTraceeInfo DenseTraceeInfo
+        {
+            get => TraceeView.DenseTraceeInfo;
+            set
+            {
+                TraceeView.DenseTraceeInfo = value;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.resx b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeForm.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TraceeWindow.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeWindow.cs
new file mode 100644
index 0000000..91192e3
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TraceeWindow.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4MDenseDev
+{
+    class TraceeWindow
+    {
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TrajectorySeries.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectorySeries.cs
new file mode 100644
index 0000000..cc18ed7
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectorySeries.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using OxyPlot;
+
+namespace M4MDenseDev
+{
+    public enum TrajectoryDownsampleMode
+    {
+        None = 0,
+        Skip = 1,
+        Mean = 2,
+    }
+
+    public class TrajectorySeries : OxyPlot.Series.LineSeries
+    {
+        public TrajectorySeries(IReadOnlyList<DataPoint> trueDataPoints, TrajectoryDownsampleMode downsampleMode, int maxRenderedSamples=0)
+        {
+            TrueDataPoints = trueDataPoints;
+            DownsampleMode = downsampleMode;
+            MaxRenderedSamples = maxRenderedSamples;
+            UpdateTrajectory(true);
+        }
+
+        public IReadOnlyList<DataPoint> TrueDataPoints { get; set; }
+        public TrajectoryDownsampleMode DownsampleMode { get; set; }
+        public int MaxRenderedSamples { get; set; }
+    
+        public override void Render(IRenderContext rc)
+        {
+            UpdateTrajectory(false);
+            base.Render(rc);
+        }
+        
+        private double? PreviousActualMinimum = null;
+        private double? PreviousActualMaximum = null;
+
+        public void Invalidate()
+        {
+            PreviousActualMinimum = null;
+            PreviousActualMaximum = null;
+        }
+
+        public void UpdateTrajectory(bool extremes)
+        {
+            if (extremes)
+            {
+                var extremePoints = new List<DataPoint>();
+                extremePoints.Add(M4M.Misc.ArgMin(TrueDataPoints, dp => dp.X));
+                extremePoints.Add(M4M.Misc.ArgMax(TrueDataPoints, dp => dp.X));
+                extremePoints.Add(M4M.Misc.ArgMin(TrueDataPoints, dp => dp.Y));
+                extremePoints.Add(M4M.Misc.ArgMax(TrueDataPoints, dp => dp.Y));
+
+                Invalidate();
+                
+                this.Points.Clear();
+                this.Points.AddRange(extremePoints);
+                this.UpdateData();
+            }
+            else
+            {
+                this.EnsureAxes();
+                var xaxis = this.XAxis;
+
+                if (PreviousActualMinimum == xaxis.ActualMinimum && PreviousActualMaximum == xaxis.ActualMaximum)
+                    return; // don't redo if unnecessary
+
+                int min = Math.Max(0, TrueDataPoints.TakeWhile(dp => dp.X < xaxis.ActualMinimum).Count() - 1);
+                int max = Math.Min(TrueDataPoints.Count - 1, TrueDataPoints.TakeWhile(dp => dp.X < xaxis.ActualMaximum).Count() + 1);
+                int count = max - min + 1;
+
+                PreviousActualMinimum = xaxis.ActualMinimum;
+                PreviousActualMaximum = xaxis.ActualMaximum;
+                
+                var points = new List<DataPoint>();
+                
+                if (count > 4)
+                {
+                    int samplePeriod = MaxRenderedSamples == 0 ? 4 : Math.Max(count / MaxRenderedSamples, 1);
+
+                    // normalise min
+                    min = Math.Max(0, min - 1);
+                    min = min - (min % samplePeriod);
+
+                    // normalise max
+                    max = Math.Min(TrueDataPoints.Count - 1, max - 1);
+                    max = max + (samplePeriod - max % samplePeriod);
+                    max = Math.Min(TrueDataPoints.Count - 1, max);
+
+                    // add datapoints
+                    if (DownsampleMode == TrajectoryDownsampleMode.Skip)
+                    {
+                        for (int i = min; i <= max; i += samplePeriod)
+                        {
+                            points.Add(TrueDataPoints[i]);
+                        }
+                    }
+                    else if (DownsampleMode == TrajectoryDownsampleMode.Mean)
+                    {
+                        // push max along 1 more
+                        max = Math.Min(TrueDataPoints.Count - 1, max + samplePeriod);
+                        max = max - (max % samplePeriod);
+
+                        for (int i = min; i < max; i += samplePeriod)
+                        {
+                            double t = 0.0;
+                            for (int subi = i; subi < i + samplePeriod; subi++)
+                                t += TrueDataPoints[subi].Y;
+
+                            double y = t / samplePeriod;
+                            points.Add(new DataPoint(TrueDataPoints[i].X, y));
+                        }
+                    }
+                    else
+                    {
+                        for (int i = min; i <= max; i += 1)
+                        {
+                            points.Add(TrueDataPoints[i]);
+                        }
+                    }
+                }
+                else
+                {
+                    max = Math.Min(TrueDataPoints.Count - 1, max + 2);
+                    min = Math.Max(0, min - 2);
+
+                    for (int i = min; i <= max; i++)
+                    {
+                        Points.Add(TrueDataPoints[i]);
+                    }
+                }
+                
+                this.Points.Clear();
+                this.Points.AddRange(points);
+                this.UpdateData();
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.Designer.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.Designer.cs
new file mode 100644
index 0000000..cbb5247
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.Designer.cs
@@ -0,0 +1,45 @@
+namespace M4MDenseDev
+{
+    partial class TrajectoryView
+    {
+        /// <summary> 
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Component Designer generated code
+
+        /// <summary> 
+        /// Required method for Designer support - do not modify 
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            // 
+            // TrajectoryView
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Name = "TrajectoryView";
+            this.Load += new System.EventHandler(this.TrajectoryView_Load);
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.cs b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.cs
new file mode 100644
index 0000000..7f74754
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseDev
+{
+    public partial class TrajectoryView : UserControl
+    {
+        private DenseContext _denseContext;
+        public DenseContext DenseContext
+        {
+            get => _denseContext;
+            set
+            {
+                if (_denseContext != value)
+                {
+                    if (_denseContext != null)
+                        _denseContext.DenseContextUpdated -= DenseContextChanged;
+
+                    _denseContext = value;
+                    
+                    if (_denseContext != null)
+                        _denseContext.DenseContextUpdated += DenseContextChanged;
+
+                    ContextChanged();
+                }
+            }
+        }
+        
+        public OxyPlot.WindowsForms.PlotView PlotView { get; private set; }
+        
+        public TrajectoryView()
+        {
+            InitializeComponent();
+        }
+
+        private void TrajectoryView_Load(object sender, EventArgs e)
+        {
+            DoLoad();
+        }
+
+        private void DoLoad()
+        {
+            if (PlotView == null)
+            {
+                PlotView = new OxyPlot.WindowsForms.PlotView();
+                PlotView.Dock = DockStyle.Fill;
+                this.Controls.Add(PlotView);
+            }
+        }
+
+        private void DenseContextChanged(DenseContextThing things)
+        {
+            ContextChanged();
+        }
+
+        private void ContextChanged()
+        {
+            DoLoad();
+
+            if (DenseContext.Trajectories == null)
+            {
+                PlotView.Model = null;
+            }
+            else
+            {
+                PlotView.Model = null; // important
+                var plot = M4M.TrajectoryPlotting.PrepareTrajectoriesPlot("", "Developmental Time Steps", "Trait Expression", null, null, null, "x", "y", null, false, false, false);
+                M4M.TrajectoryPlotting.PlotTrajectories(plot, "Developmental Trajectories", DenseContext.Trajectories, 1, 0, (OxyPlot.OxyColor?)null, null, 2, 1, M4M.TrajectoryPlotType.Line, "x", "y");
+                foreach (var s in plot.Series)
+                    s.TrackerFormatString = "Trait " + s.Tag.ToString() + Environment.NewLine + s.TrackerFormatString;
+                PlotView.Model = plot;
+            }
+            this.Invalidate(true);
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.resx b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/TrajectoryView.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/M4MDenseDev/packages.config b/M4MCode/M4MDenseDev/M4MDenseDev/packages.config
new file mode 100644
index 0000000..ac173f3
--- /dev/null
+++ b/M4MCode/M4MDenseDev/M4MDenseDev/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="MathNet.Numerics" version="4.9.1" targetFramework="net452" />
+  <package id="OxyPlot.Core" version="2.0.0" targetFramework="net452" />
+  <package id="OxyPlot.WindowsForms" version="2.0.0" targetFramework="net452" />
+</packages>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/MindlessPlotting/App.config b/M4MCode/M4MDenseDev/MindlessPlotting/App.config
new file mode 100644
index 0000000..e75a334
--- /dev/null
+++ b/M4MCode/M4MDenseDev/MindlessPlotting/App.config
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-5.0.0.1" newVersion="5.0.0.1" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/MindlessPlotting/MindlessPlotting.csproj b/M4MCode/M4MDenseDev/MindlessPlotting/MindlessPlotting.csproj
new file mode 100644
index 0000000..97bd157
--- /dev/null
+++ b/M4MCode/M4MDenseDev/MindlessPlotting/MindlessPlotting.csproj
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{7B28F50F-F7DF-4285-AE61-D8876BA3AF88}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>MindlessPlotting</RootNamespace>
+    <AssemblyName>MindlessPlotting</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Instances, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\Instances.1.6.0\lib\netstandard2.0\Instances.dll</HintPath>
+    </Reference>
+    <Reference Include="M4M.Model">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.Model.dll</HintPath>
+    </Reference>
+    <Reference Include="MathNet.Numerics, Version=4.9.1.0, Culture=neutral, PublicKeyToken=null" />
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot, Version=2.0.0.0, Culture=neutral, PublicKeyToken=638079a8f0bd61e9, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\OxyPlot.Core.2.0.0\lib\net45\OxyPlot.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot.WindowsForms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=245eacd6b5d2d338, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\OxyPlot.WindowsForms.2.0.0\lib\net45\OxyPlot.WindowsForms.dll</HintPath>
+    </Reference>
+    <Reference Include="PresentationCore" />
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Drawing.Common, Version=4.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Drawing.Common.5.0.2\lib\net461\System.Drawing.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encodings.Web.5.0.0\lib\net461\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=5.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Json.5.0.1\lib\net461\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\M4MDenseDev\M4MDenseDev.csproj">
+      <Project>{11da11e0-0d2c-4c35-b74e-ba8926d27b9d}</Project>
+      <Name>M4MDenseDev</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseDev/MindlessPlotting/Program.cs b/M4MCode/M4MDenseDev/MindlessPlotting/Program.cs
new file mode 100644
index 0000000..0733673
--- /dev/null
+++ b/M4MCode/M4MDenseDev/MindlessPlotting/Program.cs
@@ -0,0 +1,512 @@
+using M4M;
+using MathNet.Numerics.LinearAlgebra;
+using MathNet.Numerics.Random;
+using OxyPlot;
+using OxyPlot.Series;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+
+namespace MindlessPlotting
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            var fixedG = @"C:\M4MResults\IvmcSplitSingleBVFastRepeatsL010BExZ100FixedG\IvmcSplitSingleBVFastRepeatsL010BExZ100FixedG\IvmcSplitSingleBVFastRepeatsL010BExZ100FixedG\IvmcSplitSingleBVFastRepeatsL010BExZ100FixedGZ100runsOne\r0";
+            var ivmcL1 = @"C:\M4MResults\IvmcSplitSingleBVFastRepeatsL010BEx\IvmcSplitSingleBVFastRepeatsL010BEx\IvmcSplitSingleBVFastRepeatsL010BExZ095runsOne\r0";
+            var ivmcL0 = @"C:\M4MResults\IvmcSplitSingleBVFastRepeatsBExVaryL\IvmcSplitSingleBVFastRepeatsBExVaryL\IvmcSplitSingleBVFastRepeatsBExVaryLL000runsOne\r0"; // also Z=0.95
+            var ivmcL2 = @"C:\M4MResults\IvmcSplitSingleBVFastL2SpreadBEx\IvmcSplitSingleBVFastL2SpreadBEx\IvmcSplitSingleBVFastL2SpreadBExL002runsOne\Z095"; // also Z=0.95
+            var ivmcL2Z0 = @"C:\M4MResults\IvmcSplitSingleBVFastL2SpreadBEx\IvmcSplitSingleBVFastL2SpreadBEx\IvmcSplitSingleBVFastL2SpreadBExL002runsOne\Z000"; // also Z=0
+            var ivmcL1lowlambda = @"C:\M4MResults\IvmcSplitSingleBVFastRepeatsBExVaryL\IvmcSplitSingleBVFastRepeatsBExVaryL\IvmcSplitSingleBVFastRepeatsBExVaryLL002runsOne\r0";
+            var ivmcL1And2 = @"C:\M4MTest\L1And2\VaryLB\VaryLB\VaryLBL010runsOne\B0002";
+            var ivmcMMSO = @"C:\M4MResults\IvmcSplitSingleBVFastRepeatsL010BExMMSO\IvmcSplitSingleBVFastRepeatsL010BExMMSO\IvmcSplitSingleBVFastRepeatsL010BExMMSOZ000runsOne\r0"; // Z=0
+
+            GoWeirdIvmcThing(ivmcMMSO, "ivmcMMSO_weird.gif", skip: 2, lastEpoch: 20000, showDev: false);
+            GoWeirdIvmcThing(ivmcL2, "ivmcL2_weird.gif", skip: 10, lastEpoch: 100000, showDev: false);
+            GoWeirdIvmcThing(ivmcL1And2, "ivmcL1And2_weird.gif", skip: 10, lastEpoch: 100000, showDev: false);
+            GoWeirdIvmcThing(ivmcL1, "ivmcL1_weird.gif", skip: 20, lastEpoch: 240000, showDev: false);
+
+            // sample period is 10, so skip is in 10s of epochs
+            //Go(fixedG, "fixedG.gif", skip: 5, lastEpoch: 10000, showDev : false); // per paper
+            //Go(ivmcL1, "ivmcL1.gif", skip: 50, lastEpoch: 120000, showDev: false); // per paper
+            //Go(ivmcL0, "ivmcL0.gif", skip: 200, lastEpoch: 400000, showDev: false); // not in paper
+            //Go(ivmcL2, "ivmcL2.gif", skip: 50, lastEpoch: 120000, showDev: false); // not in paper
+            //Go(ivmcl1lowlambda, "ivmcl1lowlambda.gif", skip: 50, lastEpoch: 120000, showDev: false); // not in paper
+
+            // we seem to run out of memory if we don't skip some extra stuff here...
+            //Go(fixedG, "fixedGT.gif", skip: 10, lastEpoch: 10000, showDev: true); // per paper
+            //Go(ivmcL1, "ivmcL1T.gif", skip: 100, lastEpoch: 120000, showDev: true); // per paper
+            //Go(ivmcL0, "ivmcL0T.gif", skip: 500, lastEpoch: 400000, showDev: true); // not in paper
+
+            // Tracees
+            //GoTracee(ivmcL1, "ivmcL1_tracee.gif", samplePeriod: 1, lastEpoch: 120000, epochs: 5, generationCutoff: 200);
+            //GoTracee(ivmcL0, "ivmcL0_tracee.gif", samplePeriod: 1, lastEpoch: 120000, epochs: 5, generationCutoff: 200);
+            //GoTracee(ivmcL2, "ivmcL2_tracee.gif", samplePeriod: 1, lastEpoch: 120000, epochs: 5, generationCutoff: 200);
+            //GoTracee(ivmcL1, "ivmcL1_traceetest.gif", samplePeriod: 1, lastEpoch: 120000, epochs: 5, generationCutoff: 200);
+        }
+
+        static void GoTracee(string dir, string outfile, int samplePeriod, int lastEpoch, int epochs, int generationCutoff)
+        {
+            var rand = new CustomMersenneTwister(1);
+            var wholeSamples = WholeSample<DenseIndividual>.LoadWholeSamples(System.IO.Path.Combine(dir, "wholesamplese400000.dat"));
+            var exp = PopulationExperiment<DenseIndividual>.Load(System.IO.Path.Combine(dir, "epoch0savestart.dat"));
+            var ctx = new ModelExecutionContext(rand);
+            var tracee = M4M.PopulationTrace.RunTrace(Console.Out, ctx, exp.Population, exp.PopulationConfig, exp.PopulationConfig.ExperimentConfiguration.Targets.ToArray(), samplePeriod, exp.Epoch, epochs);
+
+            if (exp.PopulationConfig.ExperimentConfiguration.Targets[0] is M4M.Epistatics.IvmcProperChanging ipc)
+            {
+                // not good code
+                ipc.GetType().GetProperty("Z").SetValue(ipc, 1);
+            }
+
+            exp.Population.Clear();
+            exp.Population.IntroduceMany(wholeSamples.First(w => w.Epoch == lastEpoch).Judgements.Select(ij => ij.Individual));
+
+            int highlight = -1;
+            int totalGenerations = 0;
+            int generation = 0;
+            int c = 0;
+            var benefitSequence = new List<KeyValuePair<int, double>>();
+            var individualSequence = new List<KeyValuePair<int, HighlightTargetIndividual>>();
+            void feedback(FileStuff stuff, IReadOnlyList<IndividualJudgement<DenseIndividual>> populationJudgements, int epochCount, long generationCount, ITarget target)
+            {
+                if (generation++ >= generationCutoff)
+                {
+                    return;
+                }
+                totalGenerations++;
+
+                if (c++ != 0)
+                {
+                    if (c == samplePeriod)
+                        c = 0;
+                    return;
+                }
+                else if (c == samplePeriod)
+                {
+                    c = 0;
+                }
+
+                var t = target as M4M.IPerfectPhenotypeTarget;
+                var pp = t.PreparePerfectP();
+
+                var individual = populationJudgements[0].Individual.Clone(ctx);
+                var g = individual.Genome.InitialState;
+                for (int i = 0; i < pp.Count; i++)
+                {
+                    //// high -> correct
+                    //g[i] = g[i] * Math.Sign(pp[i]);
+                }
+
+                individual.DevelopInplace(ctx, exp.PopulationConfig.ExperimentConfiguration.DevelopmentRules);
+                individualSequence.Add(new KeyValuePair<int, HighlightTargetIndividual>(totalGenerations, new HighlightTargetIndividual(individual, highlight, pp)));
+                benefitSequence.Add(new KeyValuePair<int, double>(totalGenerations, populationJudgements[0].Judgement.CombinedFitness));
+                highlight = -1;
+            }
+
+            void reset(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+            {
+                // just reset the sampler; we re-compute the perfectP every frame because who really cares
+                c = 0;
+                generation = 0;
+            }
+
+            var mutator = new InitialStateMutationInterceptingMutator();
+            mutator.Mutation += h => highlight = h;
+            foreach (var individual in exp.Population.PeekAll())
+                individual.Genome.SetCustomInitialStateMutator(mutator);
+            M4M.PopulationTrace.RunTrace(Console.Out, ctx, null, exp.Population, exp.PopulationConfig, exp.PopulationConfig.ExperimentConfiguration.Targets.ToArray(), feedback, reset, exp.Epoch, epochs);
+
+            var dtm = individualSequence[0].Value.Individual.Genome.TransMat;
+            var modules = InferModules(dtm, out _, out var totalWeight);
+            var strengthRescale = 41 / totalWeight;
+
+            var plot = FitnessPlot(benefitSequence, -10, totalGenerations + 10);
+            MakeSpringyGif(exp, individualSequence, plot, 1800, 800, outfile, 5, lastEpoch, modules, strengthRescale, false, preSpin: 100000, withinSpin: 1, titleFormat: "Generation {0}", false);
+        }
+
+        static void GoWeirdIvmcThing(string dir, string outfile, int skip, int lastEpoch, bool showDev)
+        {
+            Console.WriteLine($"{dir} -> {outfile}");
+
+            var wsFile = System.IO.Directory.GetFiles(dir, "wholesamples*")[0];
+            var wholeSamples = WholeSample<DenseIndividual>.LoadWholeSamples(wsFile).Where(ws => ws.Epoch <= lastEpoch).TakeEvery(skip).ToList();
+
+            var targetPackage = M4M.Epistatics.IvmcTargetPackages.IvmcProper1_4x4(M4M.Epistatics.IvmcProperJudgementMode.Split, 1, 0.7, 0, 1);
+            var target = targetPackage.Targets[0] as IPerfectPhenotypeTarget;
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(DevelopmentRules.TanhHalf);
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(0, JudgementRules.ConstantEquivalent);
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(2, 0, 0, true, 1, null, NoiseType.Binary);
+            var config = new ExperimentConfiguration(targetPackage.TargetSize, targetPackage.Targets, Cyclers.Loop, 1000, 400, 0, new Misc.Range(-1, 1), false, drules, rrules, jrules);
+            var mutator = new InitialStateMutationInterceptingMutator();
+            var genome = new DenseGenome(CreateVector.Dense<double>(targetPackage.TargetSize, 0), CreateMatrix.Dense<double>(targetPackage.TargetSize, targetPackage.TargetSize), customInitialStateMutator: mutator);
+            var ctx = new ModelExecutionContext(new CustomMersenneTwister(42));
+            var individual = DenseIndividual.Develop(genome, ctx, drules, false).Clone(ctx);
+            var candidate = individual.Clone(ctx);
+
+            // gonna write the inner loop manually; this is too big a deviation from previous intents
+            //var popConfig = new PopulationExperimentConfig<DenseIndividual>(config, SelectorPreparers<DenseIndividual>.Ranked, 1, true, null, null, null);
+            //var pop = new Population<DenseIndividual>(new[] { individual });
+            //var exp = new PopulationExperiment<DenseIndividual>(pop, popConfig, null);
+
+            var sequence = new List<KeyValuePair<int, HighlightTargetIndividual>>();
+
+            int epochLength = 200;
+            int generation = epochLength - 1;
+            int epoch = 0;
+            int highlight = -1;
+            mutator.Mutation += (h => highlight = h);
+            ExposureInformation e = new ExposureInformation(epochLength);
+            foreach (var ws in wholeSamples)
+            {
+                if (++generation == epochLength)
+                {
+                    epoch++;
+                    if (epoch >= lastEpoch)
+                        break;
+                    generation = 0;
+                    target.NextExposure(ctx.Rand, jrules, epoch, ref e);
+                }
+
+                target.NextGeneration(ctx.Rand, jrules);
+
+                var b = ws.Judgements[0].Individual.Genome.TransMat;
+                
+                b.CopyTo(individual.Genome.TransMat);
+                individual.DevelopInplace(ctx, drules);
+                individual.MutateInto(candidate, ctx, drules, rrules);
+
+                var judgement = MultiMeasureJudgement.Judge(individual.Genome, individual.Phenotype, jrules, target);
+                var candidateJudgement = MultiMeasureJudgement.Judge(candidate.Genome, candidate.Phenotype, jrules, target);
+                if (candidateJudgement.CombinedFitness > judgement.CombinedFitness)
+                    (individual, candidate) = (candidate, individual);
+
+                sequence.Add(new KeyValuePair<int, HighlightTargetIndividual>(ws.Epoch, new HighlightTargetIndividual(individual.Clone(ctx), highlight, target.PreparePerfectP())));
+            }
+
+            var dtm = wholeSamples.Last().Judgements.First().Individual.Genome.TransMat;
+            var modules = InferModules(dtm, out var g, out var totalWeight);
+            var strengthRescale = 41 / totalWeight;
+
+            var plot = HuskyPlot(sequence, modules, -1000, lastEpoch + 1000);
+            MakeSpringyGif(null, sequence, plot, 1800, 800, outfile, 5, lastEpoch, modules, strengthRescale, showDev, titleFormat: "{0}", showG: false);
+        }
+
+        static void Go(string dir, string outfile, int skip, int lastEpoch, bool showDev)
+        {
+            Console.WriteLine($"{dir} -> {outfile}");
+
+            var wholeSamples = WholeSample<DenseIndividual>.LoadWholeSamples(System.IO.Path.Combine(dir, "wholesamplese400000.dat"));
+            var exp = PopulationExperiment<DenseIndividual>.Load(System.IO.Path.Combine(dir, "epoch0savestart.dat"));
+            var ctx = new ModelExecutionContext(null);
+
+            OldType(wholeSamples, exp, ctx, skip, lastEpoch, outfile, showDev);
+        }
+
+        // this might be doing too much now
+        /// <param name="dtm">The DTM from which to infer modules</param>
+        /// <param name="demoG">A genotype, where motivators are +1 and everyone else is -1</param>
+        /// <param name="totalWeight">The total weight in the DTM</param>
+        /// <returns>The inferred modules</returns>
+        static M4M.Modular.Modules InferModules(Matrix<double> dtm, out Vector<double> demoG, out double totalWeight)
+        {
+            double threshold = M4M.DtmClassification.ComputeAutoThreshold(dtm, 10.0);
+            var dtmModules = M4M.DtmClassification.DiscerneTrueModules(dtm, threshold);
+            var dtmInfo = new M4M.DtmInfo(dtm, dtmModules);
+            Console.WriteLine(dtmInfo.Description);
+
+            var modules = new M4M.Modular.Modules(dtmModules.Select(m => m.Motivatees.Concat(m.Motivators).Distinct())); // just lump everyone together: will be unhappy if they overlap
+
+            demoG = CreateVector.Dense(dtm.RowCount, -1.0);
+
+            foreach (var module in dtmModules)
+            {
+                foreach (var motivator in module.Motivators)
+                    demoG[motivator] = +1.0;
+            }
+
+            totalWeight = dtm.Enumerate().Sum(Math.Abs);
+
+            return modules;
+        }
+
+        static void OldType(List<WholeSample<DenseIndividual>> wholeSamples, PopulationExperiment<DenseIndividual> exp, ModelExecutionContext ctx, int samplePeriod, int lastEpoch, string outfile, bool showDev)
+        {
+            var dtm = wholeSamples.Last().Judgements.First().Individual.Genome.TransMat;
+            var modules = InferModules(dtm, out var g, out var totalWeight);
+
+            DenseIndividual run(DenseIndividual old)
+            {
+                var genome = old.Genome.Clone(ctx, g);
+                return DenseIndividual.Develop(genome, ctx, exp.PopulationConfig.ExperimentConfiguration.DevelopmentRules, old.Epigenetic);
+            }
+
+            var sequence = wholeSamples.Where(ws => ws.Epoch <= lastEpoch).TakeEvery(samplePeriod).Select(ws => new KeyValuePair<int, HighlightTargetIndividual>(ws.Epoch, run(ws.Judgements[0].Individual))).ToList();
+
+            var strengthRescale = 41 / totalWeight;
+
+            var plot = HuskyPlot(sequence, modules, -1000, lastEpoch + 1000);
+            MakeSpringyGif(exp, sequence, plot, 1800, 800, outfile, 5, lastEpoch, modules, strengthRescale, showDev);
+        }
+
+        public static OxyColor[] Colours(OxyColor c0, OxyColor c1, int n)
+        {
+            var res = new OxyColor[n];
+
+            for (int i = 0; i < res.Length; i++)
+            {
+                var z = (double)i / Math.Max(1, (res.Length - 1));
+                res[i] = OxyColor.Interpolate(c0, c1, z);
+            }
+
+            return res;
+        }
+
+        public static PlotModel FitnessPlot(IEnumerable<KeyValuePair<int, double>> sequence, int xmin, int xmax)
+        {
+            var plot = new OxyPlot.PlotModel();
+            var x = new OxyPlot.Axes.LinearAxis() { Position = OxyPlot.Axes.AxisPosition.Bottom, Title = "Generation", Minimum = xmin, Maximum = xmax };
+            var h = new OxyPlot.Axes.LinearAxis() { Position = OxyPlot.Axes.AxisPosition.Left, Title = "Fitness", Minimum = -0.015, Maximum = 1.015 };
+            plot.Axes.Add(x);
+            plot.Axes.Add(h);
+
+            var benefits = new ScatterSeries() { MarkerFill = OxyColors.Red, MarkerType = MarkerType.Diamond, MarkerSize = 2 };
+            plot.Series.Add(benefits);
+
+            foreach (var s in sequence)
+            {
+                benefits.Points.Add(new ScatterPoint(s.Key, s.Value));
+            }
+
+            return plot;
+        }
+
+        public static PlotModel HuskyPlot(IEnumerable<KeyValuePair<int, HighlightTargetIndividual>> sequence, M4M.Modular.Modules modules, int xmin, int xmax)
+        {
+            var c0 = OxyColors.DarkSlateGray;
+            var c1 = OxyColors.SlateGray;
+            var colours = Colours(c0, c1, modules.ModuleCount);
+
+            var plot = new OxyPlot.PlotModel();
+            var x = new OxyPlot.Axes.LinearAxis() { Position = OxyPlot.Axes.AxisPosition.Bottom, Title = "Epoch", Minimum = xmin, Maximum = xmax, StringFormat = "0.0E+0" };
+            var h = new OxyPlot.Axes.LinearAxis() { Position = OxyPlot.Axes.AxisPosition.Left, Title = "Degree of Hierarchy", Minimum = -0.015, Maximum = 1.015 };
+            plot.Axes.Add(x);
+            plot.Axes.Add(h);
+
+            var lines = colours.Select(c => new LineSeries() { Color = c }).ToArray();
+
+            foreach (var line in lines)
+                plot.Series.Add(line);
+
+            foreach (var s in sequence)
+            {
+                var huskyness = M4M.Analysis.ComputeModuleHuskyness(s.Value.Individual.Genome.TransMat, modules.ModuleAssignments);
+                for (int i = 0; i < lines.Length; i++)
+                {
+                    lines[i].Points.Add(new DataPoint(s.Key, huskyness[i]));
+                }
+            }
+
+            return plot;
+        }
+
+        // duration is centi-seconds, I think
+        public static void MakeSpringyGif(PopulationExperiment<DenseIndividual> exp, IEnumerable<KeyValuePair<int, HighlightTargetIndividual>> sequence, PlotModel plot, int width, int height, string outfile, int duration, int lastEpoch, M4M.Modular.Modules modules, double strengthRescale = 1.0, bool showDevTime = false, int preSpin = 5000, int withinSpin = 500, string titleFormat = "Epoch {0}", bool showG = true)
+        {
+            // init gid rendering
+            var font = new System.Drawing.Font(System.Drawing.FontFamily.GenericMonospace, 12, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
+            var genc = new System.Windows.Media.Imaging.GifBitmapEncoder();
+            var bmp = new System.Drawing.Bitmap(width, height);
+            var g = System.Drawing.Graphics.FromImage(bmp);
+
+            // init some stuff
+            var springy = new M4MDenseDev.IndividualSpringyPlot();
+
+            bool first = true;
+            double[][] trajectories = null;
+
+            // render frames
+            foreach (var s in sequence)
+            {
+                Console.WriteLine(s.Key);
+
+                if (first)
+                {
+                    springy.Update(s.Value.Individual, strengthRescale);
+                    springy.Step(preSpin);
+                    first = false;
+                }
+
+                // render frame
+                g.Clear(System.Drawing.Color.White);
+
+                var rc = new OxyPlot.WindowsForms.GraphicsRenderContext(g);
+                plot.Title = string.Format(titleFormat, s.Key);
+                plot.InvalidatePlot(true);
+                plot.Axes[0].FilterMaxValue = s.Key;
+                ((IPlotModel)plot).Update(true);
+                ((IPlotModel)plot).Render(rc, width - height, height);
+
+                void addFrame()
+                {
+                    System.IO.MemoryStream bms = new System.IO.MemoryStream();
+                    bmp.Save(bms, System.Drawing.Imaging.ImageFormat.Gif);
+                    var bmpf = System.Windows.Media.Imaging.BitmapFrame.Create(bms);
+                    genc.Frames.Add(bmpf);
+                }
+
+                var rect = new Rectangle(width - height, 0, height, height);
+
+                springy.Springy.Target = s.Value.Target;
+                springy.Springy.ShowG = showG;
+                springy.Springy.NodeRadius = 20;
+                springy.Springy.AttrationStrength = 0.05;
+                if (showDevTime)
+                {
+                    var drules = exp.PopulationConfig.ExperimentConfiguration.DevelopmentRules;
+                    var individual = s.Value.Individual;
+                    springy.Update(individual, strengthRescale);
+                    springy.Springy.Highlights.Clear();
+                    if (s.Value.Highlight >= 0)
+                        springy.Springy.Highlights.Add(s.Value.Highlight);
+                    springy.Step(withinSpin);
+                    individual.Genome.DevelopWithTrajectories(null, drules, ref trajectories);
+                    for (int t = 0; t < trajectories[0].Length; t++)
+                    {
+                        g.FillRectangle(Brushes.White, rect);
+                        var y = M4M.Analysis.ExtractVector(trajectories, t) / drules.UpdateRate * drules.DecayRate;
+                        springy.Update(individual.Genome.TransMat, y, y, strengthRescale);
+                        springy.Draw(g, rect);
+                        g.DrawString($"t = {t}", font, Brushes.Black, rect);
+                        addFrame();
+                    }
+                }
+                else
+                {
+                    springy.UpdateStepAndDraw(g, rect, s.Value.Individual, 500, strengthRescale, sp => { });
+                    springy.Springy.Highlights.Clear();
+                    if (s.Value.Highlight >= 0)
+                        springy.Springy.Highlights.Add(s.Value.Highlight);
+                    addFrame();
+                }
+            }
+
+            // produce gif
+            System.IO.MemoryStream ms = new System.IO.MemoryStream();
+
+            genc.Save(ms);
+            byte[] data = ms.GetBuffer();
+            byte[] netscape = { 0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00 };
+
+            int last = -1;
+
+            // promise yourself now you will never use this in production code
+            // I've not read enough of the GIF spec to know if this is a bad idea or not
+            for (int i = 0; i < ms.Length - 5; i++)
+            {
+                if (data[i] == 0x21 && data[i + 1] == 0xF9 && data[i + 2] == 0x04 && data[i + 3] == 01)
+                {
+                    data[i + 4] = (byte)(duration & 255); // something endian (least significant first)
+                    data[i + 5] = (byte)((duration >> 8) & 255);
+                    last = i + 4;
+                }
+            }
+
+            if (last != -1)
+            {
+                data[last] = (byte)(duration & 255);
+                data[last + 1] = (byte)((duration >> 8) & 255);
+            }
+
+            using (System.IO.FileStream fs = new System.IO.FileStream(outfile, System.IO.FileMode.Create))
+            {
+                fs.Write(data, 0, 13);
+
+                // behold
+                fs.Write(netscape, 0, netscape.Length);
+
+                fs.Write(data, 13, (int)ms.Length - 13); // lets hope these aren't in excess of 2GB
+            }
+
+            g.Dispose();
+            bmp.Dispose();
+        }
+    }
+
+    public delegate void GMutation(int gene);
+
+    /// <summary>
+    /// (re)implements the default mutator, but notifies when a mutation takes place
+    /// </summary>
+    public class InitialStateMutationInterceptingMutator : M4M.IInitialStateMutator
+    {
+        public string Name => "InitialStateMutationInterceptingMutator";
+
+        public event GMutation Mutation;
+
+        public Vector<double> Mutate(Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            var target = current.Clone();
+            MutateInplace(target, initialStateIndexOpenEntries, rand, rules);
+            return target;
+        }
+
+        public void MutateInplace(Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            int i;
+            if (initialStateIndexOpenEntries != null)
+            {
+                i = initialStateIndexOpenEntries[rand.Next(initialStateIndexOpenEntries.Count)];
+            }
+            else
+            {
+                i = rand.Next(current.Count);
+            }
+            current[i] = rules.InitialStateClamping.Clamp(current[i] + M4M.Misc.NextNoise(rand, rules.InitialStateMutationSize, rules.InitialStateMutationType));
+            Mutation?.Invoke(i);
+        }
+
+        public void MutateInto(Vector<double> current, Vector<double> target, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            current.CopyTo(target);
+            MutateInplace(target, initialStateIndexOpenEntries, rand, rules);
+        }
+    }
+
+    public struct HighlightTargetIndividual
+    {
+        public HighlightTargetIndividual(DenseIndividual individual, int highlight, Vector<double> target)
+        {
+            Individual = individual ?? throw new ArgumentNullException(nameof(individual));
+            Highlight = highlight;
+            Target = target;
+        }
+
+        public static implicit operator HighlightTargetIndividual(DenseIndividual i) => new HighlightTargetIndividual(i, -1, null);
+
+        public DenseIndividual Individual { get; }
+        public int Highlight { get; }
+        public Vector<double> Target { get; }
+    }
+
+    public static class Extensions
+    {
+        public static IEnumerable<T> TakeEvery<T>(this IEnumerable<T> sequence, int skip)
+        {
+            int i = 0;
+            foreach (var t in sequence)
+            {
+                if (i == 0)
+                {
+                    yield return t;
+                    i = skip;
+                }
+
+                i--;
+            }
+        }
+
+    }
+}
diff --git a/M4MCode/M4MDenseDev/MindlessPlotting/Properties/AssemblyInfo.cs b/M4MCode/M4MDenseDev/MindlessPlotting/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9a081f7
--- /dev/null
+++ b/M4MCode/M4MDenseDev/MindlessPlotting/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MindlessPlotting")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MindlessPlotting")]
+[assembly: AssemblyCopyright("Copyright ©  2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7b28f50f-f7df-4285-ae61-d8876ba3af88")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/M4MCode/M4MDenseDev/MindlessPlotting/packages.config b/M4MCode/M4MDenseDev/MindlessPlotting/packages.config
new file mode 100644
index 0000000..39f4eb8
--- /dev/null
+++ b/M4MCode/M4MDenseDev/MindlessPlotting/packages.config
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Instances" version="1.6.0" targetFramework="net472" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="5.0.0" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
+  <package id="System.Drawing.Common" version="5.0.2" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.4" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net472" />
+  <package id="System.Text.Encodings.Web" version="5.0.0" targetFramework="net472" />
+  <package id="System.Text.Json" version="5.0.1" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
+</packages>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod.sln b/M4MCode/M4MDenseMod/M4MDenseMod.sln
new file mode 100644
index 0000000..94f232b
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29215.179
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4MDenseMod", "M4MDenseMod\M4MDenseMod.csproj", "{A4122D48-BE27-400B-9FED-98ADBA24C70F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Debug|x64.ActiveCfg = Debug|x64
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Debug|x64.Build.0 = Debug|x64
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Debug|x86.ActiveCfg = Debug|x86
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Debug|x86.Build.0 = Debug|x86
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Release|x64.ActiveCfg = Release|x64
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Release|x64.Build.0 = Release|x64
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Release|x86.ActiveCfg = Release|x86
+		{A4122D48-BE27-400B-9FED-98ADBA24C70F}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {2821FD3E-8263-48E7-8292-E21A680B4071}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Annotation.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Annotation.cs
new file mode 100644
index 0000000..46fb254
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Annotation.cs
@@ -0,0 +1,63 @@
+using M4M;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4MDenseMod
+{
+    public interface IVectorAnnotator
+    {
+        void Annotate(Graphics g, Rectangle clipRectangle, VectorEditor editor);
+    }
+
+    public class LocalMatrixBoundryAnnotator : IVectorAnnotator
+    {
+        public bool Enabled { get; set; } = true;
+
+        public LocalMatrixBoundryAnnotator(Matrix<double> constraintMatrix)
+        {
+            ConstraintMatrix = constraintMatrix ?? throw new ArgumentNullException(nameof(constraintMatrix));
+        }
+
+        public MathNet.Numerics.LinearAlgebra.Matrix<double> ConstraintMatrix { get; }
+
+        public void Annotate(Graphics g, Rectangle clipRectangle, VectorEditor editor)
+        {
+            if (!Enabled)
+                return;
+
+            var mat = ConstraintMatrix;
+            var mih = new MatrixIndexHelper(editor.DisplayWidth, editor.DisplayHeight); // assume square
+
+            using (var pen = new Pen(Color.Red, 2))
+            {
+                foreach (var unhappy in M4M.Epistatics.StandardCorrelationMatrixTargets.EnumerateUnhappyCorrelations(mat, MathNet.Numerics.LinearAlgebra.CreateVector.DenseOfArray(editor.Vector.ToArray())))
+                {
+                    int i = unhappy.Row;
+                    int j = unhappy.Col;
+
+                    var ci = editor.GetCenter(i);
+                    var cj = editor.GetCenter(j);
+
+                    int r = ci.Y;
+                    int c = ci.X;
+                    int r2 = cj.Y;
+                    int c2 = cj.X;
+                    int dr = r2 - r;
+                    int dc = c2 - c;
+
+                    int x0 = c + dc / 2 + Math.Abs(dr) / -2;
+                    int x1 = c + dc / 2 + Math.Abs(dr) / 2;
+                    int y0 = r + dr / 2 + Math.Abs(dc) / -2;
+                    int y1 = r + dr / 2 + Math.Abs(dc) / 2;
+
+                    g.DrawLine(pen, x0, y0, x1, y1);
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/App.config b/M4MCode/M4MDenseMod/M4MDenseMod/App.config
new file mode 100644
index 0000000..bae5d6d
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/App.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
+    </startup>
+</configuration>
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Colours.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Colours.cs
new file mode 100644
index 0000000..a6d346c
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Colours.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4MDenseMod
+{
+    public interface IColours
+    {
+        Color GetColor(double value);
+    }
+
+    public class GreyColours : IColours
+    {
+        public GreyColours()
+        {
+            // defaults
+        }
+
+        public GreyColours(double max, double min)
+        {
+            Max = max;
+            Min = min;
+        }
+
+        public GreyColours(bool whiteHot, Color highColour, Color lowColour, double max, double min)
+        {
+            WhiteHot = whiteHot;
+            HighColour = highColour;
+            LowColour = lowColour;
+            Max = max;
+            Min = min;
+        }
+
+        public bool WhiteHot { get; } = true;
+
+        public Color HighColour { get; } = Color.Red;
+        public Color LowColour { get; } = Color.Blue;
+
+        public double Max { get; } = 1;
+        public double Min { get; } = -1;
+
+        public Color GetColor(double value)
+        {
+            double v = (value - Min) / (Max - Min);
+            if (v > 1.0)
+                return HighColour;
+            if (v < 0.0)
+                return LowColour;
+
+            byte b = (byte)Math.Round(v * 255);
+            return Color.FromArgb(b, b, b);
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/DoubleBufferedPanel.cs b/M4MCode/M4MDenseMod/M4MDenseMod/DoubleBufferedPanel.cs
new file mode 100644
index 0000000..291e7c4
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/DoubleBufferedPanel.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseMod
+{
+    public class DoubleBufferedPanel : Panel
+    {
+        public DoubleBufferedPanel()
+        {
+            this.DoubleBuffered = true;
+            SetStyle(ControlStyles.Selectable, true);
+        }
+
+        public new bool DoubleBuffered
+        {
+            get
+            {
+                return base.DoubleBuffered;
+            }
+            set
+            {
+                base.DoubleBuffered = value;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Form1.Designer.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Form1.Designer.cs
new file mode 100644
index 0000000..51c1c01
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Form1.Designer.cs
@@ -0,0 +1,219 @@
+namespace M4MDenseMod
+{
+    partial class MainForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.ControlPanel = new System.Windows.Forms.Panel();
+            this.FixWidthCheck = new System.Windows.Forms.CheckBox();
+            this.label2 = new System.Windows.Forms.Label();
+            this.DisplayWidthNumeric = new System.Windows.Forms.NumericUpDown();
+            this.LocalViolationBoundriesCheck = new System.Windows.Forms.CheckBox();
+            this.FixSeedCheck = new System.Windows.Forms.CheckBox();
+            this.label1 = new System.Windows.Forms.Label();
+            this.SeedNumeric = new System.Windows.Forms.NumericUpDown();
+            this.StepEpochButton = new System.Windows.Forms.Button();
+            this.SaveButton = new System.Windows.Forms.Button();
+            this.SaveFileDialog = new System.Windows.Forms.SaveFileDialog();
+            this.ControlPanel.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.DisplayWidthNumeric)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.SeedNumeric)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // ControlPanel
+            // 
+            this.ControlPanel.Controls.Add(this.FixWidthCheck);
+            this.ControlPanel.Controls.Add(this.label2);
+            this.ControlPanel.Controls.Add(this.DisplayWidthNumeric);
+            this.ControlPanel.Controls.Add(this.LocalViolationBoundriesCheck);
+            this.ControlPanel.Controls.Add(this.FixSeedCheck);
+            this.ControlPanel.Controls.Add(this.label1);
+            this.ControlPanel.Controls.Add(this.SeedNumeric);
+            this.ControlPanel.Controls.Add(this.StepEpochButton);
+            this.ControlPanel.Controls.Add(this.SaveButton);
+            this.ControlPanel.Dock = System.Windows.Forms.DockStyle.Left;
+            this.ControlPanel.Location = new System.Drawing.Point(0, 0);
+            this.ControlPanel.Name = "ControlPanel";
+            this.ControlPanel.Size = new System.Drawing.Size(228, 386);
+            this.ControlPanel.TabIndex = 0;
+            // 
+            // FixWidthCheck
+            // 
+            this.FixWidthCheck.AutoSize = true;
+            this.FixWidthCheck.Location = new System.Drawing.Point(176, 122);
+            this.FixWidthCheck.Name = "FixWidthCheck";
+            this.FixWidthCheck.Size = new System.Drawing.Size(36, 17);
+            this.FixWidthCheck.TabIndex = 6;
+            this.FixWidthCheck.Text = "fix";
+            this.FixWidthCheck.UseVisualStyleBackColor = true;
+            // 
+            // label2
+            // 
+            this.label2.AutoSize = true;
+            this.label2.Location = new System.Drawing.Point(12, 123);
+            this.label2.Name = "label2";
+            this.label2.Size = new System.Drawing.Size(38, 13);
+            this.label2.TabIndex = 5;
+            this.label2.Text = "Width:";
+            // 
+            // DisplayWidthNumeric
+            // 
+            this.DisplayWidthNumeric.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.DisplayWidthNumeric.Location = new System.Drawing.Point(54, 121);
+            this.DisplayWidthNumeric.Maximum = new decimal(new int[] {
+            1000000,
+            0,
+            0,
+            0});
+            this.DisplayWidthNumeric.Minimum = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            this.DisplayWidthNumeric.Name = "DisplayWidthNumeric";
+            this.DisplayWidthNumeric.Size = new System.Drawing.Size(116, 20);
+            this.DisplayWidthNumeric.TabIndex = 4;
+            this.DisplayWidthNumeric.Value = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            this.DisplayWidthNumeric.ValueChanged += new System.EventHandler(this.DisplayHeightNumeric_ValueChanged);
+            // 
+            // LocalViolationBoundriesCheck
+            // 
+            this.LocalViolationBoundriesCheck.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.LocalViolationBoundriesCheck.Location = new System.Drawing.Point(14, 96);
+            this.LocalViolationBoundriesCheck.Name = "LocalViolationBoundriesCheck";
+            this.LocalViolationBoundriesCheck.Size = new System.Drawing.Size(198, 19);
+            this.LocalViolationBoundriesCheck.TabIndex = 3;
+            this.LocalViolationBoundriesCheck.Text = "Local Violation Boundaries";
+            this.LocalViolationBoundriesCheck.UseVisualStyleBackColor = true;
+            this.LocalViolationBoundriesCheck.CheckedChanged += new System.EventHandler(this.LocalViolationBoundriesCheck_CheckedChanged);
+            // 
+            // FixSeedCheck
+            // 
+            this.FixSeedCheck.AutoSize = true;
+            this.FixSeedCheck.Location = new System.Drawing.Point(176, 71);
+            this.FixSeedCheck.Name = "FixSeedCheck";
+            this.FixSeedCheck.Size = new System.Drawing.Size(36, 17);
+            this.FixSeedCheck.TabIndex = 1;
+            this.FixSeedCheck.Text = "fix";
+            this.FixSeedCheck.UseVisualStyleBackColor = true;
+            // 
+            // label1
+            // 
+            this.label1.AutoSize = true;
+            this.label1.Location = new System.Drawing.Point(11, 72);
+            this.label1.Name = "label1";
+            this.label1.Size = new System.Drawing.Size(35, 13);
+            this.label1.TabIndex = 2;
+            this.label1.Text = "Seed:";
+            // 
+            // SeedNumeric
+            // 
+            this.SeedNumeric.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.SeedNumeric.Location = new System.Drawing.Point(54, 70);
+            this.SeedNumeric.Maximum = new decimal(new int[] {
+            1000000,
+            0,
+            0,
+            0});
+            this.SeedNumeric.Minimum = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            this.SeedNumeric.Name = "SeedNumeric";
+            this.SeedNumeric.Size = new System.Drawing.Size(116, 20);
+            this.SeedNumeric.TabIndex = 2;
+            this.SeedNumeric.Value = new decimal(new int[] {
+            1,
+            0,
+            0,
+            0});
+            // 
+            // StepEpochButton
+            // 
+            this.StepEpochButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.StepEpochButton.Location = new System.Drawing.Point(12, 41);
+            this.StepEpochButton.Name = "StepEpochButton";
+            this.StepEpochButton.Size = new System.Drawing.Size(200, 23);
+            this.StepEpochButton.TabIndex = 2;
+            this.StepEpochButton.Text = "Step Epoch";
+            this.StepEpochButton.UseVisualStyleBackColor = true;
+            this.StepEpochButton.Click += new System.EventHandler(this.StepEpochButton_Click);
+            // 
+            // SaveButton
+            // 
+            this.SaveButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.SaveButton.Location = new System.Drawing.Point(12, 12);
+            this.SaveButton.Name = "SaveButton";
+            this.SaveButton.Size = new System.Drawing.Size(200, 23);
+            this.SaveButton.TabIndex = 1;
+            this.SaveButton.Text = "Save";
+            this.SaveButton.UseVisualStyleBackColor = true;
+            this.SaveButton.Click += new System.EventHandler(this.SaveButton_Click);
+            // 
+            // MainForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(828, 386);
+            this.Controls.Add(this.ControlPanel);
+            this.Name = "MainForm";
+            this.Text = "M4M DenseMod";
+            this.ControlPanel.ResumeLayout(false);
+            this.ControlPanel.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.DisplayWidthNumeric)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.SeedNumeric)).EndInit();
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+
+        private VectorEditor Editor;
+        private System.Windows.Forms.Panel ControlPanel;
+        private System.Windows.Forms.Button SaveButton;
+        private System.Windows.Forms.SaveFileDialog SaveFileDialog;
+        private System.Windows.Forms.Button StepEpochButton;
+        private System.Windows.Forms.Label label1;
+        private System.Windows.Forms.NumericUpDown SeedNumeric;
+        private System.Windows.Forms.CheckBox FixSeedCheck;
+        private System.Windows.Forms.CheckBox LocalViolationBoundriesCheck;
+        private System.Windows.Forms.CheckBox FixWidthCheck;
+        private System.Windows.Forms.Label label2;
+        private System.Windows.Forms.NumericUpDown DisplayWidthNumeric;
+    }
+}
+
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Form1.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Form1.cs
new file mode 100644
index 0000000..7c54ed7
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Form1.cs
@@ -0,0 +1,233 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseMod
+{
+    public partial class MainForm : Form
+    {
+        public MainForm()
+        {
+            // VERY important
+            MathNet.Numerics.Control.UseSingleThread();
+
+            InitializeComponent();
+
+            Editor = new VectorEditor();
+            Editor.AllowDrop = true;
+            Editor.Dock = DockStyle.Fill;
+            Editor.DragDrop += Editor_DragDrop;
+            Editor.DragEnter += Editor_DragEnter;
+            Editor.MouseDown += Editor_MouseDown;
+            this.Controls.Add(Editor);
+            Editor.BringToFront();
+            Editor.DisplayWidthChanged += (o, n) => DisplayWidthNumeric.Value = n;
+        }
+
+        private PopulationExperiment<DenseIndividual> _Experiment;
+        public PopulationExperiment<DenseIndividual> Experiment
+        {
+            get => _Experiment;
+            set
+            {
+                _Experiment = value;
+                var individual = value.Population.PeekAll()[0];
+                Editor.Vector = new ObservableVector<double>(individual.Genome.InitialState.ToArray());
+            }
+        }
+
+        public int Seed
+        {
+            get => (int)SeedNumeric.Value;
+            set
+            {
+                this.Invoke(new Action(() => { SeedNumeric.Value = value; }));
+            }
+        }
+
+        public bool FixSeed
+        {
+            get => FixSeedCheck.Checked;
+            set
+            {
+                FixSeedCheck.Checked = value;
+            }
+        }
+
+        private bool _LocalViolationBoundries = false;
+        public bool LocalViolationBoundries
+        {
+            get => _LocalViolationBoundries;
+            set
+            {
+                if (value != LocalViolationBoundries)
+                {
+                    bool invalidate = false;
+
+                    _LocalViolationBoundries = value;
+                    LocalViolationBoundriesCheck.Checked = value;
+                    if (LocalMatrixBoundryAnnotator != null)
+                    {
+                        LocalMatrixBoundryAnnotator.Enabled = value;
+                        invalidate = true;
+                    }
+
+                    Editor.Invalidate();
+                }
+            }
+        }
+
+        // annotators
+        private LocalMatrixBoundryAnnotator LocalMatrixBoundryAnnotator = null; // may be null
+
+        public int NextSeed()
+        {
+            int res = Seed;
+            if (!FixSeed)
+                Seed++;
+            return res;
+        }
+
+        private void Editor_DragDrop(object sender, DragEventArgs e)
+        {
+            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+            foreach (string file in files)
+            {
+                try
+                {
+                    if (System.IO.File.Exists(file))
+                    {
+                        var fileName = System.IO.Path.GetFileName(file);
+
+                        if (fileName.StartsWith("epoch"))
+                        {
+                            Editor.ClearAnnotators();
+                            Experiment = PopulationExperiment<DenseIndividual>.Load(file);
+                            if (!FixWidthCheck.Checked)
+                                Editor.DisplayWidth = (int)Math.Ceiling(Math.Sqrt(Editor.Vector.Length));
+                            var target0 = M4M.CliPlotHelpers.UnwrapTarget(Experiment.PopulationConfig.ExperimentConfiguration.Targets[0]);
+                            if (target0 is M4M.Epistatics.CorrelationMatrixTarget cmt)
+                            {
+                                Editor.AddAnnotator(LocalMatrixBoundryAnnotator = new LocalMatrixBoundryAnnotator(cmt.CorrelationMatrix));
+                                LocalMatrixBoundryAnnotator.Enabled = LocalViolationBoundries;
+                            }
+                            else
+                            {
+                                LocalMatrixBoundryAnnotator = null;
+                            }
+                        }
+                    }
+                }
+                catch
+                {
+                    // TODO: do something
+                }
+            }
+        }
+
+        private void Editor_DragEnter(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                e.Effect = DragDropEffects.Copy;
+            }
+        }
+
+        // we'll be lazy and do everything manually for the moment
+        private void Editor_MouseDown(object sender, MouseEventArgs e)
+        {
+            if (Editor.HitTest(e.Location, out int i))
+            {
+                Editor.Vector[i] = -Editor.Vector[i];
+            }
+        }
+
+        public PopulationExperiment<DenseIndividual> PrepareModifiedExperiment()
+        {
+            int seed = NextSeed();
+            var newIs = MathNet.Numerics.LinearAlgebra.CreateVector.DenseOfArray(Editor.Vector.ToArray());
+            var drules = Experiment.PopulationConfig.ExperimentConfiguration.DevelopmentRules;
+            var context = new ModelExecutionContext(new CustomMersenneTwister(seed));
+            var individuals = Experiment.Population.PeekAll().Select(ij => DenseIndividual.Develop(ij.Genome.Clone(context, newInitialState: newIs), context, drules, ij.Epigenetic));
+            var pop = new Population<DenseIndividual>(individuals);
+            var exp = new PopulationExperiment<DenseIndividual>(pop, Experiment.PopulationConfig, Experiment.FileStuff, Experiment.Epoch, Experiment.TotalGenerationCount);
+            return exp;
+        }
+
+        private void SaveButton_Click(object sender, EventArgs e)
+        {
+            var res = SaveFileDialog.ShowDialog();
+            if (res == DialogResult.OK)
+            {
+                var exp = PrepareModifiedExperiment();
+                var filename = SaveFileDialog.FileName;
+                exp.SaveTo(filename);
+            }
+        }
+
+        private async void StepEpochButton_Click(object sender, EventArgs e)
+        {
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+
+            void start()
+            {
+                StepEpochButton.Enabled = false;
+                sw.Restart();
+            }
+
+            void go()
+            {
+                void judged(FileStuff stuff, IReadOnlyList<IndividualJudgement<DenseIndividual>> populationJudgements, int epochCount, long generationCount, ITarget target)
+                {
+                    if (sw.ElapsedMilliseconds > 100)
+                    {
+                        this.BeginInvoke(new Action(() =>
+                        {
+                            Editor.Vector = new ObservableVector<double>(populationJudgements[0].Individual.Genome.InitialState.ToArray());
+                            StepEpochButton.Text = "" + generationCount;
+                            sw.Restart();
+                        }));
+                        sw.Restart();
+                    }
+                }
+
+                var exp = PrepareModifiedExperiment();
+
+                var context = new ModelExecutionContext(new CustomMersenneTwister(NextSeed()));
+                var feedback = new PopulationExperimentFeedback<DenseIndividual>();
+                feedback.Judged.Register(judged);
+                exp.RunEpoch(context, feedback);
+
+                Experiment = exp;
+            }
+
+            void finish()
+            {
+                sw.Stop();
+                StepEpochButton.Enabled = true;
+                StepEpochButton.Text = "Step Epoch";
+            }
+
+            start();
+            await Task.Run(new Action(go));
+            finish();
+        }
+
+        private void LocalViolationBoundriesCheck_CheckedChanged(object sender, EventArgs e)
+        {
+            LocalViolationBoundries = LocalViolationBoundriesCheck.Checked;
+        }
+
+        private void DisplayHeightNumeric_ValueChanged(object sender, EventArgs e)
+        {
+            Editor.DisplayWidth = (int)DisplayWidthNumeric.Value;
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Form1.resx b/M4MCode/M4MDenseMod/M4MDenseMod/Form1.resx
new file mode 100644
index 0000000..8029080
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Form1.resx
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="SaveFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/M4MDenseMod.csproj b/M4MCode/M4MDenseMod/M4MDenseMod/M4MDenseMod.csproj
new file mode 100644
index 0000000..b0a67d4
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/M4MDenseMod.csproj
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{A4122D48-BE27-400B-9FED-98ADBA24C70F}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>M4MDenseMod</RootNamespace>
+    <AssemblyName>M4MDenseMod</AssemblyName>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="M4M.Model">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.Model.dll</HintPath>
+    </Reference>
+    <Reference Include="M4M.New">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.New.dll</HintPath>
+    </Reference>
+    <Reference Include="MathNet.Numerics, Version=4.9.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MathNet.Numerics.4.9.1\lib\net40\MathNet.Numerics.dll</HintPath>
+    </Reference>
+    <Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
+    <Reference Include="NetState">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Annotation.cs" />
+    <Compile Include="Colours.cs" />
+    <Compile Include="DoubleBufferedPanel.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="Form1.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Form1.Designer.cs">
+      <DependentUpon>Form1.cs</DependentUpon>
+    </Compile>
+    <Compile Include="ObservableVector.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="VectorEditor.cs">
+      <SubType>Component</SubType>
+    </Compile>
+    <Compile Include="VectorEditor.Designer.cs">
+      <DependentUpon>VectorEditor.cs</DependentUpon>
+    </Compile>
+    <EmbeddedResource Include="Form1.resx">
+      <DependentUpon>Form1.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+      <DesignTime>True</DesignTime>
+    </Compile>
+    <EmbeddedResource Include="VectorEditor.resx">
+      <DependentUpon>VectorEditor.cs</DependentUpon>
+    </EmbeddedResource>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/ObservableVector.cs b/M4MCode/M4MDenseMod/M4MDenseMod/ObservableVector.cs
new file mode 100644
index 0000000..4a5edbc
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/ObservableVector.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4MDenseMod
+{
+    public delegate void ObservableVectorModified(IReadOnlyList<int> changed);
+    public delegate void ObservableVectorChanged<T>(ObservableVector<T> previous, ObservableVector<T> current);
+
+    public class ObservableVector<T>
+    {
+        public ObservableVector(T[] data)
+        {
+            Data = data?.ToArray() ?? throw new ArgumentNullException(nameof(data));
+        }
+
+        public ObservableVector(int length, T value)
+        {
+            Data = new T[length];
+            for (int i = 0; i < length; i++)
+                Data[i] = value;
+        }
+
+        private T[] Data { get; }
+
+        public event ObservableVectorModified Modified;
+
+        public T this[int i]
+        {
+            get => Data[i];
+            set
+            {
+                if (i < 0 || i >= Length)
+                    throw new ArgumentOutOfRangeException(nameof(i));
+
+                if (!Data[i].Equals(value))
+                {
+                    Data[i] = value;
+                    Modified?.Invoke(new[] { i });
+                }
+            }
+        }
+
+        public T[] ToArray() => Data.ToArray();
+        public int Length => Data.Length;
+    }
+
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Program.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Program.cs
new file mode 100644
index 0000000..7a9b5fe
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MDenseMod
+{
+    static class Program
+    {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            Application.Run(new MainForm());
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Properties/AssemblyInfo.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..3a2c21e
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("M4MDenseMod")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("M4MDenseMod")]
+[assembly: AssemblyCopyright("Copyright ©  2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a4122d48-be27-400b-9fed-98adba24c70f")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.Designer.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..2d69248
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace M4MDenseMod.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("M4MDenseMod.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.resx b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.Designer.cs b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..7b1e824
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace M4MDenseMod.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.settings b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.Designer.cs b/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.Designer.cs
new file mode 100644
index 0000000..2ae0bcd
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.Designer.cs
@@ -0,0 +1,43 @@
+namespace M4MDenseMod
+{
+    partial class VectorEditor : DoubleBufferedPanel
+    {
+        /// <summary> 
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary> 
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Component Designer generated code
+
+        /// <summary> 
+        /// Required method for Designer support - do not modify 
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            // 
+            // VectorEditor
+            // 
+            this.Name = "VectorEditor";
+            this.Paint += new System.Windows.Forms.PaintEventHandler(this.VectorEditor_Paint);
+            this.Resize += new System.EventHandler(this.VectorEditor_Resize);
+            this.ResumeLayout(false);
+        }
+
+        #endregion
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.cs b/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.cs
new file mode 100644
index 0000000..d07ac57
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using MathNet.Numerics.LinearAlgebra;
+using M4M;
+
+namespace M4MDenseMod
+{
+    public delegate void FieldChanged<T>(T oldValue, T newValue);
+
+    public partial class VectorEditor : DoubleBufferedPanel
+    {
+        private ObservableVector<double> _Vector;
+        public ObservableVector<double> Vector
+        {
+            get => _Vector;
+            set
+            {
+                if (value == null)
+                    throw new ArgumentNullException(nameof(value));
+
+                if (_Vector != value)
+                {
+                    var old = _Vector;
+                    if (old != null)
+                        old.Modified -= _Vector_Modified;
+
+                    _Vector = value;
+                    _Vector.Modified += _Vector_Modified;
+                    VectorChanged?.Invoke(old, _Vector);
+                    Invalidate();
+                }
+            }
+        }
+
+        private void _Vector_Modified(IReadOnlyList<int> changed)
+        {
+            Invalidate();
+        }
+
+        public event ObservableVectorChanged<double> VectorChanged;
+        public event FieldChanged<int> DisplayWidthChanged;
+
+        private int _DisplayWidth = 1;
+        public int DisplayWidth
+        {
+            get => _DisplayWidth;
+            set
+            {
+                if (_DisplayWidth != value)
+                {
+                    var oldWidth = _DisplayWidth;
+                    _DisplayWidth = value;
+                    DisplayWidthChanged(oldWidth, value);
+                    Invalidate();
+                }
+            }
+        }
+
+        public IColours _Colours = null;
+        public IColours Colours
+        {
+            get => _Colours;
+            set
+            {
+                if (_Colours != value)
+                {
+                    _Colours = Colours;
+                    Invalidate();
+                }
+            }
+        }
+
+        public int DisplayHeight => (int)(Math.Ceiling((double)Vector.Length / DisplayWidth));
+
+        private HashSet<IVectorAnnotator> Annotators { get; } = new HashSet<IVectorAnnotator>();
+
+        public IEnumerable<IVectorAnnotator> EnumerateAnnotators()
+        {
+            return Annotators;
+        }
+
+        public void AddAnnotator(IVectorAnnotator annotator)
+        {
+            Annotators.Add(annotator);
+            Invalidate();
+        }
+
+        public void RemoveAnnotator(IVectorAnnotator annotator)
+        {
+            Annotators.Remove(annotator);
+            Invalidate();
+        }
+
+        public void ClearAnnotators()
+        {
+            Annotators.Clear();
+            Invalidate();
+        }
+
+        public VectorEditor()
+        {
+            // pointless defaults
+            _Vector = new ObservableVector<double>(16, 0.0);
+            _DisplayWidth = 4;
+            _Colours = new GreyColours();
+
+            InitializeComponent();
+        }
+
+        public VectorEditor(ObservableVector<double> vector, int displayWidth, IColours colours)
+        {
+            _Vector = vector;
+            _DisplayWidth = displayWidth;
+            _Colours = colours;
+
+            InitializeComponent();
+        }
+
+        private void VectorEditor_Load(object sender, EventArgs e)
+        {
+            Invalidate();
+        }
+
+        private int CellDim;
+        private Point TopLeft;
+
+        protected virtual void DoLayout(Rectangle clipRectangle)
+        {
+            CellDim = Math.Min(clipRectangle.Width / DisplayWidth, clipRectangle.Height / DisplayHeight);
+            TopLeft = new Point(
+                (clipRectangle.Width - CellDim * DisplayWidth) / 2,
+                (clipRectangle.Height - CellDim * DisplayHeight) / 2);
+        }
+
+        public Rectangle GetRectangle(int i)
+        {
+            int x = i % DisplayWidth;
+            int y = i / DisplayWidth;
+
+            return new Rectangle(TopLeft.X + x * CellDim, TopLeft.Y + y * CellDim, CellDim, CellDim);
+        }
+
+        public Point GetCenter(int i)
+        {
+            int x = i % DisplayWidth;
+            int y = i / DisplayWidth;
+
+            return new Point(TopLeft.X + x * CellDim + CellDim / 2, TopLeft.Y + y * CellDim + CellDim / 2);
+        }
+
+        public bool HitTest(Point p, out int index)
+        {
+            for (int i = 0; i < Vector.Length; i++)
+            {
+                if (GetRectangle(i).Contains(p))
+                {
+                    index = i;
+                    return true;
+                }
+            }
+
+            index = -1;
+            return false;
+        }
+
+        protected virtual void VectorEditor_Paint(object sender, PaintEventArgs e)
+        {
+            DoLayout(e.ClipRectangle);
+
+            var g = e.Graphics;
+
+            for (int i = 0; i < Vector.Length; i++)
+            {
+                using (var b = new SolidBrush(Colours.GetColor(Vector[i])))
+                {
+                    g.FillRectangle(b, GetRectangle(i));
+                }
+            }
+
+            foreach (var a in Annotators)
+                a.Annotate(g, e.ClipRectangle, this);
+        }
+
+        private void VectorEditor_Resize(object sender, EventArgs e)
+        {
+            Invalidate();
+        }
+    }
+}
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.resx b/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/VectorEditor.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MDenseMod/M4MDenseMod/packages.config b/M4MCode/M4MDenseMod/M4MDenseMod/packages.config
new file mode 100644
index 0000000..39ad563
--- /dev/null
+++ b/M4MCode/M4MDenseMod/M4MDenseMod/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="MathNet.Numerics" version="4.9.1" targetFramework="net452" />
+</packages>
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting.sln b/M4MCode/M4MPlotting/M4MPlotting.sln
new file mode 100644
index 0000000..cc6c097
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4MPlotting", "M4MPlotting\M4MPlotting.csproj", "{76A46FF0-E931-4004-82CD-C76C39BB24CA}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Debug|x64.ActiveCfg = Debug|x64
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Debug|x64.Build.0 = Debug|x64
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Release|x64.ActiveCfg = Release|x64
+		{76A46FF0-E931-4004-82CD-C76C39BB24CA}.Release|x64.Build.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {26575F8F-344B-4674-AB78-184C86D36C94}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/M4MPlotting/M4MPlotting/App.config b/M4MCode/M4MPlotting/M4MPlotting/App.config
new file mode 100644
index 0000000..03644c1
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/App.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+    </startup>
+</configuration>
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Form1.Designer.cs b/M4MCode/M4MPlotting/M4MPlotting/Form1.Designer.cs
new file mode 100644
index 0000000..52dffb1
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Form1.Designer.cs
@@ -0,0 +1,178 @@
+namespace M4MPlotting
+{
+    partial class PlotForm
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.TopMenuStrip = new System.Windows.Forms.MenuStrip();
+            this.FileMenu = new System.Windows.Forms.ToolStripMenuItem();
+            this.FileOpenMenu = new System.Windows.Forms.ToolStripMenuItem();
+            this.FileExportMenu = new System.Windows.Forms.ToolStripMenuItem();
+            this.pathToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.ArgsText = new System.Windows.Forms.TextBox();
+            this.ViewPanel = new System.Windows.Forms.Panel();
+            this.ErrorLabel = new System.Windows.Forms.Label();
+            this.InteractionsPanel = new System.Windows.Forms.FlowLayoutPanel();
+            this.MainOpenFileDialog = new System.Windows.Forms.OpenFileDialog();
+            this.MainSaveFileDialog = new System.Windows.Forms.SaveFileDialog();
+            this.viewLogsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.TopMenuStrip.SuspendLayout();
+            this.ViewPanel.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // TopMenuStrip
+            // 
+            this.TopMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.FileMenu});
+            this.TopMenuStrip.Location = new System.Drawing.Point(0, 0);
+            this.TopMenuStrip.Name = "TopMenuStrip";
+            this.TopMenuStrip.Size = new System.Drawing.Size(800, 24);
+            this.TopMenuStrip.TabIndex = 0;
+            this.TopMenuStrip.Text = "menuStrip1";
+            // 
+            // FileMenu
+            // 
+            this.FileMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.FileOpenMenu,
+            this.FileExportMenu,
+            this.pathToolStripMenuItem,
+            this.viewLogsToolStripMenuItem});
+            this.FileMenu.Name = "FileMenu";
+            this.FileMenu.Size = new System.Drawing.Size(37, 20);
+            this.FileMenu.Text = "File";
+            // 
+            // FileOpenMenu
+            // 
+            this.FileOpenMenu.Name = "FileOpenMenu";
+            this.FileOpenMenu.Size = new System.Drawing.Size(180, 22);
+            this.FileOpenMenu.Text = "Open";
+            this.FileOpenMenu.Click += new System.EventHandler(this.FileOpenMenu_Click);
+            // 
+            // FileExportMenu
+            // 
+            this.FileExportMenu.Name = "FileExportMenu";
+            this.FileExportMenu.Size = new System.Drawing.Size(180, 22);
+            this.FileExportMenu.Text = "Export";
+            this.FileExportMenu.Click += new System.EventHandler(this.FileExportMenu_Click);
+            // 
+            // pathToolStripMenuItem
+            // 
+            this.pathToolStripMenuItem.Name = "pathToolStripMenuItem";
+            this.pathToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
+            this.pathToolStripMenuItem.Text = "Path";
+            this.pathToolStripMenuItem.Click += new System.EventHandler(this.pathToolStripMenuItem_Click);
+            // 
+            // ArgsText
+            // 
+            this.ArgsText.Dock = System.Windows.Forms.DockStyle.Top;
+            this.ArgsText.Location = new System.Drawing.Point(0, 24);
+            this.ArgsText.Name = "ArgsText";
+            this.ArgsText.Size = new System.Drawing.Size(800, 20);
+            this.ArgsText.TabIndex = 1;
+            this.ArgsText.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ArgsText_KeyDown);
+            // 
+            // ViewPanel
+            // 
+            this.ViewPanel.Controls.Add(this.ErrorLabel);
+            this.ViewPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.ViewPanel.Location = new System.Drawing.Point(0, 44);
+            this.ViewPanel.Name = "ViewPanel";
+            this.ViewPanel.Size = new System.Drawing.Size(800, 406);
+            this.ViewPanel.TabIndex = 2;
+            // 
+            // ErrorLabel
+            // 
+            this.ErrorLabel.BackColor = System.Drawing.SystemColors.Control;
+            this.ErrorLabel.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.ErrorLabel.Location = new System.Drawing.Point(0, 0);
+            this.ErrorLabel.Name = "ErrorLabel";
+            this.ErrorLabel.Size = new System.Drawing.Size(800, 406);
+            this.ErrorLabel.TabIndex = 0;
+            this.ErrorLabel.Text = "label1";
+            this.ErrorLabel.Visible = false;
+            // 
+            // InteractionsPanel
+            // 
+            this.InteractionsPanel.AutoSize = true;
+            this.InteractionsPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+            this.InteractionsPanel.Dock = System.Windows.Forms.DockStyle.Right;
+            this.InteractionsPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
+            this.InteractionsPanel.Location = new System.Drawing.Point(800, 44);
+            this.InteractionsPanel.Name = "InteractionsPanel";
+            this.InteractionsPanel.Size = new System.Drawing.Size(0, 406);
+            this.InteractionsPanel.TabIndex = 0;
+            // 
+            // MainSaveFileDialog
+            // 
+            this.MainSaveFileDialog.AddExtension = false;
+            this.MainSaveFileDialog.DefaultExt = "pdf";
+            // 
+            // viewLogsToolStripMenuItem
+            // 
+            this.viewLogsToolStripMenuItem.Name = "viewLogsToolStripMenuItem";
+            this.viewLogsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
+            this.viewLogsToolStripMenuItem.Text = "View Logs";
+            this.viewLogsToolStripMenuItem.Click += new System.EventHandler(this.viewLogsToolStripMenuItem_Click);
+            // 
+            // PlotForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(800, 450);
+            this.Controls.Add(this.ViewPanel);
+            this.Controls.Add(this.InteractionsPanel);
+            this.Controls.Add(this.ArgsText);
+            this.Controls.Add(this.TopMenuStrip);
+            this.DoubleBuffered = true;
+            this.Name = "PlotForm";
+            this.Text = "M4M Plot Preview";
+            this.Load += new System.EventHandler(this.Form1_Load);
+            this.TopMenuStrip.ResumeLayout(false);
+            this.TopMenuStrip.PerformLayout();
+            this.ViewPanel.ResumeLayout(false);
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.MenuStrip TopMenuStrip;
+        private System.Windows.Forms.ToolStripMenuItem FileMenu;
+        private System.Windows.Forms.ToolStripMenuItem FileOpenMenu;
+        private System.Windows.Forms.TextBox ArgsText;
+        private System.Windows.Forms.Panel ViewPanel;
+        private System.Windows.Forms.OpenFileDialog MainOpenFileDialog;
+        private System.Windows.Forms.ToolStripMenuItem FileExportMenu;
+        private System.Windows.Forms.SaveFileDialog MainSaveFileDialog;
+        private System.Windows.Forms.FlowLayoutPanel InteractionsPanel;
+        private System.Windows.Forms.Label ErrorLabel;
+        private System.Windows.Forms.ToolStripMenuItem pathToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem viewLogsToolStripMenuItem;
+    }
+}
+
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Form1.cs b/M4MCode/M4MPlotting/M4MPlotting/Form1.cs
new file mode 100644
index 0000000..53ac2bf
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Form1.cs
@@ -0,0 +1,773 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using M4M;
+using OxyPlot;
+using OxyPlot.WindowsForms;
+
+namespace M4MPlotting
+{
+    public interface IPlotInteractions
+    {
+        string Key { get; }
+        void Apply(PlotForm plotForm);
+        void Disable();
+    }
+
+    public partial class PlotForm : Form
+    {
+        public OxyPlot.WindowsForms.PlotView PlotView { get; }
+        private List<IPlotInteractions> PlotInteractions { get; }
+        private List<ICliPlotter> FilePlotters { get; }
+        private CliPlot CliPlot { get; }
+        private CliMultiPlot CliMultiPlot { get; }
+        public string DataFileName { get; set; } = null;
+        private IPlotInteractions CurrentPlotInteractions = null;
+        public Panel PlotInteractionsPanel => this.InteractionsPanel;
+        public string LastError { get; private set; }
+
+        private string logs { get; set; }
+        private LogViewer logViewer;
+
+        private void UpdateLogViewer()
+        {
+            if (logViewer != null)
+            {
+                logViewer.Logs = logs;
+            }
+        }
+
+        public LogViewer GetLogViewer()
+        {
+            if (logViewer == null || logViewer.IsDisposed)
+            {
+                // to lazy to make this thread safe
+                logViewer = new LogViewer();
+                logViewer.Logs = logs;
+            }
+
+            return logViewer;
+        }
+
+        public string[] Args
+        {
+            get
+            {
+                return M4M.StringHelpers.Split(ArgsText.Text).ToArray();
+            }
+            set
+            {
+                ArgsText.Text = M4M.StringHelpers.Join(value);
+            }
+        }
+
+        private ICliPlotter DeterminePlotterForFilename(string filename)
+        {
+            string justname = System.IO.Path.GetFileName(filename);
+            return FilePlotters.Single(fp => justname.StartsWith(fp.Prefix, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        private IPlotInteractions DeterminePlotInteractionsForFilename(string filename)
+        {
+            string justname = System.IO.Path.GetFileName(filename);
+            return PlotInteractions.SingleOrDefault(fp => justname.StartsWith(fp.Key, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        public PlotForm()
+        {
+            InitializeComponent();
+            PlotView = new OxyPlot.WindowsForms.PlotView();
+            PlotView.KeyDown += PlotView_KeyDown;
+
+            FilePlotters = CliPlot.CreateDefaultPlotters().ToList();
+
+            CliPlot = new CliPlot(null, FilePlotters, CliPlot.DefaultPresets);
+            CliMultiPlot = new CliMultiPlot(CliPlot);
+
+            PlotInteractions = new List<IPlotInteractions> { new TrajectoryPlotInteractions("rcs", "epoch"), new TracePlotInteractions("wholesamples", "epoch"), new TracePlotInteractions("tracee", "generation"), new GenomePlotInteractions("genome"), new GenomePlotInteractions("epoch") };
+        }
+
+        private void PlotView_KeyDown(object sender, KeyEventArgs e)
+        {
+            if (PlotView?.Model != null)
+            {
+                if (e.KeyCode == Keys.Space)
+                {
+                    foreach (var s in PlotView.Model.Series)
+                        s.ClearSelection();
+                    PlotView.Invalidate();
+                }
+            }
+        }
+
+        private PlotModel Plot(string filename, CliParams clips)
+        {
+            var sb = new StringBuilder();
+            var console = new System.IO.StringWriter(sb);
+
+            try
+            {
+                bool multiPlot = clips.IsSet("multiplot");
+
+                string title = clips.Get("title", System.IO.Path.GetFileName(filename));
+
+                PlotModel plot;
+                if (multiPlot)
+                {
+                    if (!CliMultiPlot.PreparePlot(console, clips, filename, title, out plot))
+                    {
+                        LastError = "Unable to multi-plot";
+                        return null;
+                    }
+                }
+                else
+                {
+                    if (!CliPlot.PreparePlot(console, clips, filename, title, out plot))
+                    {
+                        LastError = "Unable to plot";
+                        return null;
+                    }
+                }
+
+                CliPlot.PreProcessPlot(clips, plot);
+
+                Cli.PrintUnobserved(console, clips);
+
+                CliPlot.PrintBasicInfo(console, plot);
+
+                LastError = null;
+                return plot;
+            }
+            catch (Exception ex)
+            {
+                LastError = ex.Message + "\n" + ex.StackTrace;
+                return null;
+            }
+            finally
+            {
+                console.Dispose();
+                var unmodified = sb.ToString();
+                logs = System.Text.RegularExpressions.Regex.Replace(unmodified, "\r?\n", Environment.NewLine);
+            }
+        }
+        
+        private async void Form1_Load(object sender, EventArgs e)
+        {
+            ViewPanel.Controls.Add(PlotView);
+            PlotView.Dock = DockStyle.Fill;
+            PlotView.BackColor = Color.White;
+
+            await ReloadOffThread();
+        }
+
+        private async Task ReloadAsync()
+        {
+            CliParams clips = new CliParams();
+            clips.Consume(Args);
+
+            if (DataFileName == null)
+            {
+                if (clips.IsSet("misc"))
+                    DataFileName = "misc";
+                else
+                    return;
+            }
+            
+            var model = await Task.Run(() => Plot(DataFileName, clips));
+            PlotView.Model = model;
+            PlotView.InvalidatePlot(true);
+
+            if (LastError == null)
+            {
+                ErrorLabel.Text = "";
+                ErrorLabel.Visible = false;
+            }
+            else
+            {
+                ErrorLabel.Text = LastError;
+                ErrorLabel.Visible = true;
+            }
+
+            CurrentPlotInteractions?.Disable();
+            CurrentPlotInteractions = DeterminePlotInteractionsForFilename(DataFileName);
+            CurrentPlotInteractions?.Apply(this);
+
+            UpdateLogViewer();
+        }
+        
+        public async Task ReloadOffThread()
+        {
+            await ReloadAsync();
+//            await Task.Run(ReloadAsync);
+        }
+
+        private async void ArgsText_KeyDown(object sender, KeyEventArgs e)
+        {
+            e.Handled = false;
+            if (e.KeyCode == Keys.Return)
+            {
+                e.Handled = true;
+                e.SuppressKeyPress = true;
+                await ReloadOffThread();
+            }
+        }
+
+        private async void FileOpenMenu_Click(object sender, EventArgs e)
+        {
+            var result = MainOpenFileDialog.ShowDialog();
+            if (result == DialogResult.OK)
+            {
+                DataFileName = MainOpenFileDialog.FileName;
+
+                await ReloadOffThread();
+            }
+        }
+
+        private void FileExportMenu_Click(object sender, EventArgs e)
+        {
+            var result = MainSaveFileDialog.ShowDialog();
+            if (result == DialogResult.OK)
+            {
+                string saveFilename = MainSaveFileDialog.FileName;
+                if (saveFilename.EndsWith(".pdf"))
+                    ExportToPdf(saveFilename.Substring(0, saveFilename.Length - 4));
+                else if (saveFilename.EndsWith(".png"))
+                    ExportToPng(saveFilename.Substring(0, saveFilename.Length - 4));
+                else
+                {
+                    // default to pdf
+                    ExportToPdf(saveFilename);
+                }
+            }
+        }
+
+        private void pathToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            MessageBox.Show(DataFileName);
+        }
+
+        public void ExportToPng(string filename)
+        {
+            var plot = PlotView.Model;
+            
+            OxyPlot.WindowsForms.PngExporter exporter = new OxyPlot.WindowsForms.PngExporter();
+            exporter.Width = PlotView.Width;
+            exporter.Height = PlotView.Height;
+        
+            filename += ".png";
+            if (System.IO.File.Exists(filename))
+                System.IO.File.Delete(filename); // something doesn't like writing onto plots apparently
+
+            using (var fs = new System.IO.FileStream(filename, System.IO.FileMode.OpenOrCreate))
+            {
+                exporter.Export(plot, fs);
+            }
+        }
+
+        public void ExportToPdf(string filename)
+        {
+            var plot = PlotView.Model;
+
+            //PlotView.Model = plot;
+            
+            OxyPlot.PdfExporter exporter = new PdfExporter();
+            exporter.Width = PlotView.Width;
+            exporter.Height = PlotView.Height;
+        
+            filename += ".pdf";
+            if (System.IO.File.Exists(filename))
+                System.IO.File.Delete(filename); // something doesn't like writing onto plots apparently
+
+            using (var fs = new System.IO.FileStream(filename, System.IO.FileMode.OpenOrCreate))
+            {
+                exporter.Export(plot, fs);
+            }
+        }
+
+        private void viewLogsToolStripMenuItem_Click(object sender, EventArgs e)
+        {
+            var lv = GetLogViewer();
+            lv.Show();
+            lv.BringToFront();
+        }
+    }
+
+    public class GenomePlotInteractions : IPlotInteractions
+    {
+        public GenomePlotInteractions(string key)
+        {
+            Key = key;
+
+            InitGenomePanel();
+        }
+
+        public string Key { get; }
+        
+        private Panel GenomePanel;
+        private RadioButton[] GenomeRadios;
+        private RadioButton DtmRadio;
+        private RadioButton IsRadio;
+        private RadioButton PsRadio;
+        private RadioButton DevRadio;
+        private RadioButton BiasRadio;
+
+        private PlotForm TargetPlotForm { get; set; }
+
+        private void InitGenomePanel()
+        {
+            GenomePanel = new Panel();
+            GenomePanel.AutoSize = true;
+
+            GenomeRadios = new RadioButton[] {
+                DtmRadio = new RadioButton() { Text = "Dtm", Checked = false, Tag = "dtm" },
+                IsRadio = new RadioButton() { Text = "Is", Checked = false, Tag = "is" },
+                PsRadio = new RadioButton() { Text = "Ps", Checked = false, Tag = "ps" },
+                DevRadio = new RadioButton() { Text = "Dev", Checked = false, Tag = "dev" },
+                BiasRadio = new RadioButton() { Text = "Bias", Checked = false, Tag = "biasvector" },
+            };
+
+            int y = 10;
+            int x = 10;
+
+            foreach (var r in GenomeRadios)
+            {
+                GenomePanel.Controls.Add(r);
+                r.AutoSize = true;
+                r.Location = new Point(x, y);
+                y += 20 + 5;
+                r.CheckedChanged += CheckedChanged;
+            }
+        }
+
+        public void Apply(PlotForm plotForm)
+        {
+            bool changed = TargetPlotForm != plotForm;
+
+            Disable();
+            
+            TargetPlotForm = plotForm;
+            TargetPlotForm.PlotInteractionsPanel.Controls.Add(GenomePanel);
+
+            if (changed)
+                Configure();
+        }
+
+        public void Disable()
+        {
+            if (TargetPlotForm != null)
+            {
+                if (GenomePanel != null && TargetPlotForm.PlotInteractionsPanel.Controls.Contains(GenomePanel))
+                    TargetPlotForm.PlotInteractionsPanel.Controls.Remove(GenomePanel);
+                TargetPlotForm = null;
+            }
+        }
+
+        private async void Configure()
+        {
+            List<string> args = TargetPlotForm.Args.ToList();
+
+            bool refresh = false;
+            
+            foreach (var gr in GenomeRadios)
+            {
+                var thing = "thing=" + (string)gr.Tag;
+                if (gr.Checked)
+                {
+                    if (!args.Contains(thing))
+                    {
+                        args.Add(thing);
+                        refresh = true;
+                    }
+                }
+                else
+                {
+                    if (args.Contains(thing))
+                    {
+                        args.Remove(thing);
+                        refresh = true;
+                    }
+                }
+            }
+
+            if (refresh)
+            {
+                TargetPlotForm.Args = args.ToArray();
+                await TargetPlotForm.ReloadOffThread();
+            }
+        }
+        
+        private void CheckedChanged(object sender, EventArgs e)
+        {
+            Configure();
+        }
+    }
+
+    public class TracePlotInteractions : TrajectoryPlotInteractions
+    {
+        public TracePlotInteractions(string key, string timeType) : base(key, timeType)
+        {
+            InitGenomePanel();
+            InitTracePanel();
+        }
+
+        private void InitGenomePanel()
+        {
+            GenomePanel = new Panel();
+            GenomePanel.AutoSize = true;
+
+            GenomeRadios = new RadioButton[] {
+                DtmRadio = new RadioButton() { Text = "Dtm", Checked = false, Tag = "dtm" },
+                IsRadio = new RadioButton() { Text = "Is", Checked = false, Tag = "is" },
+                PsRadio = new RadioButton() { Text = "Ps", Checked = false, Tag = "ps" },
+                DevRadio = new RadioButton() { Text = "Dev", Checked = false, Tag = "dev" },
+                BiasRadio = new RadioButton() { Text = "Bias", Checked = false, Tag = "biasvector" },
+            };
+
+            int y = 10;
+            int x = 10;
+
+            foreach (var r in GenomeRadios)
+            {
+                GenomePanel.Controls.Add(r);
+                r.Location = new Point(x, y);
+                y += 20 + 5;
+                r.CheckedChanged += CheckedChanged;
+            }
+        }
+
+        private void InitTracePanel()
+        {
+            TracePanel = new Panel();
+            TracePanel.AutoSize = true;
+
+            TraceCheckBoxes = new CheckBox[] {
+                RegCoefsCheck = new CheckBox() { Text = "RegCoefs", Checked = false, Tag="regcoefs=l" },
+                PTraitsCheck = new CheckBox() { Text = "PTraits", Checked = false, Tag="ptraits=l" },
+                GTraitsCheck = new CheckBox() { Text = "GTraits", Checked = false, Tag="gtraits=l" },
+                FitnessCheck = new CheckBox() { Text = "Fitness", Checked = true, Tag="fitness=r" },
+                HuskynessCheck = new CheckBox() { Text = "Huskyness", Checked = false, Tag="huskyness=l" }
+            };
+
+            int y = 10;
+            int x = 10;
+
+            foreach (var c in TraceCheckBoxes)
+            {
+                TracePanel.Controls.Add(c);
+                c.Location = new Point(x, y);
+                y += 20 + 5;
+                c.CheckedChanged += CheckedChanged;
+            }
+        }
+
+        private void CheckedChanged(object sender, EventArgs e)
+        {
+            Configure();
+        }
+
+        private Panel TracePanel;
+        private CheckBox[] TraceCheckBoxes;
+        private CheckBox RegCoefsCheck;
+        private CheckBox PTraitsCheck;
+        private CheckBox GTraitsCheck;
+        private CheckBox FitnessCheck;
+        private CheckBox HuskynessCheck;
+
+        private Panel GenomePanel;
+        private RadioButton[] GenomeRadios;
+        private RadioButton DtmRadio;
+        private RadioButton IsRadio;
+        private RadioButton PsRadio;
+        private RadioButton DevRadio;
+        private RadioButton BiasRadio;
+
+        private PlotForm TargetPlotForm { get; set; }
+
+        public override void Apply(PlotForm plotForm)
+        {
+            base.Apply(plotForm);
+
+            bool changed = TargetPlotForm != plotForm;
+
+            if (changed)
+                Disable(false);
+
+            TargetPlotForm = plotForm;
+            if (plotForm.Args.Any(a => a.StartsWith(TimeType + "=")))
+            {
+                TargetPlotForm.PlotInteractionsPanel.Controls.Add(GenomePanel);
+            }
+            else
+            {
+                TargetPlotForm.PlotInteractionsPanel.Controls.Add(TracePanel);
+            }
+
+            if (changed)
+                Configure();
+        }
+
+        public override void Disable()
+        {
+            Disable(true);
+        }
+
+        private void Disable(bool baseToo)
+        {
+            if (baseToo)
+                base.Disable();
+
+            if (TargetPlotForm != null)
+            {
+                if (TracePanel != null && TargetPlotForm.PlotInteractionsPanel.Controls.Contains(TracePanel))
+                    TargetPlotForm.PlotInteractionsPanel.Controls.Remove(TracePanel);
+                if (GenomePanel != null && TargetPlotForm.PlotInteractionsPanel.Controls.Contains(GenomePanel))
+                    TargetPlotForm.PlotInteractionsPanel.Controls.Remove(GenomePanel);
+                TargetPlotForm = null;
+            }
+        }
+
+        private async void Configure()
+        {
+            List<string> args = TargetPlotForm.Args.ToList();
+
+            bool refresh = false;
+
+            if (!FitnessCheck.Checked)
+            {
+                if (!args.Contains("fitness="))
+                {
+                    args.Add("fitness=");
+                    refresh = true;
+                }
+            }
+            else
+            {
+                if (args.Contains("fitness="))
+                {
+                    args.Remove("fitness=");
+                    refresh = true;
+                }
+            }
+
+            if (RegCoefsCheck.Checked)
+            {
+                if (!args.Contains("regcoefs=l"))
+                {
+                    args.Add("regcoefs=l");
+                    refresh = true;
+                }
+            }
+            else
+            {
+                if (args.Contains("regcoefs=l"))
+                {
+                    args.Remove("regcoefs=l");
+                    refresh = true;
+                }
+            }
+
+            if (GTraitsCheck.Checked)
+            {
+                if (!args.Contains("gtraits=l"))
+                {
+                    args.Add("gtraits=l");
+                    refresh = true;
+                }
+            }
+            else
+            {
+                if (args.Contains("gtraits=l"))
+                {
+                    args.Remove("gtraits=l");
+                    refresh = true;
+                }
+            }
+
+            if (PTraitsCheck.Checked)
+            {
+                if (!args.Contains("ptraits=l"))
+                {
+                    args.Add("ptraits=l");
+                    refresh = true;
+                }
+            }
+            else
+            {
+                if (args.Contains("ptraits=l"))
+                {
+                    args.Remove("ptraits=l");
+                    refresh = true;
+                }
+            }
+
+            if (HuskynessCheck.Checked)
+            {
+                if (!args.Contains("huskyness=l"))
+                {
+                    args.Add("huskyness=l");
+                    refresh = true;
+                }
+            }
+            else
+            {
+                if (args.Contains("huskyness=l"))
+                {
+                    args.Remove("huskyness=l");
+                    refresh = true;
+                }
+            }
+
+            foreach (var gr in GenomeRadios)
+            {
+                var thing = "thing=" + (string)gr.Tag;
+                if (gr.Checked)
+                {
+                    if (!args.Contains(thing))
+                    {
+                        args.Add(thing);
+                        refresh = true;
+                    }
+                }
+                else
+                {
+                    if (args.Contains(thing))
+                    {
+                        args.Remove(thing);
+                        refresh = true;
+                    }
+                }
+            }
+
+            if (refresh)
+            {
+                TargetPlotForm.Args = args.ToArray();
+                await TargetPlotForm.ReloadOffThread();
+            }
+        }
+    }
+
+    public delegate void TrajectoryClick(int epoch, int lineIndex);
+
+    public class TrajectoryPlotInteractions : IPlotInteractions
+    {
+        public string Key { get; }
+        public string TimeType { get; }
+        private PlotView TargetPlotView { get; set; }
+        private PlotForm TargetPlotForm { get; set; }
+        private PlotForm _subordinatePlotForm = null;
+        public TrajectoryClick TrajectoryClick { get; set; }
+        public bool UseClickWindow { get; set; } = true;
+
+        public TrajectoryPlotInteractions(string key, string timeType)
+        {
+            Key = key;
+            TimeType = timeType;
+        }
+
+        private PlotForm SubordinatePlotForm
+        {
+            get
+            {
+                if (_subordinatePlotForm == null)
+                {
+                    _subordinatePlotForm = new PlotForm();
+                    _subordinatePlotForm.Visible = true;
+                    _subordinatePlotForm.FormClosed += _subordinatePlotForm_FormClosed;
+                }
+
+                return _subordinatePlotForm;
+            }
+        }
+
+        private void _subordinatePlotForm_FormClosed(object sender, FormClosedEventArgs e)
+        {
+            _subordinatePlotForm = null;
+        }
+
+        public virtual void Apply(PlotForm plotForm)
+        {
+            Disable();
+
+            TargetPlotForm = plotForm;
+            TargetPlotView = TargetPlotForm.PlotView;
+            foreach (var series in TargetPlotView.Model.Series)
+                series.MouseDown += TrajectoryPlotInteractions_MouseDown;
+        }
+
+        public virtual void Disable()
+        {
+            if (TargetPlotView != null)
+            {
+                foreach (var series in TargetPlotView.Model.Series)
+                    series.MouseDown -= TrajectoryPlotInteractions_MouseDown;
+
+                TargetPlotForm = null;
+                TargetPlotView = null;
+            }
+        }
+
+        private async void TrajectoryPlotInteractions_MouseDown(object sender, OxyMouseDownEventArgs e)
+        {
+            try
+            {
+                if (e.ChangedButton != OxyMouseButton.Left)
+                {
+                    return;
+                }
+
+                var series = (OxyPlot.Series.LineSeries)sender;
+                var screenPoint = e.Position;
+                var hitResult = series.GetNearestPoint(screenPoint, false);
+                if (hitResult != null)
+                {
+                    int epoch = (int)hitResult.DataPoint.X;
+                    int index = series.Tag as int? ?? 0;
+
+                    TrajectoryClick?.Invoke(epoch, index);
+
+                    if (UseClickWindow)
+                    {
+                        int n = (int)Math.Sqrt(TargetPlotView.Model.Series.Count);
+                        int j = index % n;
+                        int i = index / n;
+
+                        foreach (var s in TargetPlotView.Model.Series)
+                            s.ClearSelection();
+                        series.Select();
+
+                        TargetPlotView.InvalidatePlot(false);
+
+                        var clips = new CliParams();
+                        clips.Consume(TargetPlotForm.Args);
+                        Cli.LoadParamFiles(clips);
+                        clips.Consume(SubordinatePlotForm.Args);
+                        Cli.LoadParamFiles(clips);
+
+                        clips.Set($"{TimeType}", $"{epoch}");
+                        clips.Set($"title", $"{index}{TimeType}{epoch}");
+
+                        SubordinatePlotForm.DataFileName = TargetPlotForm.DataFileName;
+                        SubordinatePlotForm.Visible = true;
+                        SubordinatePlotForm.Args = clips.ToStringArray();
+                        SubordinatePlotForm.DataFileName = TargetPlotForm.DataFileName;
+                        e.Handled = true;
+                        SubordinatePlotForm.PlotView.Model = null;
+                        await SubordinatePlotForm.ReloadOffThread();
+                        SubordinatePlotForm.PlotView.Model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = j });
+                        SubordinatePlotForm.PlotView.Model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = i });
+                        SubordinatePlotForm.PlotView.InvalidatePlot(false);
+                    }
+                }
+            }
+            catch
+            {
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Form1.resx b/M4MCode/M4MPlotting/M4MPlotting/Form1.resx
new file mode 100644
index 0000000..6dbaa76
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Form1.resx
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="TopMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+  <metadata name="MainOpenFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>126, 17</value>
+  </metadata>
+  <metadata name="MainSaveFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>276, 17</value>
+  </metadata>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting/LogViewer.Designer.cs b/M4MCode/M4MPlotting/M4MPlotting/LogViewer.Designer.cs
new file mode 100644
index 0000000..052c77d
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/LogViewer.Designer.cs
@@ -0,0 +1,61 @@
+namespace M4MPlotting
+{
+    partial class LogViewer
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.LogText = new System.Windows.Forms.TextBox();
+            this.SuspendLayout();
+            // 
+            // LogText
+            // 
+            this.LogText.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.LogText.Location = new System.Drawing.Point(0, 0);
+            this.LogText.Multiline = true;
+            this.LogText.Name = "LogText";
+            this.LogText.ReadOnly = true;
+            this.LogText.Size = new System.Drawing.Size(494, 450);
+            this.LogText.TabIndex = 0;
+            // 
+            // LogViewer
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(494, 450);
+            this.Controls.Add(this.LogText);
+            this.Name = "LogViewer";
+            this.Text = "LogViewer";
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        #endregion
+
+        private System.Windows.Forms.TextBox LogText;
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting/LogViewer.cs b/M4MCode/M4MPlotting/M4MPlotting/LogViewer.cs
new file mode 100644
index 0000000..ae78993
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/LogViewer.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MPlotting
+{
+    public partial class LogViewer : Form
+    {
+        public string Logs
+        {
+            get
+            {
+                return LogText.Text;
+            }
+            set
+            {
+                LogText.Text = value;
+            }
+        }
+
+        public LogViewer()
+        {
+            InitializeComponent();
+        }
+    }
+}
diff --git a/M4MCode/M4MPlotting/M4MPlotting/LogViewer.resx b/M4MCode/M4MPlotting/M4MPlotting/LogViewer.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/LogViewer.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting/M4MPlotting.csproj b/M4MCode/M4MPlotting/M4MPlotting/M4MPlotting.csproj
new file mode 100644
index 0000000..417d1bf
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/M4MPlotting.csproj
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{76A46FF0-E931-4004-82CD-C76C39BB24CA}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>M4MPlotting</RootNamespace>
+    <AssemblyName>M4MPlotting</AssemblyName>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>m4m.ico</ApplicationIcon>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>6</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <LangVersion>6</LangVersion>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="M4M.Model">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.Model.dll</HintPath>
+    </Reference>
+    <Reference Include="M4M.New">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.New.dll</HintPath>
+    </Reference>
+    <Reference Include="MathNet.Numerics, Version=4.9.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MathNet.Numerics.4.9.1\lib\net40\MathNet.Numerics.dll</HintPath>
+    </Reference>
+    <Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
+    <Reference Include="NetState">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot, Version=2.0.0.0, Culture=neutral, PublicKeyToken=638079a8f0bd61e9, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.Core.2.0.0\lib\net45\OxyPlot.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot.WindowsForms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=245eacd6b5d2d338, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.WindowsForms.2.0.0\lib\net45\OxyPlot.WindowsForms.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Deployment" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Form1.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Form1.Designer.cs">
+      <DependentUpon>Form1.cs</DependentUpon>
+    </Compile>
+    <Compile Include="LogViewer.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="LogViewer.Designer.cs">
+      <DependentUpon>LogViewer.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <EmbeddedResource Include="Form1.resx">
+      <DependentUpon>Form1.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="LogViewer.resx">
+      <DependentUpon>LogViewer.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="m4m.ico" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Program.cs b/M4MCode/M4MPlotting/M4MPlotting/Program.cs
new file mode 100644
index 0000000..30482ea
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Program.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace M4MPlotting
+{
+    static class Program
+    {
+        /// <summary>
+        /// The main entry point for the application.
+        /// </summary>
+        [STAThread]
+        static void Main(string[] args)
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            var form = new PlotForm();
+            if (args.Length == 1)
+            {
+                form.DataFileName = args[0];
+            }
+            else if (args.Length > 1)
+            {
+                form.DataFileName = args[0];
+                form.Args = args.Skip(1).ToArray();
+            }
+            Application.Run(form);
+        }
+    }
+}
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Properties/AssemblyInfo.cs b/M4MCode/M4MPlotting/M4MPlotting/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..64b2078
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("M4MPlotting")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("M4MPlotting")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("76a46ff0-e931-4004-82cd-c76c39bb24ca")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.Designer.cs b/M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..f961b77
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace M4MPlotting.Properties
+{
+
+
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("M4MPlotting.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.resx b/M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.Designer.cs b/M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..2058a7f
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace M4MPlotting.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.settings b/M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>
diff --git a/M4MCode/M4MPlotting/M4MPlotting/m4m.ico b/M4MCode/M4MPlotting/M4MPlotting/m4m.ico
new file mode 100644
index 0000000000000000000000000000000000000000..b16a7b74bd114e419183ccf1188fe6e3c6d0cb3d
GIT binary patch
literal 45451
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

literal 0
HcmV?d00001

diff --git a/M4MCode/M4MPlotting/M4MPlotting/packages.config b/M4MCode/M4MPlotting/M4MPlotting/packages.config
new file mode 100644
index 0000000..ac173f3
--- /dev/null
+++ b/M4MCode/M4MPlotting/M4MPlotting/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="MathNet.Numerics" version="4.9.1" targetFramework="net452" />
+  <package id="OxyPlot.Core" version="2.0.0" targetFramework="net452" />
+  <package id="OxyPlot.WindowsForms" version="2.0.0" targetFramework="net452" />
+</packages>
\ No newline at end of file
diff --git a/M4MCode/M4M_Ded/M4M_Ded.CoreRunner/M4M_Ded.CoreRunner.csproj b/M4MCode/M4M_Ded/M4M_Ded.CoreRunner/M4M_Ded.CoreRunner.csproj
new file mode 100644
index 0000000..30af2a2
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded.CoreRunner/M4M_Ded.CoreRunner.csproj
@@ -0,0 +1,42 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <Content Remove="Info/version.txt" />
+    <Content Remove="Info/buildinfo.txt" />
+  </ItemGroup>
+  <Target Name="UpdateVersion" BeforeTargets="BeforeBuild" Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <Exec Command="dotnet ../../M4M_MkI/BuildTools/AutoVersion.dll info Info/buildinfo.txt Info/version.txt" ContinueOnError="WarnAndContinue" />
+  </Target>
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\M4M_Ded\M4M_Ded.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="M4M.Model">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.Model.dll</HintPath>
+    </Reference>
+    <Reference Include="M4M.New">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.New.dll</HintPath>
+    </Reference>
+    <Reference Include="NetState">
+      <HintPath>..\..\..\NetState\NetState\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <EmbeddedResource Include="Info/version.txt" />
+    <EmbeddedResource Include="Info/buildinfo.txt" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MathNet.Numerics" Version="4.9.1" />
+    <PackageReference Include="OxyPlot.Core" Version="2.0.0" />
+  </ItemGroup>
+  
+</Project>
diff --git a/M4MCode/M4M_Ded/M4M_Ded.CoreRunner/Program.cs b/M4MCode/M4M_Ded/M4M_Ded.CoreRunner/Program.cs
new file mode 100644
index 0000000..84d9960
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded.CoreRunner/Program.cs
@@ -0,0 +1,253 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M_Ded.CoreRunner
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+//args = @"m4mded ResetFitnessTraces -expfile C:\M4MExperiments\Cs\CsRiVaryQNoDelta\CsRiVaryQNoDelta\CsRiVaryQNoDeltaQ10k\r0\epoch2000save.dat count=10 out=fitnessTraces.dat".Split(' ');
+
+            var clips = new CliParams();
+            clips.Consume(args);
+
+            if (!clips.IsSet("quiet"))
+            {
+                Console.WriteLine("~~ M4M Ded Core Running ~~");
+            }
+
+            if (clips.IsSet("AreaTrajectoryTraces"))
+            {
+                AreaTrajectoryTraces(clips);
+            }
+
+            if (clips.IsSet("ResetFitnessTraces"))
+            {
+                ResetFitnessTraces(clips);
+            }
+
+            if (clips.IsSet("SatBlock"))
+            {
+                SatBlock(clips);
+            }
+
+            if (clips.IsSet("RunonBlock"))
+            {
+                RunonBlock(clips);
+            }
+
+            bool doNotListUnobserved = clips.IsSet("quiet") || clips.IsSet("donotlistunobserved");
+            if (!doNotListUnobserved)
+            {
+                var unobserved = clips.EnumerateUnobsered().ToArray();
+                if (unobserved.Length > 0)
+                    Console.WriteLine("Unobserved CliParams: " + string.Join(", ", unobserved));
+            }
+        }
+
+        public static void AreaTrajectoryTraces(CliParams clips)
+        {
+            var tracesFiles = clips.Get("AreaTrajectoryTraces", s => s.Split(';'));
+            var labels = clips.Get("label", s => s.Split(';'));
+            var colors = clips.Get("color", s => s.Split(';').Select(CliPlotHelpers.ParseColour).ToArray());
+            var title = clips.Get("title");
+
+            double? ymin = clips.Get("ymin", s => (double?)double.Parse(s), null);
+            double? ymax = clips.Get("ymax", s => (double?)double.Parse(s), null);
+            
+            string xAxisKey = clips.Get("xaxiskey", "x");
+            string yAxisKey = clips.Get("yaxiskey", "y");
+
+            bool logX = clips.Get("logX", bool.Parse, false);
+            bool logY = clips.Get("logY", bool.Parse, false);
+
+            var plot = TrajectoryPlotting.PrepareTrajectoriesPlot(title, "Generation", $"(Qualify title:{yAxisKey})", null, ymin, ymax, xAxisKey, yAxisKey, null, logX, logY, false);
+
+            var cts = new ColourfulTraces[tracesFiles.Length];
+            for (int i = 0; i < tracesFiles.Length; i++)
+            {
+                var traj = Analysis.LoadTrajectories(tracesFiles[i], out var samplePeriod);
+                cts[i] = new ColourfulTraces(labels[i], colors[i], traj, samplePeriod, 0);
+                EvolvabilityTraces.PlotArea(plot, cts[i], 1);
+            }
+
+            SimplePdfPlotExporter.ExportToPdf(plot, "plot.pdf", 300, 300, false, false, null);
+        }
+
+        public static void ResetFitnessTraces(CliParams clips)
+        {
+            var expFile = clips.Get("ExpFile");
+            var count = clips.Get("Count", int.Parse, 1000);
+            var epochCount = clips.Get("EpochCount", int.Parse, 1);
+            var seed = clips.Get("seed", int.Parse, 1);
+            var outFile = clips.Get("out");
+            var saturate = clips.Get("saturate", bool.Parse, false);
+            var saturateMin = clips.Get("saturatemin", double.Parse, -1.0);
+            var saturateThreshold = clips.Get("saturatethreshold", double.Parse, 0.0);
+            var saturateMax = clips.Get("saturatemax", double.Parse, 1.0);
+
+            var rand = new CustomMersenneTwister(seed);
+            var context = new ModelExecutionContext(rand);
+
+            var trajectories = PopulationExperimentHelpers.LoadUnknownType(expFile, new TracesRunnner(count, epochCount, context, saturate, saturateMin, saturateThreshold, saturateMax));
+            Analysis.SaveTrajectories(outFile, trajectories, 1);
+        }
+
+        class TracesRunnner : IUnknownPopulationExperimentTypeReceiver<double[][]>
+        {
+            public TracesRunnner(int resetCount, int epochCount, ModelExecutionContext context, bool saturate, double saturationMin, double saturationThreshold, double saturationMax)
+            {
+                ResetCount = resetCount;
+                EpochCount = epochCount;
+                Context = context;
+                Saturate = saturate;
+                SaturationMin = saturationMin;
+                SaturationThreshold = saturationThreshold;
+                SaturationMax = saturationMax;
+            }
+
+            public int ResetCount { get; }
+            public int EpochCount { get; }
+            public ModelExecutionContext Context { get; }
+            public bool Saturate { get; }
+            public double SaturationMin { get; }
+            public double SaturationThreshold { get; }
+            public double SaturationMax { get; }
+
+            public double[][] Receive<TIndividual>(PopulationExperiment<TIndividual> populationExperiment) where TIndividual : IIndividual<TIndividual>
+            {
+                var config = populationExperiment.PopulationConfig.ExperimentConfiguration;
+
+                IEnumerable<Population<TIndividual>> populations()
+                {
+                    for (int i = 0; i < ResetCount; i++)
+                    {
+                        var pop = populationExperiment.Population.Clone();
+                        populationExperiment.PopulationConfig.PopulationResetOperation.Reset(Context, pop, config.InitialStateResetRange, config.DevelopmentRules, config.ReproductionRules, config.Targets[0]);
+                        yield return pop;
+                    }
+                }
+
+                ITarget currentTarget = null;
+                SaturationTarget satTarget = null;
+                
+                double judge(ITarget target, IReadOnlyList<IndividualJudgement<TIndividual>> individualJudgements)
+                {
+                    if (Saturate)
+                    {
+                        if (currentTarget != target)
+                        {
+                            currentTarget = target;
+                            satTarget = new SaturationTarget(target, SaturationMin, SaturationThreshold, SaturationMax);
+                        }
+
+                        return individualJudgements.Max(ij => ij.Individual.Judge(config.JudgementRules, satTarget).CombinedFitness);
+                    }
+                    else
+                    {
+                        return individualJudgements.Max(ij => ij.Judgement.CombinedFitness);
+                    }
+                }
+
+                return PopulationTrace.TracesToTrajectories(Context, populationExperiment.Clone(null), populations(), judge, EpochCount);
+            }
+        }
+
+        public static void SatBlock(CliParams clips)
+        {
+            var dir = clips.Get("SatBlock");
+            var groupName = clips.Get("GroupName");
+            var satExpFile = clips.Get("SatExpFile");
+            var wholesampleFilename = clips.Get("WholesampleFilename");
+            var blockTitle = clips.Get("BlockTitle");
+            var projectionMode = clips.Get("ProjectionMode", CliProject.ParseProjectionMode, ProjectionMode.EvalOnly);
+            var saturateExistingTarget = clips.Get("SaturateExistingTarget", bool.Parse, false);
+
+            SatBlock(dir.Split(';'), groupName.Split(';'), satExpFile, wholesampleFilename, blockTitle, projectionMode, saturateExistingTarget);
+        }
+
+        // TODO: this shuold really be broken up into one task that produces the saturated wholesamples, and the other tasks which do the work
+        // These could appear as a CliBlockAnalysis class or something (err... 2 of them are plotters...)
+        public static void SatBlock(IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string satExpFile, string wholesampleFilename, string blockTitle, ProjectionMode projectionMode, bool saturateExistingTarget)
+        {
+            if (dirs.Count == 0)
+                throw new ArgumentException("Must provide atleast one Dir and GroupName");
+            if (dirs.Count != groupNames.Count)
+                throw new ArgumentException("Must provide the same number of Dirs and groupNames");
+
+            var sats = new List<ExpSamples>();
+
+            for (int i = 0; i < dirs.Count; i++)
+            {
+                var sed = new SaturatedExperimentData(dirs[i], groupNames[i], satExpFile, wholesampleFilename, projectionMode, saturateExistingTarget);
+                sats.AddRange(sed.Sats.OrderBy(e => e.ExpInfo.Block));
+                SaturatedExperimentData.ProcessWholeSamples(sats, sed.SatExp, null);
+            }
+
+            var traceesplot = GroupPlotting.PlotBockTracees(sats);
+            SimplePdfPlotExporter.ExportToPdf(traceesplot, "SatTracees.pdf", 300, 300, false, false, null);
+
+            var boxplot = GroupPlotting.PlotTerminalFitnessDistributions(sats, blockTitle, null);
+            SimplePdfPlotExporter.ExportToPdf(boxplot, "SatBoxPlots.pdf", 300, 300, false, false, null);
+        }
+
+        public static void RunonBlock(CliParams clips)
+        {
+            var dir = clips.Get("RunonBlock");
+            var groupName = clips.Get("GroupName");
+            var runonExpFile = clips.Get("RunonExpFile");
+            var expFilename = clips.Get("ExpFilename");
+            var blockTitle = clips.Get("BlockTitle");
+            var postfix = clips.Get("Postfix");
+
+            var width = clips.Get("width", double.Parse, 600);
+            var height = clips.Get("height", double.Parse, 300);
+
+            double[] thresholds = clips.Get("threshold", s => s.Split(';').Select(double.Parse).ToArray(), new double[0]);
+
+            //
+            var runons = RunonBlock(dir.Split(';'), groupName.Split(';'), runonExpFile, expFilename, postfix);
+
+            var boxplot = GroupPlotting.PlotMeanFitnessDistributions(runons, blockTitle, null);
+            foreach (var threshold in thresholds)
+            {
+                boxplot.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = threshold, Color = OxyPlot.OxyColors.Gray });
+            }
+            SimplePdfPlotExporter.ExportToPdf(boxplot, "RunonBoxPlots.pdf", width, height, false, false, null);
+
+            foreach (var threshold in thresholds)
+            {
+                Console.WriteLine($"# Threshold {threshold}");
+
+                foreach (var exp in runons.GroupBy(e => e.ExpInfo.Block).OrderBy(ct => ct.Key))
+                {
+                    var passRate = (double)exp.Count(ws => ws.Samples.Average(s => s.Judgements[0].Judgement.Benefit) >= threshold) / exp.Count();
+                    Console.WriteLine(exp.Key + ": " + passRate);
+                }
+
+                Console.WriteLine();
+            }
+        }
+
+        public static List<ExpSamples> RunonBlock(IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string runonExpFile, string expFilename, string postfix)
+        {
+            if (dirs.Count == 0)
+                throw new ArgumentException("Must provide atleast one Dir and GroupName");
+            if (dirs.Count != groupNames.Count)
+                throw new ArgumentException("Must provide the same number of Dirs and groupNames");
+
+            var runons = new List<ExpSamples>();
+
+            for (int i = 0; i < dirs.Count; i++)
+            {
+                var runon = new RunonExperimentData(dirs[i], groupNames[i], runonExpFile, expFilename, postfix);
+                runons.AddRange(runon.Runons.OrderBy(e => e.ExpInfo.Block));
+            }
+
+            return runons;
+        }
+    }
+}
diff --git a/M4MCode/M4M_Ded/M4M_Ded.sln b/M4MCode/M4M_Ded/M4M_Ded.sln
new file mode 100644
index 0000000..a3f41c4
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29025.244
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M_Ded", "M4M_Ded\M4M_Ded.csproj", "{6BA34296-D465-4A61-9B07-06DEEBCED1A3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M_Ded.CoreRunner", "M4M_Ded.CoreRunner\M4M_Ded.CoreRunner.csproj", "{455D2650-340F-4396-91E3-7C696A86196C}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{6BA34296-D465-4A61-9B07-06DEEBCED1A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6BA34296-D465-4A61-9B07-06DEEBCED1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6BA34296-D465-4A61-9B07-06DEEBCED1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6BA34296-D465-4A61-9B07-06DEEBCED1A3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{455D2650-340F-4396-91E3-7C696A86196C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{455D2650-340F-4396-91E3-7C696A86196C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{455D2650-340F-4396-91E3-7C696A86196C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{455D2650-340F-4396-91E3-7C696A86196C}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {0FA6CC8C-95D2-4AAA-9000-A39DF815914D}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/M4M_Ded/M4M_Ded/ExpInfo.cs b/M4MCode/M4M_Ded/M4M_Ded/ExpInfo.cs
new file mode 100644
index 0000000..7719fdc
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded/ExpInfo.cs
@@ -0,0 +1,51 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace M4M_Ded
+{
+    public class ExpSamples
+    {
+        public ExpSamples(ExpInfo expInfo, IReadOnlyList<WholeSample<DenseIndividual>> samples, string comment)
+        {
+            ExpInfo = expInfo ?? throw new ArgumentNullException(nameof(expInfo));
+            Samples = samples ?? throw new ArgumentNullException(nameof(samples));
+            Comment = comment;
+        }
+
+        public ExpInfo ExpInfo { get; }
+        public IReadOnlyList<M4M.WholeSample<M4M.DenseIndividual>> Samples { get; }
+        public string Comment { get; }
+    }
+
+    public class ExpInfo
+    {
+        public ExpInfo(string dir, string block, string run, int repeat)
+        {
+            Dir = dir ?? throw new ArgumentNullException(nameof(dir));
+            Block = block;
+            Run = run;
+            Repeat = repeat;
+        }
+
+        public string Dir { get; }
+        public string Block { get; }
+        public string Run { get; }
+        public int Repeat { get; }
+
+        public static IEnumerable<ExpInfo> EnumerateExps(string dir, string name, string filePattern)
+        {
+            var files = System.IO.Directory.GetFiles(dir, filePattern, System.IO.SearchOption.AllDirectories);
+            foreach (var f in files)
+            {
+                var block = Regex.Match(f, $@"(?<={name})[^\\/]+(?=runs)").Groups[0].Value;
+                var run = Regex.Match(f, $@"(?<={block}runs)[^\\/]+[\\/]").Groups[0].Value;
+                var repeat = int.Parse(Regex.Match(f, @"(?<=[\\/]r)\d+(?=[\\/])").Groups[0].Value);
+
+                yield return new ExpInfo(System.IO.Path.GetDirectoryName(f), block, run, repeat);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_Ded/M4M_Ded/GroupPlotting.cs b/M4MCode/M4M_Ded/M4M_Ded/GroupPlotting.cs
new file mode 100644
index 0000000..b0e2089
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded/GroupPlotting.cs
@@ -0,0 +1,88 @@
+using M4M;
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M_Ded
+{
+
+    public class GroupPlotting
+    {
+        public static PlotModel PlotBockTracees(IEnumerable<ExpSamples> exps)
+        {
+            var cts = MakeBlockTracees(exps);
+            var plot = M4M.TrajectoryPlotting.PrepareTrajectoriesPlot("Fitness Trajectories", "Epoch", "Fitness", null, 0, 500, "x", "y", null, false, false, false);
+            var multiPlot = new PlotModel();
+            foreach (var ct in cts.OrderBy(ct => ct.Key))
+                M4M.EvolvabilityTraces.PlotArea(plot, ct.Value, 1);
+            return plot;
+        }
+
+        public static PlotModel PlotTerminalFitnessDistributions(IEnumerable<ExpSamples> exps, string blockTitle, Func<string, string> blockFormatter)
+        {
+            blockFormatter = blockFormatter ?? (x => x);
+
+            var plot = new PlotModel() { Title = "Terminal Fitness Distributions" };
+            var caxis = new CategoryAxis() { Title = blockTitle, Position = AxisPosition.Bottom };
+            plot.Axes.Add(caxis);
+            plot.Axes.Add(new LinearAxis() { Title = "Terminal Fitness", Position = AxisPosition.Left });
+
+            var bp = new BoxPlotSeries();
+            int i = 0;
+            foreach (var exp in exps.GroupBy(e => e.ExpInfo.Block).OrderBy(ct => ct.Key))
+            {
+                caxis.Labels.Add(blockFormatter(exp.Key));
+                var bi = MiscPlotting.Box(i++, exp.Select(ws => ws.Samples.Last().Judgements[0].Judgement.Benefit));
+                bp.Items.Add(bi);
+            }
+            //bp.BoxWidth = 0.8;
+            plot.Series.Add(bp);
+
+            return plot;
+        }
+
+        public static PlotModel PlotMeanFitnessDistributions(IEnumerable<ExpSamples> exps, string blockTitle, Func<string, string> blockFormatter)
+        {
+            blockFormatter = blockFormatter ?? (x => x);
+
+            var plot = new PlotModel() { Title = "Mean Fitness Distributions" };
+            var caxis = new CategoryAxis() { Title = blockTitle, Position = AxisPosition.Bottom };
+            plot.Axes.Add(caxis);
+            plot.Axes.Add(new LinearAxis() { Title = "Mean Fitness", Position = AxisPosition.Left });
+
+            var bp = new BoxPlotSeries();
+            int i = 0;
+            foreach (var exp in exps.GroupBy(e => e.ExpInfo.Block).OrderBy(ct => ct.Key))
+            {
+                caxis.Labels.Add(blockFormatter(exp.Key));
+                var bi = MiscPlotting.Box(i++, exp.Select(ws => ws.Samples.Average(s => s.Judgements[0].Judgement.Benefit)));
+                bp.Items.Add(bi);
+            }
+            //bp.BoxWidth = 0.8;
+            plot.Series.Add(bp);
+
+            return plot;
+        }
+
+        static Dictionary<string, M4M.ColourfulTraces> MakeBlockTracees(IEnumerable<ExpSamples> exps)
+        {
+            var grps = exps.GroupBy(e => e.ExpInfo.Block).ToList();
+            var dict = new Dictionary<string, M4M.ColourfulTraces>();
+            var colors = new M4M.TrajectoryColours1D(OxyColors.LightBlue, OxyColors.Red).Colours(grps.Count);
+
+            int ci = 0;
+            foreach (var grp in grps)
+            {
+                int sr = grp.First().Samples[1].Epoch - grp.First().Samples[0].Epoch;
+                var samples = grp.Select(r => r.Samples.Select(ws => ws.Judgements[0].Judgement.Benefit).ToArray()).ToList();
+                var ct = new M4M.ColourfulTraces($"Q={grp.Key}", colors[ci++], samples, sr, 0);
+                dict.Add(grp.Key, ct);
+            }
+
+            return dict;
+        }
+    }
+}
diff --git a/M4MCode/M4M_Ded/M4M_Ded/M4M_Ded.csproj b/M4MCode/M4M_Ded/M4M_Ded/M4M_Ded.csproj
new file mode 100644
index 0000000..ff4f3f8
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded/M4M_Ded.csproj
@@ -0,0 +1,39 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <Content Remove="Info/version.txt" /> 
+    <Content Remove="Info/buildinfo.txt" /> 
+  </ItemGroup>
+  <Target Name="UpdateVersion" BeforeTargets="BeforeBuild" Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <Exec Command="dotnet ../../M4M_MkI/BuildTools/AutoVersion.dll info Info/buildinfo.txt Info/version.txt" ContinueOnError="WarnAndContinue" />
+  </Target>
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="M4M.Model">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.Model.dll</HintPath>
+    </Reference>
+    <Reference Include="M4M.New">
+      <HintPath>..\..\M4M_MkI\M4M.New\bin\Release\netstandard2.0\M4M.New.dll</HintPath>
+    </Reference>
+    <Reference Include="NetState">
+      <HintPath>..\..\..\NetState\NetState\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <EmbeddedResource Include="Info/version.txt" />
+    <EmbeddedResource Include="Info/buildinfo.txt" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MathNet.Numerics" Version="4.9.1" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" />
+    <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0007" />
+    <PackageReference Include="OxyPlot.Core" Version="2.0.0" />
+  </ItemGroup>
+
+</Project>
diff --git a/M4MCode/M4M_Ded/M4M_Ded/MiscPlotting.cs b/M4MCode/M4M_Ded/M4M_Ded/MiscPlotting.cs
new file mode 100644
index 0000000..350f609
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded/MiscPlotting.cs
@@ -0,0 +1,43 @@
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M_Ded
+{
+    public class MiscPlotting
+    {
+        public static BoxPlotItem Box(double x, IEnumerable<double> samples)
+        {
+            var sorted = samples.OrderBy(s => s).ToArray();
+
+            var lq = MathNet.Numerics.Statistics.ArrayStatistics.LowerQuartileInplace(sorted);
+            var uq = MathNet.Numerics.Statistics.ArrayStatistics.UpperQuartileInplace(sorted);
+            var median = MathNet.Numerics.Statistics.ArrayStatistics.MedianInplace(sorted);
+
+            var lt = lq - (median - lq) * 1.5;
+            var ut = uq + (uq - median) * 1.5;
+
+            var low = Math.Min(samples.Where(s => s >= lt).Min(), lq);
+            var high = Math.Max(samples.Where(s => s <= ut).Max(), uq);
+
+            var bi = new BoxPlotItem(x, low, lq, median, uq, high);
+
+            foreach (var o in sorted.Where(s => s < low || s > high))
+                bi.Outliers.Add(o);
+
+            return bi;
+        }
+
+        public static double Median(IReadOnlyList<double> samples)
+        {
+            int c = samples.Count;
+            if (c % 2 == 1)
+                return samples[c / 2];
+            else
+                return (samples[c / 2 - 1] + samples[c / 2]) / 2.0;
+        }
+    }
+}
diff --git a/M4MCode/M4M_Ded/M4M_Ded/RunonAnalysis.cs b/M4MCode/M4M_Ded/M4M_Ded/RunonAnalysis.cs
new file mode 100644
index 0000000..7b85af8
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded/RunonAnalysis.cs
@@ -0,0 +1,66 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M_Ded
+{
+    public class RunonExperimentData
+    {
+        public RunonExperimentData(string dir, string groupName, string runonExpFile, string expFileName, string postfix)
+        {
+            Dir = dir;
+            GroupName = groupName;
+
+            RunonExp = M4M.PopulationExperiment<M4M.DenseIndividual>.Load(runonExpFile);
+            Exps = ExpInfo.EnumerateExps(dir, groupName, expFileName).ToList();
+            Runons = EnumerateRunon(Exps, RunonExp, expFileName, postfix).ToArray();
+        }
+
+        public string Dir { get; }
+        public string GroupName { get; }
+
+        public PopulationExperiment<DenseIndividual> RunonExp { get; }
+        public IReadOnlyList<ExpInfo> Exps { get; }
+        public ExpSamples[] Runons { get; }
+
+        public static IEnumerable<ExpSamples> EnumerateRunon(IEnumerable<ExpInfo> exps, M4M.PopulationExperiment<M4M.DenseIndividual> runonExp, string expFileName, string postfix)
+        {
+            var rand = new M4M.CustomMersenneTwister(1);
+            var context = new M4M.ModelExecutionContext(rand);
+
+            int i = 0;
+            foreach (var e in exps)
+            {
+                var runonDir = System.IO.Path.Combine(e.Dir, "runon" + postfix);
+                if (!System.IO.Directory.Exists(runonDir))
+                    System.IO.Directory.CreateDirectory(runonDir);
+
+                var runonWholesampleFile = System.IO.Path.Combine(runonDir, "wholesampleseRunon.dat");
+                if (System.IO.File.Exists(runonWholesampleFile))
+                {
+                    var wsSat = M4M.WholeSample<M4M.DenseIndividual>.LoadWholeSamples(runonWholesampleFile);
+                    yield return new ExpSamples(e, wsSat, "runon");
+                }
+                else
+                {
+                    var unRunonExpFile = System.IO.Path.Combine(e.Dir, expFileName);
+                    var unRunonExp = PopulationExperiment<DenseIndividual>.Load(unRunonExpFile);
+
+                    var exp = PopulationExperimentRunners.PrepareExperiment(unRunonExp.Population, runonExp.PopulationConfig, runonDir, disableAllIO: true, appendTimestamp: false);
+                    var feedback = new WholeSampleFeedback<DenseIndividual>(new PopulationExperimentFeedback<DenseIndividual>(), 1, false);
+                    var cliPrefix = $"{i}: ";
+                    i++;
+
+                    PopulationExperimentRunners.RunEpochs(System.Console.Out, cliPrefix, runonExp.PopulationConfig.ExperimentConfiguration.Epochs, context, exp, feedback.Feedback, -1, -1, nosave: true);
+                    var wsRunon = feedback.WholeSamples;
+
+                    exp.Save("runon", false);
+                    WholeSample<M4M.DenseIndividual>.SaveWholeSamples(runonWholesampleFile, wsRunon);
+                    yield return new ExpSamples(e, wsRunon, "runon");
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_Ded/M4M_Ded/SaturationAnalysis.cs b/M4MCode/M4M_Ded/M4M_Ded/SaturationAnalysis.cs
new file mode 100644
index 0000000..72d796e
--- /dev/null
+++ b/M4MCode/M4M_Ded/M4M_Ded/SaturationAnalysis.cs
@@ -0,0 +1,109 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace M4M_Ded
+{
+    public class SaturatedExperimentData
+    {
+        public SaturatedExperimentData(string dir, string groupName, string satExpFile, string wholesampleFilename, ProjectionMode projectionMode, bool saturateExistingTarget)
+        {
+            Dir = dir;
+            GroupName = groupName;
+            ProjectionMode = projectionMode;
+
+            SatExp = M4M.PopulationExperiment<M4M.DenseIndividual>.Load(satExpFile);
+            Exps = ExpInfo.EnumerateExps(dir, groupName, wholesampleFilename + ".dat").ToList();
+            Sats = EnumerateSaturated(Exps, SatExp, wholesampleFilename, projectionMode, saturateExistingTarget).ToArray();
+        }
+
+        public string Dir { get; }
+        public string GroupName { get; }
+        public ProjectionMode ProjectionMode { get; }
+
+        public PopulationExperiment<DenseIndividual> SatExp { get; }
+        public IReadOnlyList<ExpInfo> Exps { get; }
+        public ExpSamples[] Sats { get; }
+
+        public static PopulationExperiment<T> ReplaceTargets<T>(PopulationExperiment<T> exp, ITarget[] targets, FileStuff fileStuff) where T : IIndividual<T>
+        {
+            var popConfig = exp.PopulationConfig;
+            var config = exp.PopulationConfig.ExperimentConfiguration;
+            var newConfig = new ExperimentConfiguration(config.Size, targets, config.TargetCycler, config.Epochs, config.GenerationsPerTargetPerEpoch, config.InitialStateResetProbability, config.InitialStateResetRange, config.ShuffleTargets, config.DevelopmentRules, config.ReproductionRules, config.JudgementRules);
+            var newPopConfig = new PopulationExperimentConfig<T>(newConfig, popConfig.SelectorPreparer, popConfig.EliteCount, popConfig.HillclimberMode, popConfig.PopulationEndTargetOperation, popConfig.PopulationResetOperation, popConfig.CustomPopulationSpinner);
+            var newExp = new PopulationExperiment<T>(exp.Population, newPopConfig, fileStuff);
+            return newExp;
+        }
+
+        public static PopulationExperiment<T> Saturate<T>(PopulationExperiment<T> exp, ITarget[] targets,  double min, double threshold, double max, FileStuff fileStuff) where T : IIndividual<T>
+        {
+            var satTargets = targets.Select(t => new SaturationTarget(t, min, threshold, max)).ToArray();
+            return ReplaceTargets(exp, satTargets, fileStuff);
+        }
+
+        public static IEnumerable<ExpSamples> EnumerateSaturated(IEnumerable<ExpInfo> exps, M4M.PopulationExperiment<M4M.DenseIndividual> satExp, string wholesampleFilename, ProjectionMode projectionMode, bool saturateExistingTarget)
+        {
+            var rand = new M4M.CustomMersenneTwister(1);
+            var context = new M4M.ModelExecutionContext(rand);
+            IWholeSampleProjector<DenseIndividual> projector = saturateExistingTarget
+                ? null 
+                : new M4M.BasicExtractorWholeSampleProjectorPreparer(projectionMode, false).PrepareProjector(satExp);
+
+            foreach (var e in exps)
+            {
+                var satPath = System.IO.Path.Combine(e.Dir, wholesampleFilename + "sat.dat");
+                if (System.IO.File.Exists(satPath))
+                {
+                    var wsSat = M4M.WholeSample<M4M.DenseIndividual>.LoadWholeSamples(satPath);
+                    yield return new ExpSamples(e, wsSat, "saturated");
+                }
+                else
+                {
+                    var unSatPath = System.IO.Path.Combine(e.Dir, wholesampleFilename + ".dat");
+                    var ws = M4M.WholeSample<M4M.DenseIndividual>.LoadWholeSamples(unSatPath);
+
+                    if (saturateExistingTarget)
+                    {
+                        var targets = new[] { ws.Last().Target };
+                        var satTargetTemplate = (SaturationTarget)satExp.PopulationConfig.ExperimentConfiguration.Targets[0];
+                        var fileStuff = FileStuff.CreateNow(e.Dir, "", "", false, false);
+                        var exp = Saturate(satExp, targets, satTargetTemplate.Min, satTargetTemplate.Threshold, satTargetTemplate.Max, fileStuff);
+                        projector = new M4M.BasicExtractorWholeSampleProjectorPreparer(projectionMode, false).PrepareProjector(exp);
+                        exp.Save("sat", false);
+                        Console.WriteLine("SaturateExistingTarget: " + exp.FileStuff.OutDir);
+                    }
+
+                    var wsSat = M4M.WholeSampleProjectionHelpers.Project(System.Console.Out, context, ws, projector, false, false);
+                    M4M.WholeSample<M4M.DenseIndividual>.SaveWholeSamples(satPath, wsSat);
+                    yield return new ExpSamples(e, wsSat, "saturated");
+                }
+            }
+        }
+
+        public static void ProcessWholeSamples(IEnumerable<ExpSamples> sats, M4M.PopulationExperiment<M4M.DenseIndividual> satExp, Func<string, string> blockFormatter)
+        {
+            blockFormatter = blockFormatter ?? (x => x);
+
+            var satTarget = (M4M.SaturationTarget)satExp.PopulationConfig.ExperimentConfiguration.Targets[0];
+
+            if (satTarget.Target is M4M.Epistatics.CorrelationMatrixTarget cmTarget)
+            {
+                var bMax = cmTarget.CorrelationMatrix.Enumerate().Count(x => x != 0.0);
+
+                foreach (var e in sats)
+                {
+                    var last = e.Samples.Last();
+                    var bt = last.Judgements[0].Judgement.Benefit;
+                    var flatLine = e.Samples.AsEnumerable().Reverse().TakeWhile(s => s.Judgements[0].Judgement.Benefit == bt);
+                    Console.WriteLine($"{blockFormatter(e.ExpInfo.Block)}/runs{e.ExpInfo.Run}/r{e.ExpInfo.Repeat}: {last.Epoch - flatLine.Last().Epoch} epochs at {bt}/{bMax}");
+                }
+            }
+            else if (satTarget.Target is M4M.Epistatics.IIvmcProperTarget ivmcProperTarget)
+            {
+                // forget it
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/.tours/enumandstructstateproviders.tour b/M4MCode/M4M_MkI/.tours/enumandstructstateproviders.tour
new file mode 100644
index 0000000..6a93387
--- /dev/null
+++ b/M4MCode/M4M_MkI/.tours/enumandstructstateproviders.tour
@@ -0,0 +1,151 @@
+{
+  "$schema": "https://aka.ms/codetour-schema",
+  "title": "StateProvisionTour",
+  "steps": [
+    {
+      "file": "M4M.Model/Hebbian/Hebbian.cs",
+      "description": "Enums do not have code associated with them, so we must register a serialiser for them.",
+      "line": 8
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Here we register an `AutoEnumStateProvider` provider for our enum.\r\n\r\nNote that it supplies the type of the enum (`int`) and its state provider (`IntStateProvider`)",
+      "line": 137,
+      "selection": {
+        "start": {
+          "line": 137,
+          "character": 61
+        },
+        "end": {
+          "line": 137,
+          "character": 82
+        }
+      }
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Here we register a provider for an external type: we don't control the `Vector<T>` class, so we register a custom provider for it as well.",
+      "line": 128
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "This slightly nutty indirection allows us to pick up a provider for the element type when we resolve the provider for the vector. This `Auto` type can, therefore, be used for `Vector<double>` or `Vector<int>` in theory. In practise, we only use the `double` variant.",
+      "line": 16
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "Here is the actual implementation of the state provider for vectors.",
+      "line": 24
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "Here we receive the element provider from the `Auto` indirection.",
+      "line": 26
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "This class is both a provider and a provider factory. This is OK because the provider has no state, so it can just return itself.",
+      "line": 33
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "The `ReadCreate` method creates a new `Vector`. In this case, it reads the length and initialises it as a dense vector.",
+      "line": 48
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "The `ReadState` method then reads the actual state.\r\n\r\nThe reading and writing is done in these two stages (create/state) because state may include circular references: this way, we ensure that an object has been created (and so has an ID and object) before anyone else tries to use it.",
+      "line": 57
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Here we register a provider for a custom struct. `NetState` can't do these automatically (can't remember why, probably lazyness on my part), so we have to register them manually.",
+      "line": 132
+    },
+    {
+      "file": "M4M.Model/Model.cs",
+      "description": "Here is the struct in question. It is very simple. Note the comment telling us where to find the state provider.",
+      "line": 1460
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "Here it is.\r\n\r\nNote that it is an `IStateProvider`, not an `IClassStateProvider`, so there is no need for the create/state seperation. This means that there are no constraints on what you can stuff into a readonly struct, so long as you don't try to initialise them from a create method.",
+      "line": 227
+    },
+    {
+      "file": "M4M.Model/State/StateProviders.cs",
+      "description": "See how we use the `StreamRw` to read directly from the stream for convienience.\r\n\r\nThis is what the providers for other primitives do. Those primitives are defined in the `NetState` project.",
+      "line": 233
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Observe that we registered these things in an `AutoGraphStateProviderTable`.",
+      "line": 123,
+      "selection": {
+        "start": {
+          "line": 123,
+          "character": 38
+        },
+        "end": {
+          "line": 123,
+          "character": 65
+        }
+      }
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "This is where we use the table.",
+      "line": 104
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "We register the table with the `gsi` before anything else.",
+      "line": 114
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Just below, we register the `AutoSoftStateProvider`, which takes cares of most classes.",
+      "line": 117
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Classes that use the Soft-state auto serialises need this attribute.",
+      "line": 15
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Most properties (don't use fields) will use the `SimpleStatePropertyAttribute` attribute, which represents a non-deprecated property.",
+      "line": 31,
+      "selection": {
+        "start": {
+          "line": 31,
+          "character": 18
+        },
+        "end": {
+          "line": 31,
+          "character": 46
+        }
+      }
+    },
+    {
+      "file": "M4M.Model/Model.cs",
+      "description": "State classes need a parameterless constructor. This can be private or protected (protected is usually better), and should be marked as obsolete so that it isn't used by accident.",
+      "line": 2799
+    },
+    {
+      "file": "M4M.Model/Model.cs",
+      "description": "See how the properties also have private setters, even though we would like them to be readonly. This is necessary to deal with circular references, and simplifies the implementation of the soft-state provision considerabbly.",
+      "line": 2762
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "Deprecated properties should be marked with one of these. This tells soft-state to not export it, and not complain if it isn't present.",
+      "line": 43
+    },
+    {
+      "file": "M4M.Model/State/GraphSerialisation.cs",
+      "description": "This concludes the tour on serialisation.\r\n\r\nKeep everything simple, and everything should be OK.\r\n\r\nIf you ever have to write a custom serialiser, test as quickly as possible, because a desync between read/write will break everything and be virtually impossible to diagnose.",
+      "line": 69
+    }
+  ]
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/.vscode/launch.json b/M4MCode/M4M_MkI/.vscode/launch.json
new file mode 100644
index 0000000..2abed67
--- /dev/null
+++ b/M4MCode/M4M_MkI/.vscode/launch.json
@@ -0,0 +1,70 @@
+{
+   // Use IntelliSense to find out which attributes exist for C# debugging
+   // Use hover for the description of the existing attributes
+   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+   "version": "0.2.0",
+   "configurations": [
+    {
+        "name": ".NET Core Launch (Release Prompt)",
+        "type": "coreclr",
+        "request": "launch",
+        "preLaunchTask": "build_release",
+        // If you have changed target frameworks, make sure to update the program path.
+        "program": "${workspaceFolder}/M4M.CoreRunner/bin/Release/netcoreapp2.1/M4M.CoreRunner.dll",
+        "args": ["prompt"],
+        "cwd": "${workspaceFolder}/M4M.CoreRunner",
+        // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
+        "console": "integratedTerminal",
+        "stopAtEntry": false,
+        "internalConsoleOptions": "openOnSessionStart"
+    },
+    {
+        "name": ".NET Core Launch (Release)",
+        "type": "coreclr",
+        "request": "launch",
+        "preLaunchTask": "build_release",
+        // If you have changed target frameworks, make sure to update the program path.
+        "program": "${workspaceFolder}/M4M.CoreRunner/bin/Release/netcoreapp2.1/M4M.CoreRunner.dll",
+        "args": [],
+        "cwd": "${workspaceFolder}/M4M.CoreRunner",
+        // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
+        "console": "integratedTerminal",
+        "stopAtEntry": false,
+        "internalConsoleOptions": "openOnSessionStart"
+    },
+    {
+        "name": ".NET Core Launch (Debug Prompt)",
+        "type": "coreclr",
+        "request": "launch",
+        "preLaunchTask": "build",
+        // If you have changed target frameworks, make sure to update the program path.
+        "program": "${workspaceFolder}/M4M.CoreRunner/bin/Debug/netcoreapp2.1/M4M.CoreRunner.dll",
+        "args": ["prompt"],
+        "cwd": "${workspaceFolder}/M4M.CoreRunner",
+        // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
+        "console": "integratedTerminal",
+        "stopAtEntry": false,
+        "internalConsoleOptions": "openOnSessionStart"
+    },
+    {
+        "name": ".NET Core Launch (console)",
+        "type": "coreclr",
+        "request": "launch",
+        "preLaunchTask": "build",
+        // If you have changed target frameworks, make sure to update the program path.
+        "program": "${workspaceFolder}/M4M.CoreRunner/bin/Debug/netcoreapp2.1/M4M.CoreRunner.dll",
+        "args": [],
+        "cwd": "${workspaceFolder}/M4M.CoreRunner",
+        // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
+        "console": "internalConsole",
+        "stopAtEntry": false,
+        "internalConsoleOptions": "openOnSessionStart"
+    },
+    {
+        "name": ".NET Core Attach",
+        "type": "coreclr",
+        "request": "attach",
+        "processId": "${command:pickProcess}"
+    }
+    ,]
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/.vscode/tasks.json b/M4MCode/M4M_MkI/.vscode/tasks.json
new file mode 100644
index 0000000..21cface
--- /dev/null
+++ b/M4MCode/M4M_MkI/.vscode/tasks.json
@@ -0,0 +1,27 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "build_release",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "--configuration",
+                "release",
+                "${workspaceFolder}/M4M.CoreRunner/M4M.CoreRunner.csproj"
+            ],
+            "problemMatcher": "$msCompile"
+        },
+        {
+            "label": "build",
+            "command": "dotnet",
+            "type": "process",
+            "args": [
+                "build",
+                "${workspaceFolder}/M4M.CoreRunner/M4M.CoreRunner.csproj"
+            ],
+            "problemMatcher": "$msCompile"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/BuildTools/AutoVersion.dll b/M4MCode/M4M_MkI/BuildTools/AutoVersion.dll
new file mode 100644
index 0000000000000000000000000000000000000000..187fa8d69ef2ac01c41d2c53436dc956ac9a5898
GIT binary patch
literal 8704
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
Y%GET+29P-xA5bkwW2;7uAABJI0EsgNNB{r;

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/BuildTools/AutoVersion.runtimeconfig.json b/M4MCode/M4M_MkI/BuildTools/AutoVersion.runtimeconfig.json
new file mode 100644
index 0000000..ba4a521
--- /dev/null
+++ b/M4MCode/M4M_MkI/BuildTools/AutoVersion.runtimeconfig.json
@@ -0,0 +1,10 @@
+{
+  "runtimeOptions": {
+    "tfm": "netcoreapp2.1",
+    "framework": {
+      "name": "Microsoft.NETCore.App",
+      "version": "2.1.0",
+      "rollForward": "Minor"
+    }
+  }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.Benchmarks/DenseGenomeBenchmarks.cs b/M4MCode/M4M_MkI/M4M.Benchmarks/DenseGenomeBenchmarks.cs
new file mode 100644
index 0000000..4974a95
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Benchmarks/DenseGenomeBenchmarks.cs
@@ -0,0 +1,47 @@
+using BenchmarkDotNet.Attributes;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+
+namespace M4M.Benchmarks
+{
+    [MemoryDiagnoser]
+    public class DenseGenomeBenchmarks
+    {
+        [Params(/*4,*/ 16, 64, 256)]
+        public int Size;
+
+        [Params(0, 1, 10)]
+        public int T;
+
+        [Params(false)]
+        public bool ParallelMath;
+
+        public Matrix<double> BMatrix { get; set; }
+        public DenseGenome Genome { get; set; }
+        public ModelExecutionContext Context { get; set; }
+        public DevelopmentRules DRules { get; set; }
+
+        [GlobalSetup]
+        public void Initialize()
+        {
+            var rnd = new Random(1);
+            BMatrix = CreateMatrix.Dense<double>(Size, Size, (i, j) => rnd.NextDouble() * 2.0 - 1.0);
+            Genome = DenseGenome.CreateDefaultGenome(Size, customDevelopmentStep: new DefaultDevelopmentStep(Size, 0.0));
+            Genome.CopyOverTransMat(BMatrix);
+
+            Context = new ModelExecutionContext(null);
+            DRules = TypicalConfiguration.CreateStandardDevelopmentRules(DevelopmentRules.TanhHalf, T);
+
+            if (ParallelMath)
+                MathNet.Numerics.Control.UseMultiThreading();
+            else
+                MathNet.Numerics.Control.UseSingleThread();
+        }
+
+        [Benchmark]
+        public void Dev()
+        {
+            Genome.Develop(Context, DRules);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Benchmarks/M4M.Benchmarks.csproj b/M4MCode/M4M_MkI/M4M.Benchmarks/M4M.Benchmarks.csproj
new file mode 100644
index 0000000..8044eb9
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Benchmarks/M4M.Benchmarks.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp3.1;net5</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\M4M.New\M4M.New.csproj" />
+    <ProjectReference Include="..\M4M\M4M.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NetState">
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MathNet.Numerics" Version="4.9.1" />
+    <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
+  </ItemGroup>
+
+</Project>
diff --git a/M4MCode/M4M_MkI/M4M.Benchmarks/Program.cs b/M4MCode/M4M_MkI/M4M.Benchmarks/Program.cs
new file mode 100644
index 0000000..77e79fe
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Benchmarks/Program.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using BenchmarkDotNet;
+using BenchmarkDotNet.Running;
+
+namespace M4M.Benchmarks
+{
+    public class Program
+    {
+        static void Main(string[] args)
+        {
+            var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+            var switcher = new BenchmarkSwitcher(assembly);
+            switcher.Run(args);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Benchmarks/RegularisationBenchmarks.cs b/M4MCode/M4M_MkI/M4M.Benchmarks/RegularisationBenchmarks.cs
new file mode 100644
index 0000000..cfedcc5
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Benchmarks/RegularisationBenchmarks.cs
@@ -0,0 +1,97 @@
+using BenchmarkDotNet.Attributes;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Linq;
+
+namespace M4M.Benchmarks
+{
+    [MemoryDiagnoser]
+    public class RegularisationBenchmarks
+    {
+        [Params(4, 16, 64, 256)]
+        public int Size;
+
+        public Matrix<double> BMatrix { get; set; }
+        public DenseGenome Genome { get; set; }
+        private Vector<double> Ones { get; set; }
+        public double R;
+
+        [GlobalSetup]
+        public void Initialize()
+        {
+            var rnd = new Random(1);
+            BMatrix = CreateMatrix.Dense<double>(Size, Size, (i, j) => rnd.NextDouble() * 2.0 - 1.0);
+            Genome = DenseGenome.CreateDefaultGenome(Size);
+            Genome.CopyOverTransMat(BMatrix);
+            Ones = CreateVector.Dense(Size, 1.0);
+        }
+
+        [Benchmark]
+        public void L1_RegHelpers()
+        {
+            R = RegularisationHelpers.L1Sum(BMatrix);
+        }
+
+        [Benchmark]
+        public void L1_FPRowCandidate()
+        {
+            R = RegularisationHelpers.RowL1Sum(BMatrix);
+        }
+
+        [Benchmark]
+        public void L1_FPColCandidate()
+        {
+            R = RegularisationHelpers.ColL1Sum(BMatrix);
+        }
+
+        //[Benchmark]
+        //public void L1_Linq()
+        //{
+        //    R = BMatrix.Enumerate().Sum(Math.Abs);
+        //}
+
+        [Benchmark]
+        public void L1_UncachedMul()
+        {
+            R = BMatrix.PointwiseAbs().LeftMultiply(Ones).DotProduct(Ones);
+        }
+
+        //[Benchmark]
+        //public void L2_RegHelpers()
+        //{
+        //    R = RegularisationHelpers.L2Sum(BMatrix);
+        //}
+
+        //[Benchmark]
+        //public void L2_FPCandidate()
+        //{
+        //    double d = 0.0;
+
+        //    // fast path arrays from dense storage
+        //    if (BMatrix.Storage is MathNet.Numerics.LinearAlgebra.Storage.DenseColumnMajorMatrixStorage<double> dcmms)
+        //    {
+        //        var data = dcmms.Data;
+        //        for (int i = 0; i < data.Length; i++)
+        //        {
+        //            var v = data[i];
+        //            d += v * v;
+        //        }
+        //    }
+
+        //    R = d;
+        //}
+
+
+        //[Benchmark]
+        //public void L2_Linq()
+        //{
+        //    R = BMatrix.Enumerate().Sum(x => x * x);
+        //}
+
+        //[Benchmark]
+        //public void L2_UncachedMul()
+        //{
+        //    R = BMatrix.PointwiseMultiply(BMatrix).LeftMultiply(Ones).DotProduct(Ones);
+        //}
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj b/M4MCode/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj
new file mode 100644
index 0000000..4d2c9b3
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj
@@ -0,0 +1,39 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <Content Remove="Info/version.txt" />
+    <Content Remove="Info/buildinfo.txt" />
+  </ItemGroup>
+  <Target Name="UpdateVersion" BeforeTargets="BeforeBuild" Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <!-- NOTE: need netcoreapp2.1 for build tools -->
+    <Exec Command="dotnet ../BuildTools/AutoVersion.dll info Info/buildinfo.txt Info/version.txt" ContinueOnError="WarnAndContinue" />
+  </Target>
+
+  <PropertyGroup Condition="'$(M4M_DUAL_TARGET)' == 'true'">
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp3.1;net5</TargetFrameworks>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(M4M_DUAL_TARGET)' != 'true'">
+    <!-- This is so that the CI and other boxes with only netcore2.1 can cope. -->
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.1</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\M4M.Model\M4M.Model.csproj" />
+    <ProjectReference Include="..\M4M.New\M4M.New.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NetState">
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <EmbeddedResource Include="Info/version.txt" />
+    <EmbeddedResource Include="Info/buildinfo.txt" />
+  </ItemGroup>
+
+</Project>
diff --git a/M4MCode/M4M_MkI/M4M.CoreRunner/Program.cs b/M4MCode/M4M_MkI/M4M.CoreRunner/Program.cs
new file mode 100644
index 0000000..403d103
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.CoreRunner/Program.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Linq;
+
+namespace M4M.CoreRunner
+{
+    public class Program
+    {
+        private static void ConfigureCulture()
+        {
+            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
+        }
+
+        private static void ConfigureMathNet(bool quiet, bool parallelMath)
+        {
+            if (parallelMath)
+                MathNet.Numerics.Control.UseMultiThreading();
+            else
+                MathNet.Numerics.Control.UseSingleThread();
+
+            if (MathNet.Numerics.Control.TryUseNativeOpenBLAS() && !quiet)
+                Console.WriteLine("OpenBLAS");
+        }
+
+        static void Main(string[] args)
+        {
+            bool quiet = args.Contains("quiet");
+            bool parallelMath = args.Contains("parallelMath");
+
+            if (!quiet)
+                Console.WriteLine("~~ M4M Core Running ~~");
+            ConfigureCulture();
+            ConfigureMathNet(quiet, parallelMath);
+            
+            Cli cli = Cli.PrepareDefaultCli();
+            CliPrompt cliPrompt = new CliPrompt(cli);
+            cliPrompt.Run(System.Console.Out, args);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Analysis.cs b/M4MCode/M4M_MkI/M4M.Model/Analysis.cs
new file mode 100644
index 0000000..251df88
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Analysis.cs
@@ -0,0 +1,622 @@
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using static M4M.Misc;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using static M4M.Analysis;
+using System.Diagnostics;
+
+namespace M4M
+{
+    public class SavedGenome
+    {
+        public SavedGenome(int epoch, DenseGenome genome, GenomeDistributionInfo gdi)
+        {
+            Epoch = epoch;
+            Genome = genome;
+            GenomeDistibutionInfo = gdi;
+        }
+
+        public int Epoch { get; }
+        public DenseGenome Genome { get; }
+        public GenomeDistributionInfo GenomeDistibutionInfo { get; }
+    }
+
+    public class ModuleAnalysis
+    {
+        public Linear.Matrix<double> Region { get; }
+        public IReadOnlyList<double> SnowflakeWeights { get; }
+        public int BeamIndex { get; }
+        public IReadOnlyList<double> BeamWeights { get; }
+        public double SnowflakeBeamRatio { get; }
+        public double Huskyness { get; }
+
+        public ModuleAnalysis(DenseGenome genome, int startIndex = 0, int count = -1)
+        {
+            if (count < 0)
+                count = genome.Size;
+
+            // cut a square out
+            Region = genome.TransMat.SubMatrix(startIndex, count, startIndex, count);
+
+            // compute
+            SnowflakeWeights = Region.Diagonal().ToArray();
+            BeamIndex = SnowflakeWeights.IndexMax(k => k);
+            BeamWeights = Region.Column(BeamIndex).ToArray();
+            SnowflakeBeamRatio = BeamWeights[BeamIndex] / BeamWeights.Sum();
+            Huskyness = BeamWeights.Sum() / Region.Enumerate().Sum();
+        }
+    }
+
+    public class GenomeInfo<TGenome> where TGenome : IGenome<TGenome>
+    {
+        public GenomeInfo(TGenome genome, Phenotype phenotype, MultiMeasureJudgement judgement)
+        {
+            Genome = genome;
+            Phenotype = phenotype;
+            Judgement = judgement;
+        }
+
+        public TGenome Genome { get; }
+        public Phenotype Phenotype { get; }
+        public MultiMeasureJudgement Judgement { get; }
+
+        public static GenomeInfo<TGenome> Process(TGenome genome, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target)
+        {
+            // compute fitness
+            Phenotype phenotype = genome.Develop(context, drules);
+            MultiMeasureJudgement judgement = MultiMeasureJudgement.Judge(genome, phenotype, jrules, target);
+
+            return new GenomeInfo<TGenome>(genome, phenotype, judgement);
+        }
+    }
+
+    /// <summary>
+    /// Describes the distribution of phenotypes generated from a given genotype compared to a given set of expected frequencies
+    /// </summary>
+    public class GenomeDistributionInfo
+    {
+        private GenomeDistributionInfo(DenseGenome genome, string name, IReadOnlyDictionary<VectorTarget, int> counts, IReadOnlyDictionary<VectorTarget, double> frequencies, double error, double entropy)
+        {
+            Genome = genome;
+            Name = name;
+            Counts = counts;
+            Frequencies = frequencies;
+            Error = error;
+            Entropy = entropy;
+        }
+
+        public static GenomeDistributionInfo Process(ModelExecutionContext context, DenseGenome g, DevelopmentRules drules, IReadOnlyDictionary<VectorTarget, double> expected, string name, int samples = 5000)
+        {
+            var d = Distribute(RandomPhenotypes(context, g, drules, samples), expected.Keys.ToArray());
+            int total = samples;
+            Dictionary<VectorTarget, double> frequencies = d.ToDictionary(kv => kv.Key, kv => (double)kv.Value / total);
+            double error = ChiSquaredError(frequencies, expected);
+            double entropy = Entropy(frequencies.Values);
+
+            return new GenomeDistributionInfo(g, name, d, frequencies, error, entropy);
+        }
+
+        public DenseGenome Genome { get; }
+        public string Name { get; }
+        public IReadOnlyDictionary<VectorTarget, int> Counts { get; }
+        public IReadOnlyDictionary<VectorTarget, double> Frequencies { get; }
+        public double Error { get; }
+        public double Entropy { get; }
+    }
+    
+    public static class Analysis
+    {
+        public static void ExtractMatrix(double[][] trajectories, int index, Linear.Matrix<double> mat)
+        {
+            for (int i = 0; i < mat.RowCount; i++)
+                for (int j = 0; j < mat.ColumnCount; j++)
+                    mat[i, j] = trajectories[i * mat.ColumnCount + j][index];
+        }
+
+        public static void ExtractVector(double[][] trajectories, int index, Linear.Vector<double> vec)
+        {
+            for (int i = 0; i < trajectories.Length; i++)
+                vec[i] = trajectories[i][index];
+        }
+
+        public static Linear.Matrix<double> ExtractMatrix(double[][] trajectories, int index, int rowCount, int colCount)
+        {
+            var mat = Linear.CreateMatrix.Dense<double>(rowCount, colCount, (i, j) => trajectories[i * colCount + j][index]);
+            return mat;
+        }
+
+        public static Linear.Vector<double> ExtractVector(double[][] trajectories, int index)
+        {
+            var vec = Linear.CreateVector.Dense<double>(trajectories.Length, i => trajectories[i][index]);
+            return vec;
+        }
+
+        public static double[] ExtractArray(double[][] trajectories, int index)
+        {
+            var arr = Misc.Create(trajectories.Length, i => trajectories[i][index]);
+            return arr;
+        }
+
+        // TODO: we need to be able to attach metadata to these things, describing the axes and intervals and such, so that RCS viewer can do something useful with them
+        // we could make the first 'length' a version indicator when negative
+        // really we want a new pair of methods which take a proper class
+        // (RCSViewer can have a dependency on NetState and M4M.Model, that is fine, or we can write a new better version,
+        // or write a new program which produces pretty plots (since RCSViewer has special RCS feature we want to keep)
+        // (first we need a versioning story in NetState)
+        public static void SaveTrajectories(String filename, double[][] trajectories, int samplePeriod)
+        {
+            using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Create))
+            {
+                NetState.Serialisation.Utils.WriteInt32(fs, trajectories.Length); // length
+                NetState.Serialisation.Utils.WriteInt32(fs, trajectories[0].Length); // width
+                
+                for (int i = 0; i < trajectories.Length; i++)
+                {
+                    double[] curr = trajectories[i];
+
+                    for (int j = 0; j < curr.Length; j++)
+                    {
+                        NetState.Serialisation.Utils.WriteFloat64(fs, curr[j]);
+                    }
+                }
+
+                if (samplePeriod >= 0)
+                {
+                    NetState.Serialisation.Utils.WriteInt32(fs, -1);
+                    NetState.Serialisation.Utils.WriteInt32(fs, samplePeriod);
+                }
+            }
+        }
+
+        public static double[][] LoadTrajectories(String filename, out int samplePeriod)
+        {
+            using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open))
+            {
+                int length = NetState.Serialisation.Utils.ReadInt32(fs);
+                int width = NetState.Serialisation.Utils.ReadInt32(fs);
+
+                double[][] trajectories = new double[length][];
+                
+                for (int i = 0; i < trajectories.Length; i++)
+                {
+                    double[] curr = trajectories[i] = new double[width];
+
+                    for (int j = 0; j < curr.Length; j++)
+                    {
+                        curr[j] = NetState.Serialisation.Utils.ReadFloat64(fs);
+                    }
+                }
+
+                if (fs.Position < fs.Length)
+                { // there is more
+                    int signaller = NetState.Serialisation.Utils.ReadInt32(fs);
+                    samplePeriod = NetState.Serialisation.Utils.ReadInt32(fs);
+                }
+                else
+                {
+                    samplePeriod = -1;
+                }
+
+                return trajectories;
+            }
+        }
+
+        public static IEnumerable<Linear.Matrix<double>> EnumerateMatricies(double[][] trajectories, int rowCount, int colCount, int start, int end, bool reuseMatrix = false)
+        {
+            if (reuseMatrix)
+            {
+                var mat = Linear.CreateMatrix.Dense<double>(rowCount, colCount);
+                for (int i = start; i < end; i++)
+                {
+                    ExtractMatrix(trajectories, i, mat);
+                    yield return mat;
+                }
+            }
+            else
+            {
+                for (int i = start; i < end; i++)
+                {
+                    yield return ExtractMatrix(trajectories, i, rowCount, colCount);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Saves basic information about a genome. Does not save custom mutators/combiners/etc.
+        /// </summary>
+        public static void SaveGenome(string filename, DenseGenome g)
+        {
+            using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Create))
+            {
+                NetState.Serialisation.Utils.WriteInt32(fs, g.Size);
+
+                for (int i = 0; i < g.Size; i++)
+                    NetState.Serialisation.Utils.WriteFloat64(fs, g.InitialState[i]);
+
+                for (int i = 0; i < g.Size; i++)
+                    for (int j = 0; j < g.Size; j++)
+                        NetState.Serialisation.Utils.WriteFloat64(fs, g.TransMat[i, j]);
+
+                int entryCount = g.TransMatIndexOpenEntries == null ? -1 : g.TransMatIndexOpenEntries.Count;
+                NetState.Serialisation.Utils.WriteInt32(fs, entryCount);
+
+                for (int i = 0; i < entryCount; i++)
+                {
+                    NetState.Serialisation.Utils.WriteInt32(fs, g.TransMatIndexOpenEntries[i].Row);
+                    NetState.Serialisation.Utils.WriteInt32(fs, g.TransMatIndexOpenEntries[i].Col);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Loads basic information about a genome. Does not include custom mutators/combiners/etc.
+        /// </summary>
+        public static DenseGenome LoadGenome(string filename)
+        {
+            using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open))
+            {
+                int size = NetState.Serialisation.Utils.ReadInt32(fs);
+
+                Linear.Vector<double> initialState = Linear.CreateVector.Dense<double>(size);
+                for (int i = 0; i < size; i++)
+                    initialState[i] = NetState.Serialisation.Utils.ReadFloat64(fs);
+
+                Linear.Matrix<double> transMat = Linear.CreateMatrix.Dense<double>(size, size);
+                for (int i = 0; i < size; i++)
+                    for (int j = 0; j < size; j++)
+                        transMat[i, j] = NetState.Serialisation.Utils.ReadFloat64(fs);
+
+                int entryCount = NetState.Serialisation.Utils.ReadInt32(fs);
+
+                MatrixEntryAddress[] openEntries = entryCount >= 0 ? new MatrixEntryAddress[entryCount] : null;
+
+                if (openEntries != null)
+                {
+                    for (int i = 0; i < entryCount; i++)
+                        openEntries[i] = new MatrixEntryAddress(NetState.Serialisation.Utils.ReadInt32(fs), NetState.Serialisation.Utils.ReadInt32(fs));
+                }
+
+                return new DenseGenome(initialState, transMat, null, openEntries);
+            }
+        }
+
+        /// <summary>
+        /// Loads basic information about all genomes in a given directory along with distribution information. Does not include custom mutators/combiners/etc.
+        /// </summary>
+        public static IEnumerable<SavedGenome> LoadGenomes(string dir, DevelopmentRules drules, IReadOnlyDictionary<VectorTarget, double> expected, int gdiSamples = 50000)
+        {
+            RandomSource rand = new MersenneTwister();
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            var files = System.IO.Directory.GetFiles(dir);
+            foreach (var p in files)
+            {
+                var f = System.IO.Path.GetFileNameWithoutExtension(p);
+                if (f.StartsWith("Genome") || f.StartsWith("genome"))
+                {
+                    int epoch = int.Parse(f.Substring(6));
+                    DenseGenome genome = Analysis.LoadGenome(p);
+
+                    yield return new SavedGenome(epoch, genome, GenomeDistributionInfo.Process(context, genome, drules, expected, f, gdiSamples));
+                }
+            }
+        }
+
+        public static double HammingDistance(Linear.Vector<double> a, Linear.Vector<double> b)
+        {
+            Debug.Assert(a.Count == b.Count, $"Program/HammingDistance.ctor(string): a and b must be the same length");
+
+            double acc = 0.0;
+
+            for (int i = 0; i < a.Count; i++)
+            {
+                acc += Math.Abs(a[i] - b[i]);
+            }
+
+            return acc;
+        }
+
+        public static bool Matches(Linear.Vector<double> a, Linear.Vector<double> b)
+        {
+            Debug.Assert(a.Count == b.Count, $"Program/HammingDistance.ctor(string): a and b must be the same length");
+            
+            for (int i = 0; i < a.Count; i++)
+            {
+                if (a[i] * b[i] < 0)
+                    return false;
+            }
+
+            return true;
+        }
+
+        public static VectorTarget Categorize(Phenotype p, IReadOnlyList<VectorTarget> targets)
+        {
+            // there must be some thresholding going on, let's try to work out what it is (probably the same as implied on p18)
+            return targets.FirstOrDefault(t => Matches(p.Vector, t.Vector));
+
+            //return targets.ArgMin(t => HammingDistance(t.Vector, p.Vector));
+        }
+
+        public static IReadOnlyDictionary<VectorTarget, int> Distribute(IEnumerable<Phenotype> phenotypes, IReadOnlyList<VectorTarget> targets)
+        {
+            Dictionary<VectorTarget, int> table = new Dictionary<VectorTarget, int>();
+
+            foreach (VectorTarget t in targets)
+            {
+                table[t] = 0;
+            }
+
+            foreach (Phenotype p in phenotypes)
+            {
+                VectorTarget placement = Categorize(p, targets);
+
+                if (placement != null)
+                {
+                    table[placement]++;
+                }
+            }
+
+            return table;
+        }
+
+        public static IEnumerable<Phenotype> RandomPhenotypes(ModelExecutionContext context, DenseGenome template, DevelopmentRules drules, int count)
+        {
+            for (int i = 0; i < count; i++)
+            {
+                var initialState = RandomUniformVector(context.Rand, template.Size);
+                var g = template.Clone(context);
+                g.CopyOverInitialState(initialState);
+                yield return g.Develop(context, drules);
+            }
+        }
+
+        public static Linear.Vector<double> RandomUniformVector(MathNet.Numerics.Random.RandomSource rand, int size)
+        {
+            double[] arr = Create<double>(size, i => rand.NextDouble() * 2.0 - 1.0);
+            return Linear.CreateVector.Dense<double>(arr);
+        }
+
+        /// <summary>
+        /// Determines the proportion of modules which have a dev-time dominaint trait
+        /// (Involves developing the genome)
+        /// </summary>
+        public static double MeasureDevTimeDominance(RandomSource rand, DevelopmentRules drules, DenseGenome genome, int[][] modules)
+        {
+            return DetectDevTimeDominance(rand, drules, genome, modules).Count(b => b);
+        }
+
+        /// <summary>
+        /// Determines which modules have a dev-time dominaint trait
+        /// (Involves developing the genome)
+        /// </summary>
+        public static bool[] DetectDevTimeDominance(RandomSource rand, DevelopmentRules drules, DenseGenome genome, int[][] modules)
+        {
+            double[][] trajectories = null;
+            genome.DevelopWithTrajectories(rand, drules, ref trajectories);
+
+            return DetectDevTimeDominance(trajectories, modules);
+        }
+
+        /// <summary>
+        /// Determines which modules have a dev-time dominaint trait
+        /// </summary>
+        public static bool[] DetectDevTimeDominance(double[][] trajectories, int[][] modules)
+        {
+            var sums = trajectories.Select(t => t.Sum(e => Math.Abs(e))).ToArray();
+
+            bool[] res = new bool[modules.Length];
+            for (int mi = 0; mi < modules.Length; mi++)
+            {
+                var module = modules[mi];
+                var dominantTrait = module.ArgMax(i => sums[i]);
+                var isDominant = CheckIsDominant(trajectories, dominantTrait, module);
+                res[mi] = isDominant;
+            }
+
+            return res;
+        }
+
+        public static bool CheckIsDominant(double[][] trajectores, int trait, int[] others)
+        {
+            bool oneWeaker = false;
+
+            var upper = trajectores[trait];
+            foreach (var t in others)
+            {
+                var other = trajectores[t];
+
+                for (int i = 0; i < upper.Length; i++)
+                {
+                    var u = Math.Abs(upper[i]);
+                    var o = Math.Abs(other[i]);
+
+                    if (u > o)
+                        oneWeaker = true;
+                    else if (u < o)
+                        return false; // not dominant
+                }
+            }
+
+            return oneWeaker;
+        }
+
+        /// <summary>
+        /// Computes the huskyness of a module containing 2 or more traits
+        /// huskyness = (max_col_weight / total_weight), scaled between 0 (dense/nothing) and 1 (ideal husky)
+        /// Returns 0 if there are no weights
+        /// </summary>
+        public static double ComputeModuleHuskyness(Linear.Matrix<double> dtm, IEnumerable<int> moduleTraits)
+        {
+            double[] columnSums = moduleTraits.Select(j => moduleTraits.Sum(i => Math.Abs(dtm[i, j]))).ToArray();
+
+            int count = columnSums.Length;
+            if (count < 2)
+            {
+                return double.NaN; // too much of a faff having this throw
+                //throw new ArgumentException(nameof(moduleTraits), "A module must comprise 2 or more traits");
+            }
+
+            double max = columnSums.Max();
+            if (max == 0) // avoid div0: this is not a husky
+                return 0.0;
+
+            double total = columnSums.Sum();
+            double hratio = max / total;
+
+            if (double.IsNaN(hratio) || double.IsInfinity(hratio))
+                return double.NaN; // probably ought throw...
+
+            // re-scale hratio from [1/n, 1] to [0, 1]
+            int n = count;
+            return (hratio - (1.0 / n)) / (1.0 - (1.0 / n));
+        }
+
+        /// <summary>
+        /// Computes the independence of a module
+        /// module_independence = (weight_within_module / weight_in_module_row), on scale between 0 (no control) and 1 (total independence)
+        /// Returns NaN if there are no weights
+        /// </summary>
+        public static double ComputeModuleIndependence(Linear.Matrix<double> dtm, IReadOnlyList<int> moduleTraits)
+        {
+            double withinSum = 0.0;
+            double rowSum = 0.0;
+
+            foreach (var i in moduleTraits)
+            {
+                foreach (var j in moduleTraits)
+                {
+                    withinSum += Math.Abs(dtm[i, j]);
+                }
+
+                for (int j = 0; j < dtm.ColumnCount; j++)
+                {
+                    rowSum += Math.Abs(dtm[i, j]);
+                }
+            }
+
+            var iratio = withinSum / rowSum;
+
+            if (double.IsNaN(iratio) || double.IsInfinity(iratio))
+                return double.NaN;
+
+            return iratio;
+        }
+
+        /// <summary>
+        /// Computes the huskyness of each module in tje given dtm
+        /// huskyness = (max_col_weight / total_weight), scaled between 0 (dense/nothing) and 1 (ideal husky)
+        /// Returns 0 if there are no weights
+        /// </summary>
+        public static double[] ComputeModuleHuskyness(Linear.Matrix<double> dtm, IEnumerable<IEnumerable<int>> moduleAssignments)
+        {
+            return moduleAssignments.Select(m => ComputeModuleHuskyness(dtm, m)).ToArray();
+        }
+
+        /// <summary>
+        /// Computes the huskyness of each module in tje given dtm
+        /// huskyness = (max_col_weight / total_weight), scaled between 0 (dense/nothing) and 1 (ideal husky)
+        /// Returns 0 if there are no weights
+        /// </summary>
+        public static double[] ComputeModuleIndependence(Linear.Matrix<double> dtm, IEnumerable<IEnumerable<int>> moduleAssignments)
+        {
+            return moduleAssignments.Select(m => ComputeModuleIndependence(dtm, m.ToList())).ToArray();
+        }
+
+        /// <summary>
+        /// Computes the huskyness of sequencial block modules of the given size
+        /// </summary>
+        public static IEnumerable<double> ComputeBlockModuleHuskynesses(Linear.Matrix<double> dtm, int moduleSize)
+        {
+            for (int i = 0; i < dtm.ColumnCount; i += moduleSize)
+            {
+                yield return ComputeModuleHuskyness(dtm, Enumerable.Range(i, moduleSize));
+            }
+        }
+
+        /// <summary>
+        /// Returns an array of arrays of members of the given number of sequential block modules of the given size
+        /// </summary>
+        public static int[][] BlockModuleTraits(int moduleSize, int moduleCount)
+        {
+            int[][] res = new int[moduleCount][];
+
+            for (int i = 0; i < moduleCount; i++)
+            {
+                res[i] = Enumerable.Range(i * moduleSize, moduleSize).ToArray();
+            }
+
+            return res;
+        }
+
+        /// <summary>
+        /// Computes a simple fitness delta
+        /// </summary>
+        /// <param name="signed">True to multiply the delta by the sign</param>
+        public static TResult ComputeSimpleDelta<TResult>(ModelExecutionContext context, ITarget target, JudgementRules jrules, DevelopmentRules drules, DenseIndividual individual, IReadOnlyList<MatrixEntryAddress> entries, double delta, bool signed, Func<MultiMeasureJudgement, MultiMeasureJudgement, TResult> fitnessComparer)
+        {
+            var individual2 = individual.Clone(context);
+            var dtm2 = individual2.Genome.TransMat;
+
+            if (signed)
+            {
+                foreach (var mea in entries)
+                {
+                    dtm2[mea.Row, mea.Col] += delta * Math.Sign(dtm2[mea.Row, mea.Col]);
+                }
+            }
+            else
+            {
+                foreach (var mea in entries)
+                {
+                    dtm2[mea.Row, mea.Col] += delta;
+                }
+            }
+
+            individual2.DevelopInplace(context, drules);
+
+            var f = MultiMeasureJudgement.Judge(individual.Genome, individual.Phenotype, jrules, target);
+            var f2 = MultiMeasureJudgement.Judge(individual2.Genome, individual2.Phenotype, jrules, target);
+
+            return fitnessComparer(f2, f);
+        }
+    }
+    
+    public class MutantAnalysis<TGenome> where TGenome : IGenome<TGenome>
+    {
+        public GenomeInfo<TGenome> Source { get; }
+        public IReadOnlyList<GenomeInfo<TGenome>> MutantInfos { get; }
+
+        public ReproductionRules ReproductionRules { get; }
+        public DevelopmentRules DevelopmentRules { get; }
+        public JudgementRules JudgementRules { get; }
+        public ITarget Target { get; }
+
+        public double[] FitnessChanges { get; }
+
+        /// <summary>
+        /// makes a copy of the genome, so you don't have to worry about it
+        /// </summary>
+        public MutantAnalysis(TGenome genome, int mutantCount, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target)
+        {
+            // no defensive copy
+
+            ReproductionRules = rrules;
+            DevelopmentRules = drules;
+            JudgementRules = jrules;
+            Target = target;
+            
+            Source = GenomeInfo<TGenome>.Process(genome, context, rrules, drules, jrules, target);
+            MutantInfos = Enumerable.Range(0, mutantCount).Select(i => GenomeInfo<TGenome>.Process(genome.Mutate(context, rrules), context, rrules, drules, jrules, target)).ToArray();
+
+            FitnessChanges = MutantInfos.Select(mi => mi.Judgement.CombinedFitness - Source.Judgement.CombinedFitness).ToArray();
+        }
+        
+        public void ComputeFitnessChangePercentiles(double[] positions, ref double[] reusableBuffer, ref Percentile[] percentiles)
+        {
+            Percentile.ComputePercentiles(positions, FitnessChanges, ref reusableBuffer, ref percentiles);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/DataStructures/ComparableList.cs b/M4MCode/M4M_MkI/M4M.Model/DataStructures/ComparableList.cs
new file mode 100644
index 0000000..1a7c6fa
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DataStructures/ComparableList.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.DataStructures
+{
+    class ComparableList<T> : IEnumerable<T>, IComparable<ComparableList<T>> where T : IComparable<T>
+    {
+        public IReadOnlyList<T> List { get; }
+        private int? _hashCode = null;
+
+        public ComparableList(IReadOnlyList<T> list)
+        {
+            List = list.OrderBy(x => x).ToArray();
+        }
+
+        public int CompareTo(ComparableList<T> other)
+        {
+            return Compare(this.List, other.List);
+        }
+
+        public static int Compare(IReadOnlyList<T> l, IReadOnlyList<T> r)
+        {
+            if (l == r)
+                return 0;
+
+            int lengthCmp = l.Count.CompareTo(r.Count);
+
+            if (lengthCmp != 0)
+                return lengthCmp;
+
+            var zip = l.Zip(r, (a, b) => a.CompareTo(b));
+
+            foreach (var z in zip)
+            {
+                if (z < 0)
+                    return -1;
+                if (z > 0)
+                    return 1;
+            }
+
+            return 0;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj is ComparableList<T> other)
+            {
+                return this.CompareTo(other) == 0;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public override int GetHashCode()
+        {
+            if (_hashCode.HasValue)
+                return _hashCode.Value;
+
+            unchecked
+            {
+                _hashCode = List.Aggregate(0, (a, b) => a * 7 + b.GetHashCode());
+            }
+
+            return _hashCode.Value;
+        }
+
+        public IEnumerator<T> GetEnumerator()
+        {
+            return List.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return List.GetEnumerator();
+        }
+    }
+    
+    class LengthIndependentComparableList<T> : IEnumerable<T>, IComparable<LengthIndependentComparableList<T>> where T : IComparable<T>
+    {
+        public IReadOnlyList<T> List { get; }
+        private int? _hashCode = null;
+
+        public LengthIndependentComparableList(IReadOnlyList<T> list)
+        {
+            List = list.OrderBy(x => x).ToArray();
+        }
+
+        public int CompareTo(LengthIndependentComparableList<T> other)
+        {
+            return Compare(this.List, other.List);
+        }
+
+        public static int Compare(IReadOnlyList<T> l, IReadOnlyList<T> r)
+        {
+            if (l == r)
+                return 0;
+
+            for (int i = 0; ; i++)
+            {
+                bool endl = i == l.Count;
+                bool endr = i == r.Count;
+
+                if (endl && endr)
+                    return 0;
+                if (endl)
+                    return 1;
+                if (endr)
+                    return -1;
+
+                int cmp = l[i].CompareTo(r[i]);
+                if (cmp != 0)
+                    return cmp;
+            }
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj is LengthIndependentComparableList<T> other)
+            {
+                return this.CompareTo(other) == 0;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public override int GetHashCode()
+        {
+            if (_hashCode.HasValue)
+                return _hashCode.Value;
+
+            unchecked
+            {
+                _hashCode = List.Aggregate(0, (a, b) => a * 7 + b.GetHashCode());
+            }
+
+            return _hashCode.Value;
+        }
+
+        public IEnumerator<T> GetEnumerator()
+        {
+            return List.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return List.GetEnumerator();
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/DataStructures/DisjointSets.cs b/M4MCode/M4M_MkI/M4M.Model/DataStructures/DisjointSets.cs
new file mode 100644
index 0000000..3bf0fcf
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DataStructures/DisjointSets.cs
@@ -0,0 +1,123 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.DataStructures
+{
+    // not properly tested
+    public class DisjointSets<T>
+    {
+        private readonly Dictionary<T, int> Index = new Dictionary<T, int>();
+        private readonly List<T> ReverseIndex = new List<T>();
+
+        private readonly List<int> Ranks = new List<int>();
+        private readonly List<int> Parents = new List<int>();
+
+        public int Count => Parents.Count;
+
+        public void Add(T elem)
+        {
+            if (!Index.TryGetValue(elem, out int idx))
+            {
+                idx = Index.Count;
+                Index.Add(elem, idx);
+                ReverseIndex.Add(elem);
+                Parents.Add(idx);
+                Ranks.Add(1);
+            }
+        }
+
+        public void Join(T a, T b)
+        {
+            // union by rank
+            int ia = Flatten(Index[a]);
+            int ib = Flatten(Index[b]);
+
+            if (ia == ib)
+                return;
+
+            int ra = Ranks[ia];
+            int rb = Ranks[ib];
+
+            if (ra == rb)
+            {
+                Parents[ib] = ia;
+                Ranks[ia] = rb + 1;
+            }
+            else if (ra < rb)
+            {
+                Parents[ib] = ia;
+            }
+            else // rb > ra
+            {
+                Parents[ia] = ib;
+            }
+        }
+
+        private int Flatten(int e)
+        {
+            int parent = Parents[e];
+
+            if (parent == e)
+            {
+                return e;
+            }
+            else
+            {
+                e = Flatten(parent);
+                Parents[e] = e;
+                return e;
+            }
+        }
+
+        public IEnumerable<IEnumerable<T>> EnumerateSets()
+        {
+            Dictionary<int, IList<int>> sets = new Dictionary<int, IList<int>>();
+
+            for (int i = 0; i < Count; i++)
+            {
+                int root = Flatten(i);
+
+                if (sets.ContainsKey(root))
+                {
+                    sets[root].Add(i);
+                }
+                else
+                {
+                    sets.Add(root, new List<int>() { i });
+                }
+            }
+
+            return sets.Values.Select(s => s.Select(e => ReverseIndex[e]));
+        }
+
+        public static IEnumerable<IReadOnlyCollection<T>> Cluster(IEnumerable<IEnumerable<T>> groups)
+        {
+            DisjointSets<T> djs = new DisjointSets<T>();
+
+            foreach (var group in groups)
+            {
+                // take the first element in each group as the 'token' element, and join all the others with it (this could be done much faster if we allowed ourselves access to internal representation)
+                T tokenElement = default(T);
+                bool gotToken = false;
+
+                foreach (var elem in group)
+                {
+                    djs.Add(elem);
+
+                    if (gotToken)
+                    { // join us to the tokenElement
+                        djs.Join(tokenElement, elem);
+                    }
+                    else
+                    { // we are the token element
+                        tokenElement = elem;
+                        gotToken = true;
+                    }
+                }
+            }
+
+            return djs.EnumerateSets().Select(s => s.ToArray());
+        }
+    }
+}
+
diff --git a/M4MCode/M4M_MkI/M4M.Model/DataStructures/Graph.cs b/M4MCode/M4M_MkI/M4M.Model/DataStructures/Graph.cs
new file mode 100644
index 0000000..6159dbe
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DataStructures/Graph.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.DataStructures
+{
+    // Graph
+
+    public class GraphEdge<T>
+    {
+        public GraphEdge(GraphNode<T> end, float cost)
+        {
+            End = end;
+            Cost = cost;
+        }
+
+        public GraphNode<T> End { get; }
+        public float Cost { get; }
+    }
+
+    public class GraphNode<T>
+    {
+        public readonly ExpansionFunction<GraphNode<T>> DefaultExpantionFunction = gnode => gnode.Edges.Select(e => new Step<GraphNode<T>>(e.End, e.Cost));
+        public readonly HeuristicFunction<GraphNode<T>> NullHeuristicFunction = gnode => 0f;
+
+        public GraphNode(T payload)
+        {
+            Payload = payload;
+        }
+
+        public T Payload { get; }
+        public List<GraphEdge<T>> _edges = new List<GraphEdge<T>>();
+        public IReadOnlyList<GraphEdge<T>> Edges => _edges.AsReadOnly();
+
+        public IReadOnlyList<GraphNode<T>> RouteTo(GraphNode<T> destination, HeuristicFunction<GraphNode<T>> heuristicFunction = null)
+        {
+            heuristicFunction = heuristicFunction ?? NullHeuristicFunction;
+
+            PathNode<GraphNode<T>> pathNode = AStar<GraphNode<T>>.Route(this, destination, DefaultExpantionFunction, heuristicFunction);
+
+            if (pathNode == null)
+            {
+                return null;
+            }
+            else
+            {
+                return pathNode.AssembleRoute().ToArray();
+            }
+        }
+
+        public IEnumerable<PathNode<GraphNode<T>>> Expand(HeuristicFunction<GraphNode<T>> heuristicFunction = null)
+        {
+            heuristicFunction = heuristicFunction ?? NullHeuristicFunction;
+
+            return AStar<GraphNode<T>>.Expand(new[] { this }, DefaultExpantionFunction, heuristicFunction);
+        }
+
+        /// <summary>
+        /// One way join
+        /// </summary>
+        public void JoinTo(GraphNode<T> other, float cost)
+        {
+            _edges.Add(new GraphEdge<T>(other, cost));
+        }
+
+        /// <summary>
+        /// Two way join
+        /// </summary>
+        public void JoinWith(GraphNode<T> other, float cost)
+        {
+            _edges.Add(new GraphEdge<T>(other, cost));
+            other._edges.Add(new GraphEdge<T>(this, cost));
+        }
+    }
+
+    // A*
+
+    public struct Step<T>
+    {
+        public Step(T end, float cost) : this()
+        {
+            End = end;
+            Cost = cost;
+        }
+
+        public T End { get; }
+        public float Cost { get; }
+    }
+
+    public class PathNode<T> : IComparable<PathNode<T>>
+    {
+        /// <summary>
+        /// Constructs a Start node
+        /// </summary>
+        public PathNode(T graphNode, float heurisitcCost)
+        {
+            Previous = null;
+            GraphNode = graphNode;
+            TrueCost = 0f;
+            HeuristicCost = heurisitcCost;
+        }
+
+        /// <summary>
+        /// Constructs a non-Start node
+        /// </summary>
+        public PathNode(PathNode<T> previous, T graphNode, float trueCost, float heuristicCost)
+        {
+            Previous = previous;
+            GraphNode = graphNode;
+            TrueCost = trueCost;
+            HeuristicCost = heuristicCost;
+        }
+
+        public PathNode<T> Previous { get; }
+        public T GraphNode { get; }
+        public float TrueCost { get; }
+        public float HeuristicCost { get; }
+
+        int IComparable<PathNode<T>>.CompareTo(PathNode<T> other)
+        {
+            return HeuristicCost.CompareTo(other.HeuristicCost);
+        }
+
+        public Stack<T> AssembleRoute()
+        {
+            return AssemblePathInternal(this);
+        }
+
+        private static Stack<T> AssemblePathInternal(PathNode<T> top)
+        {
+            var stack = new Stack<T>();
+
+            var cur = top;
+
+            while (cur.Previous != null)
+            {
+                stack.Push(cur.GraphNode);
+                cur = cur.Previous;
+            }
+
+            return stack;
+        }
+
+        public PathNode<T> FindStartNode()
+        {
+            var cur = this;
+
+            while (cur.Previous != null)
+            {
+                cur = cur.Previous;
+            }
+
+            return cur;
+        }
+    }
+
+    public delegate float HeuristicFunction<T>(T graphNode);
+    public delegate IEnumerable<Step<T>> ExpansionFunction<T>(T graphNode);
+
+    public class AStar<T>
+    {
+        /// <summary>
+        /// Returns the PathNode that defines the route from the start to the destination
+        /// Returns null if there is no route
+        /// </summary>
+        public static PathNode<T> Route(T start, T destination, ExpansionFunction<T> expansionFunction, HeuristicFunction<T> heuristicFunction)
+        {
+            var route = Expand(new[] { start }, expansionFunction, heuristicFunction).FirstOrDefault(pn => pn.GraphNode.Equals(destination));
+
+            return route;
+        }
+
+        public static IEnumerable<PathNode<T>> Expand(IEnumerable<T> starts, ExpansionFunction<T> expansionFunction, HeuristicFunction<T> heuristicFunction)
+        {
+            var due = new PriorityQueue<T, PathNode<T>>();
+            foreach (var start in starts)
+            due.Enqueue(start, new PathNode<T>(start, heuristicFunction(start)));
+
+            var visited = new HashSet<T>();
+            
+            while (due.TryRemoveMin(out var cur))
+            {
+                var curNode = cur.Element;
+                var curPath = cur.Priority; // bit confusing...
+                yield return curPath;
+
+                visited.Add(curNode);
+
+                foreach (var step in expansionFunction(curNode))
+                {
+                    if (visited.Contains(step.End))
+                    {
+                        continue; // already expanded this node: we can't have a better path
+                    }
+
+                    // compute cost, assemble PathNode
+                    var cost = curPath.TrueCost + step.Cost;
+                    var hcost = cost + heuristicFunction(step.End);
+                    var provisionalPath = new PathNode<T>(curPath, step.End, cost, hcost);
+
+                    if (due.TryGetPriority(step.End, out var existingPath))
+                    {
+                        // only use our new path if it is better than the existing path
+                        if (provisionalPath.HeuristicCost < existingPath.HeuristicCost)
+                        {
+                            due.Update(step.End, provisionalPath);
+                        }
+                    }
+                    else
+                    {
+                        due.Enqueue(step.End, provisionalPath);
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.Model/DataStructures/PriorityQueue.cs b/M4MCode/M4M_MkI/M4M.Model/DataStructures/PriorityQueue.cs
new file mode 100644
index 0000000..03dfdbb
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DataStructures/PriorityQueue.cs
@@ -0,0 +1,364 @@
+using System;
+using System.Collections.Generic;
+
+namespace M4M.DataStructures
+{
+    /// <summary>
+    /// A struct, representing an element and its priority
+    /// </summary>
+    /// <typeparam name="TElement">The type of element</typeparam>
+    /// <typeparam name="TPriority">The type of priority</typeparam>
+    public struct PriorityPair<TElement, TPriority>
+    {
+        /// <summary>
+        /// Creates an element/priority pair
+        /// </summary>
+        /// <param name="element">The element</param>
+        /// <param name="priority">The priority</param>
+        internal PriorityPair(TElement element, TPriority priority) : this()
+        {
+            Element = element;
+            Priority = priority;
+        }
+
+        /// <summary>
+        /// The element
+        /// </summary>
+        public TElement Element { get; }
+
+        /// <summary>
+        /// The priority
+        /// </summary>
+        public TPriority Priority { get; }
+    }
+
+    /// <summary>
+    /// Represents an ordered queue of elements, allowing ordered removal of elements according to a comparable priority
+    /// Elements with the same priority are sorted such that elements added earlier are treated as having an ordinally lower priority (FIFO)
+    /// (Updating optionally behaves as removing and re-adding)
+    /// </summary>
+    /// <typeparam name="TElement">The type of element</typeparam>
+    /// <typeparam name="TPriority">The type of priority (must implement IComparable(<typeparamref name="TPriority"/>))</typeparam>
+    public class PriorityQueue<TElement, TPriority> where TPriority : IComparable<TPriority>
+    {
+        private class NodeComparer : IComparer<Node>
+        {
+            public readonly static NodeComparer Instance = new NodeComparer();
+
+            private NodeComparer()
+            {
+                // nix
+            }
+
+            int IComparer<Node>.Compare(Node x, Node y)
+            {
+                return x.CompareTo(y);
+            }
+        }
+
+        private class Node : IComparable<Node>
+        {
+            public Node(int ElementId, TElement element, TPriority priority)
+            {
+                this.ElementId = ElementId;
+                Element = element;
+                Priority = priority;
+            }
+
+            public int ElementId { get; }
+            public TElement Element { get; }
+            public TPriority Priority { get; }
+
+            public int CompareTo(Node other)
+            {
+                // compare by priority
+                int pc = Priority.CompareTo(other.Priority);
+                if (pc != 0)
+                    return pc;
+
+                // if the priorities are the same, compare by id
+                int eic = ElementId.CompareTo(other.ElementId);
+                return eic;
+            }
+        }
+
+        /// <summary>
+        /// Initialises an empty PriorityQueue
+        /// </summary>
+        public PriorityQueue()
+        {
+            NodeQueue = new SortedSet<Node>(NodeComparer.Instance);
+            NodeTable = new Dictionary<TElement, Node>();
+        }
+
+        private int _nextId = 0;
+        private int NextId => _nextId++;
+
+        private SortedSet<Node> NodeQueue { get; }
+        private Dictionary<TElement, Node> NodeTable { get; }
+
+        private void InternalAdd(TElement element, TPriority priority)
+        {
+            Node node = new Node(NextId, element, priority);
+            NodeQueue.Add(node);
+            NodeTable.Add(element, node);
+        }
+
+        private void InternalUpdate(Node oldNode, TPriority newPriority, bool takeNewId)
+        {
+            NodeQueue.Remove(oldNode);
+            Node newNode = new Node(takeNewId ? NextId : oldNode.ElementId, oldNode.Element, newPriority);
+            NodeQueue.Add(newNode);
+            NodeTable[oldNode.Element] = newNode;
+        }
+
+        private void InternalRemove(Node node)
+        {
+            NodeQueue.Remove(node);
+            NodeTable.Remove(node.Element);
+        }
+
+        /// <summary>
+        /// Gets the number of elements in the queue
+        /// </summary>
+        public int Count => NodeQueue.Count;
+
+        /// <summary>
+        /// Gets a boolean value indicating whether the queue is empty
+        /// </summary>
+        public bool Empty => Count == 0;
+
+        /// <summary>
+        /// Gets the element with the lowest ordinal priority from the queue
+        /// Throws if the queue is empty
+        /// </summary>
+        public PriorityPair<TElement, TPriority> Min
+        {
+            get
+            {
+                if (NodeQueue.Count == 0)
+                {
+                    throw new InvalidOperationException("Cannot remove the minimum from an empty queue");
+                }
+
+                Node minNode = NodeQueue.Min;
+                return new PriorityPair<TElement, TPriority>(minNode.Element, minNode.Priority);
+            }
+        }
+
+        /// <summary>
+        /// Removes and returns the element with the lowest ordinal priority from the queue
+        /// Throws if the queue is empty
+        /// </summary>
+        /// <returns>The lowest ordinal priority element and its priority</returns>
+        public PriorityPair<TElement, TPriority> RemoveMin()
+        {
+            if (NodeQueue.Count == 0)
+            {
+                throw new InvalidOperationException("Cannot remove the minimum from an empty queue");
+            }
+
+            Node minNode = NodeQueue.Min;
+            InternalRemove(minNode);
+            return new PriorityPair<TElement, TPriority>(minNode.Element, minNode.Priority);
+        }
+
+        /// <summary>
+        /// Attempts to remove and return the element with the lowest ordinal priority from the queue
+        /// Returns true if the queue was not empty, and minElement has been set, else false
+        /// </summary>
+        /// <param name="minElement">The element removed from the queue, or a default value if the queue was empty</param>
+        /// <returns>The lowest ordinal priority element and its priority</returns>
+        public bool TryRemoveMin(out PriorityPair<TElement, TPriority> minElement)
+        {
+            if (NodeQueue.Count == 0)
+            {
+                minElement = default(PriorityPair<TElement, TPriority>);
+                return false;
+            }
+
+            Node minNode = NodeQueue.Min;
+            InternalRemove(minNode);
+            minElement = new PriorityPair<TElement, TPriority>(minNode.Element, minNode.Priority);
+            return true;
+        }
+
+        /// <summary>
+        /// Looks up the priority for a given element
+        /// Throws if the element was not in the queue
+        /// </summary>
+        /// <param name="element">The element whose priority should be looked up</param>
+        /// <returns>The priority of the given element</returns>
+        public TPriority GetPriority(TElement element)
+        {
+            if (NodeTable.TryGetValue(element, out Node found))
+            {
+                return found.Priority;
+            }
+            else
+            {
+                throw new ArgumentException("Element was not in the queue", nameof(found));
+            }
+        }
+
+        /// <summary>
+        /// Attempts to lookup the priority for a given element
+        /// </summary>
+        /// <param name="element">The element whose priority should be looked up</param>
+        /// <param name="priority">The priority of the element if found (out), otherwise a default value</param>
+        /// <returns>True if the element was present and the priority has been returned</returns>
+        public bool TryGetPriority(TElement element, out TPriority priority)
+        {
+            if (NodeTable.TryGetValue(element, out Node found))
+            {
+                priority = found.Priority;
+                return true;
+            }
+            else
+            {
+                priority = default(TPriority);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// Update the priority for the given element
+        /// Throws if the element was not present
+        /// </summary>
+        /// <param name="element">The element whose priority should be updated</param>
+        /// <param name="newPriority">The new priority of the element</param>
+        /// <param name="requeue">Whether or not to requeue the element</param>
+        public void Update(TElement element, TPriority newPriority, bool requeue = true)
+        {
+            if (!NodeTable.TryGetValue(element, out Node oldNode))
+            {
+                throw new ArgumentException("Element not already present in Queue");
+            }
+
+            InternalUpdate(oldNode, newPriority, requeue);
+        }
+
+        /// <summary>
+        /// Attempts to update the priority for the given element
+        /// Returns true if the element was present and has been updated, returns false if the element was not present (does not add the element)
+        /// </summary>
+        /// <param name="element">The element whose priority should be updated</param>
+        /// <param name="newPriority">The new priority of the element</param>
+        /// <param name="requeue">Whether or not to requeue the element</param>
+        /// <returns>True if the element's priority was updated, otherwise false</returns>
+        public bool TryUpdate(TElement element, TPriority newPriority, bool requeue = true)
+        {
+            if (!NodeTable.TryGetValue(element, out Node oldNode))
+            {
+                return false;
+            }
+
+            InternalUpdate(oldNode, newPriority, requeue);
+            return true;
+        }
+
+        /// <summary>
+        /// Removes the element from the queue
+        /// Throws if the element is not present
+        /// </summary>
+        /// <param name="element">The element to remove from the queue</param>
+        public void Remove(TElement element)
+        {
+            if (!NodeTable.TryGetValue(element, out Node found))
+            {
+                throw new ArgumentException("Element not already present in Queue");
+            }
+
+            InternalRemove(found);
+        }
+
+        /// <summary>
+        /// Attempts to remove the element from the queue
+        /// Returns true if the element was present and was removed, false if the element was not present
+        /// </summary>
+        /// <param name="element"></param>
+        /// <returns>True if the element was removed, otherwise false</returns>
+        public bool TryRemove(TElement element)
+        {
+            if (!NodeTable.TryGetValue(element, out Node found))
+            {
+                return false;
+            }
+
+            InternalRemove(found);
+            return true;
+        }
+
+        /// <summary>
+        /// Attempts to enqueue the element to the queue with the given priority
+        /// Returns true if added, false if the element was already present
+        /// </summary>
+        /// <param name="element">The element to add to the queue</param>
+        /// <param name="priority">The priority the element carries</param>
+        /// <param name="requeueOnUpdate">If the element is already present, whether to simulated remove/requeue</param>
+        /// <returns>True if the element was added, otherwise false</returns>
+        public bool EnqueueOrUpdate(TElement element, TPriority priority, bool requeueOnUpdate = true)
+        {
+            if (NodeTable.TryGetValue(element, out Node oldNode))
+            {
+                // update
+                InternalUpdate(oldNode, priority, requeueOnUpdate);
+                return false;
+            }
+            else
+            {
+                // add
+                InternalAdd(element, priority);
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Attempts to add the element to the queue with the given priority
+        /// Returns true if added, false if the element was already present
+        /// </summary>
+        /// <param name="element">The element to add to the queue</param>
+        /// <param name="priority">The priority the element carries</param>
+        /// <returns>True if the element was added, otherwise false</returns>
+        public bool TryEnqueue(TElement element, TPriority priority)
+        {
+            if (NodeTable.TryGetValue(element, out Node oldNode))
+            {
+                // no
+                return false;
+            }
+            else
+            {
+                // add
+                InternalAdd(element, priority);
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Queues the element in the queue with the given priority
+        /// Throws if the element is already present in the queue
+        /// </summary>
+        /// <param name="element">The element to add to the queue</param>
+        /// <param name="priority">The priority the element carries</param>
+        public void Enqueue(TElement element, TPriority priority)
+        {
+            if (NodeTable.TryGetValue(element, out Node found))
+            {
+                throw new ArgumentException("Element already present in Queue");
+            }
+
+            InternalAdd(element, priority);
+        }
+
+        /// <summary>
+        /// Removes all elements from the queue
+        /// </summary>
+        public void Clear()
+        {
+            NodeTable.Clear();
+            NodeQueue.Clear();
+            _nextId = 0;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/DeltaRule/DeltaRule.cs b/M4MCode/M4M_MkI/M4M.Model/DeltaRule/DeltaRule.cs
new file mode 100644
index 0000000..f6a008d
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DeltaRule/DeltaRule.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.DeltaRule
+{
+    /// <summary>
+    /// DeltaRule Spinner from 17G01.
+    /// Hill-climbs a population of 1, before performing a 'Delta-Rule' update to the TransMat.
+    /// Doesn't mess with the rules: you probably want to provide a NullTransMatMutator.
+    /// </summary>
+    [State.StateClass]
+    public class DeltaRuleSpinner : IPopulationSpinner<DenseIndividual>
+    {
+        [Obsolete]
+        protected DeltaRuleSpinner()
+        {
+        }
+
+        public DeltaRuleSpinner(double fitnessWeightingPower)
+        {
+            FitnessWeightingPower = fitnessWeightingPower;
+        }
+
+        public string Name => "DeltaRuleSpinner";
+
+        public string Description => $"{Name} (FitnessWeightingPower = {FitnessWeightingPower})";
+
+        /// <summary>
+        /// The power to which to raise the fitness for the fitness factor on update magnitude.
+        /// Fitness should be non-negative for non-zero power.
+        /// </summary>
+        [State.SimpleStateProperty("FitnessWeightingPower")]
+        public double FitnessWeightingPower { get; private set; } = 0.0;
+
+        public IReadOnlyList<IndividualJudgement<DenseIndividual>> SpinPopulation(Population<DenseIndividual> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<DenseIndividual> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<DenseIndividual>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            if (population.Count != 1)
+                throw new ArgumentException("Population must have exactly 1 individual to use a DeltaRuleSpinner");
+
+            // spin as normal
+            population.SpinPopulation(context, rrules, drules, jrules, target, selectorPreparer, generations, judgementFeedback, eliteCount, hillclimberMode);
+
+            // now extrct and perform DeltaRule update
+            var individual = population.ExtractAll().Single();
+
+            DeltaRuleStepInplace(individual, context, rrules, drules, jrules, target);
+
+            var cloned = individual.Clone(context);
+            var j = cloned.Judge(jrules, target);
+
+            population.Introduce(individual);
+
+            return new[] { new IndividualJudgement<DenseIndividual>(cloned, j) };
+        }
+
+        public void DeltaRuleStepInplace(DenseIndividual individual, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target)
+        {
+            double q = rrules.DevelopmentalTransformationMatrixMutationSize;
+            double σ = 0.01 * q;
+
+            IEnumerable<MatrixEntryAddress> entries = individual.Genome.TransMatIndexOpenEntries ?? MatrixEntryAddress.CompleteLazy(individual.Genome.Size, individual.Genome.Size);
+
+            var devTransMat = individual.Genome.TransMat;
+            var clonedTransMat = context.Clone(devTransMat);
+            var rand = context.Rand;
+
+            double fitnessFactor;
+
+            target.NextGeneration(rand, jrules);
+
+            if (FitnessWeightingPower == 0.0)
+            {
+                // don't measure
+                fitnessFactor = 1.0;
+            }
+            else
+            {
+                individual.DevelopInplace(context, drules);
+                double w = individual.Judge(jrules, target).CombinedFitness;
+                fitnessFactor = Math.Pow(w, FitnessWeightingPower);
+            }
+
+            foreach (var entry in entries)
+            {
+                double old = devTransMat[entry.Row, entry.Col];
+
+                // +
+                double dPos = MathNet.Numerics.Distributions.Normal.Sample(rand, +q, σ);
+                devTransMat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(old + dPos);
+                individual.DevelopInplace(context, drules);
+                double wPos = individual.Judge(jrules, target).CombinedFitness;
+
+                // -
+                double dNeg = MathNet.Numerics.Distributions.Normal.Sample(rand, -q, σ);
+                devTransMat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(old + dNeg);
+                individual.DevelopInplace(context, drules);
+                double wNeg = individual.Judge(jrules, target).CombinedFitness;
+
+                // cache result
+                if (wPos > wNeg)
+                {
+                    clonedTransMat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(old + dPos * fitnessFactor);
+                }
+                else
+                {
+                    clonedTransMat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(old + dNeg * fitnessFactor);
+                }
+
+                // reset original
+                devTransMat[entry.Row, entry.Col] = old;
+            }
+
+            context.Release(clonedTransMat);
+            individual.Genome.CopyOverTransMat(clonedTransMat);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/DeltaTest.cs b/M4MCode/M4M_MkI/M4M.Model/DeltaTest.cs
new file mode 100644
index 0000000..ce65ab2
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DeltaTest.cs
@@ -0,0 +1,195 @@
+using System;
+
+namespace M4M
+{
+    // TODO: this should be an interface like IDeltaMode
+    public enum DeltaResultMode
+    {
+        Best,
+        Beneficial,
+    }
+
+    public enum DeltaResult
+    {
+        Neither = 0,
+        Minus = 1,
+        Plus = 2,
+        Both = 3,
+    }
+
+    public static class DeltaResultExtensions
+    {
+        public static string ToSymbol(this DeltaResult deltaResult)
+        {
+            switch (deltaResult)
+            {
+                case DeltaResult.Neither:
+                    return "/";
+                case DeltaResult.Plus:
+                    return "+";
+                case DeltaResult.Minus:
+                    return "-";
+                case DeltaResult.Both:
+                    return "±";
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(deltaResult));
+            }
+        }
+    }
+
+    public interface IDeltaMode
+    {
+        string Name { get; }
+        double Judge(ModelExecutionContext context, DenseGenome genome, DevelopmentRules drules, JudgementRules jrules, ITarget target, int r, int c, double delta);
+        void Apply(DenseGenome genome, int r, int c, double delta);
+    }
+
+    public class CellDeltaMode : IDeltaMode
+    {
+        public static readonly CellDeltaMode Instance = new CellDeltaMode();
+
+        public string Name => "Cell DeltaMode";
+
+        public void Apply(DenseGenome genome, int r, int c, double delta)
+        {
+            genome.TransMat[r, c] += delta;
+        }
+
+        public double Judge(ModelExecutionContext context, DenseGenome genome, DevelopmentRules drules, JudgementRules jrules, ITarget target, int r, int c, double delta)
+        {
+            var old = genome.TransMat[r, c];
+            genome.TransMat[r, c] += delta;
+            var i = DenseIndividual.Develop(genome, context, drules, false);
+            var f = i.Judge(jrules, target).CombinedFitness;
+            genome.TransMat[r, c] = old;
+            return f;
+        }
+    }
+
+    public class ColumnDeltaMode : IDeltaMode
+    {
+        public static readonly ColumnDeltaMode Instance = new ColumnDeltaMode();
+        
+        public string Name => "Column DeltaMode";
+
+        public void Apply(DenseGenome genome, int r, int c, double delta)
+        {
+            for (int ri = 0; ri < genome.Size; ri++)
+                genome.TransMat[ri, c] += delta;
+        }
+
+        public double Judge(ModelExecutionContext context, DenseGenome genome, DevelopmentRules drules, JudgementRules jrules, ITarget target, int r, int c, double delta)
+        {
+            var old = genome.TransMat.Column(c);
+
+            for (int ri = 0; ri < genome.Size; ri++)
+                genome.TransMat[ri, c] += delta;
+
+            var i = DenseIndividual.Develop(genome, context, drules, false);
+            var f = i.Judge(jrules, target).CombinedFitness;
+
+            genome.TransMat.SetColumn(c, old);
+            return f;
+        }
+    }
+
+    public class CompositeDeltaMode : IDeltaMode
+    {
+        public CompositeDeltaMode(string name, IDeltaMode applier, IDeltaMode judger)
+        {
+            Name = name ?? throw new ArgumentNullException(nameof(name));
+            Applier = applier ?? throw new ArgumentNullException(nameof(applier));
+            Judger = judger ?? throw new ArgumentNullException(nameof(judger));
+        }
+
+        public string Name { get; }
+        public IDeltaMode Applier { get; }
+        public IDeltaMode Judger { get; }
+
+        public void Apply(DenseGenome genome, int r, int c, double delta)
+        {
+            Applier.Apply(genome, r, c, delta);
+        }
+
+        public double Judge(ModelExecutionContext context, DenseGenome genome, DevelopmentRules drules, JudgementRules jrules, ITarget target, int r, int c, double delta)
+        {
+            return Judger.Judge(context, genome, drules, jrules, target, r, c, delta);
+        }
+    }
+
+    public class DeltaTest
+    {
+        public double F0 { get; }
+        public MathNet.Numerics.LinearAlgebra.Matrix<double> DeltaPlus { get; }
+        public MathNet.Numerics.LinearAlgebra.Matrix<double> DeltaMinus { get; }
+        private bool[,] Open { get; }
+        
+        public bool IsOpen(int r, int c)
+        {
+            return Open == null || Open[r, c];
+        }
+
+        public DeltaTest(double f0, MathNet.Numerics.LinearAlgebra.Matrix<double> deltaPlus, MathNet.Numerics.LinearAlgebra.Matrix<double> deltaMinus, bool[,] open)
+        {
+            F0 = f0;
+            DeltaPlus = deltaPlus;
+            DeltaMinus = deltaMinus;
+            Open = open;
+        }
+
+        private void EnsureOpen(int r, int c)
+        {
+            if (!IsOpen(r, c))
+                throw new InvalidOperationException($"Entry {r}, {c} is not open.");
+        }
+
+        public DeltaResult GetBest(int r, int c)
+        {
+            EnsureOpen(r, c);
+
+            var p = DeltaPlus[r, c];
+            var m = DeltaMinus[r, c];
+
+            var cmp = p.CompareTo(m);
+
+            if (cmp < 0)
+                return m > F0 ? DeltaResult.Minus : DeltaResult.Neither;
+            else if (cmp > 0)
+                return p > F0 ? DeltaResult.Plus : DeltaResult.Neither;
+            else
+                return m > F0 ? DeltaResult.Both : DeltaResult.Neither;
+        }
+
+        public DeltaResult GetBeneficial(int r, int c)
+        {
+            EnsureOpen(r, c);
+
+            var p = DeltaPlus[r, c];
+            var m = DeltaMinus[r, c];
+
+            var pp = p > F0;
+            var mp = m > F0;
+
+            if (pp && mp)
+                return DeltaResult.Both;
+            else if (pp)
+                return DeltaResult.Plus;
+            else if (mp)
+                return DeltaResult.Minus;
+            else
+                return DeltaResult.Neither;
+        }
+
+        public static DeltaTest Test(DenseGenome genome, DevelopmentRules drules, JudgementRules jrules, ITarget target, double deltaMag, IDeltaMode deltaMode, bool[,] open = null)
+        {
+            var context = new ModelExecutionContext(new CustomMersenneTwister(0));
+            
+            var f0 = deltaMode.Judge(context, genome, drules, jrules, target, 0, 0, 0);
+
+            var deltaPlus = MathNet.Numerics.LinearAlgebra.CreateMatrix.Dense(genome.Size, genome.Size, (i, j) => open == null || open[i, j] ? deltaMode.Judge(context, genome, drules, jrules, target, i, j, +deltaMag) : double.NaN);
+            var deltaMinus = MathNet.Numerics.LinearAlgebra.CreateMatrix.Dense(genome.Size, genome.Size, (i, j) => open == null || open[i, j] ? deltaMode.Judge(context, genome, drules, jrules, target, i, j, -deltaMag) : double.NaN);
+
+            return new DeltaTest(f0, deltaPlus, deltaMinus, open);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/DensePopulationFeedback.cs b/M4MCode/M4M_MkI/M4M.Model/DensePopulationFeedback.cs
new file mode 100644
index 0000000..0a1d9e1
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DensePopulationFeedback.cs
@@ -0,0 +1,37 @@
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    /// <summary>
+    /// Something that configures a DensePopulationExperimentFeedback
+    /// </summary>
+    /// <param name="sensePopulationExperimentFeedback">The DensePopulationExperimentFeedback to configure</param>
+    public delegate void DensePopulationExperimentFeedbackConfigurator(IDensePopulationExperimentFeedback densePopulationExperimentFeedback);
+    
+    /// <summary>
+    /// Something that creates a DensePopulationExperimentFeedback
+    /// </summary>
+    /// <param name="rand">A random source</param>
+    /// <param name="populationExperimentConfig">The experiment configuration being run</param>
+    /// <param name="feedback">Optional: null means 'provide your own'</param>
+    public delegate IDensePopulationExperimentFeedback DensePopulationExperimentFeedbackFactory(RandomSource rand, PopulationExperimentConfig<DenseIndividual> populationExperimentConfig, PopulationExperimentFeedback<DenseIndividual> feedback);
+
+    public interface IDensePopulationExperimentFeedback
+    {
+        PopulationExperimentConfig<DenseIndividual> PopulationConfig { get; }
+
+        /// <summary>
+        /// The PopulationExperimentFeedback this instance uses
+        /// </summary>
+        PopulationExperimentFeedback<DenseIndividual> Feedback { get; }
+        
+        IReadOnlyList<IndividualJudgement<DenseIndividual>> BestSamples { get; }
+        IReadOnlyList<int> SampleEpochs { get; }
+
+        int SamplePeriod { get; }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/DtmClassification.cs b/M4MCode/M4M_MkI/M4M.Model/DtmClassification.cs
new file mode 100644
index 0000000..caee60e
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/DtmClassification.cs
@@ -0,0 +1,534 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using M4M.DataStructures;
+
+namespace M4M
+{
+    public class Motivator
+    {
+        public int Index { get; }
+
+        private readonly int[] _motivatees;
+        public IReadOnlyList<int> Motivatees => _motivatees;
+
+        public Motivator(int index, IEnumerable<int> motivatees)
+        {
+            Index = index;
+
+            _motivatees = motivatees.ToArray();
+            Array.Sort(_motivatees);
+        }
+
+        public bool Motivates(int other)
+        {
+            return Array.BinarySearch<int>(_motivatees, other) >= 0;
+        }
+    }
+
+    public class Module : IComparable<Module>
+    {
+        public IReadOnlyList<int> Motivators { get; }
+
+        private readonly int[] _motivatees;
+        public IReadOnlyList<int> Motivatees => _motivatees;
+
+        public Module(IEnumerable<int> motivators, IEnumerable<int> motivatees)
+        {
+            Motivators = motivators.OrderBy(i => i).ToArray();
+            _motivatees = motivatees.OrderBy(i => i).ToArray();
+        }
+
+        public bool Motivates(int other)
+        {
+            return Array.BinarySearch<int>(_motivatees, other) >= 0;
+        }
+
+        // order by motivators, then motivatees (lengths first)
+        public int CompareTo(Module other)
+        {
+            var cmp = Motivators.Count.CompareTo(other.Motivators.Count);
+
+            if (cmp != 0)
+                return cmp;
+
+            cmp = Motivatees.Count.CompareTo(other.Motivatees.Count);
+
+            if (cmp != 0)
+                return cmp;
+
+            cmp = ComparableList<int>.Compare(Motivators, other.Motivators);
+
+            if (cmp != 0)
+                return cmp;
+
+            cmp = ComparableList<int>.Compare(Motivatees, other.Motivatees);
+            
+            return cmp;
+        }
+
+        public string SummaryString
+        {
+            get => $"{Motivators.Count}/{Motivatees.Count}";
+        }
+
+        public static IEnumerable<Module> OrderByTraits(IEnumerable<Module> modules)
+        {
+            var sep = new[] { int.MinValue }; // use to separate the Motivators and Motivatees
+            return modules.OrderBy(m => new LengthIndependentComparableList<int>(m.Motivators.Concat(sep).Concat(m.Motivatees).ToArray()));
+        }
+
+        public static IEnumerable<Module> OrderBySize(IEnumerable<Module> modules)
+        {
+            return modules.OrderByDescending(m => m);
+        }
+
+        public static string SummarizeTraitOrder(IEnumerable<Module> modules)
+        {
+            return SummarizeUnsorted(OrderByTraits(modules));
+        }
+
+        public static string Summarize(IEnumerable<Module> modules)
+        {
+            return SummarizeUnsorted(OrderBySize(modules));
+        }
+
+        public static string SummarizeUnsorted(IEnumerable<Module> modules)
+        {
+            return String.Join(", ", modules.Select(m => m.SummaryString));
+        }
+        
+        public static string DescribeTraitOrder(IEnumerable<Module> modules)
+        {
+            return DescribeUnsorted(OrderByTraits(modules));
+        }
+        public static string Describe(IEnumerable<Module> modules)
+        {
+            return DescribeUnsorted(OrderBySize(modules));
+        }
+
+        public static string DescribeUnsorted(IEnumerable<Module> modules)
+        {
+            StringBuilder sb = new StringBuilder();
+
+            sb.AppendLine($"{modules.Count()} modules: {Module.SummarizeUnsorted(modules)}");
+            sb.AppendLine();
+
+            int mi = 0;
+            foreach (var module in modules)
+            {
+                sb.AppendLine($"#{++mi}: {module.SummaryString}");
+                sb.AppendLine(string.Join(", ", module.Motivators));
+                sb.AppendLine(string.Join(", ", module.Motivatees));
+                sb.AppendLine();
+            }
+
+            return sb.ToString();
+        }
+        
+        public static string DescribeUnsortedCompact(IEnumerable<Module> modules)
+        {
+            StringBuilder sb = new StringBuilder();
+
+            sb.AppendLine($"{modules.Count()} modules: {Module.SummarizeUnsorted(modules)}");
+
+            int mi = 0;
+            foreach (var module in modules)
+            {
+                sb.Append($"#{++mi}: ");
+                sb.Append(string.Join(", ", module.Motivators));
+                sb.Append("; ");
+                sb.Append(string.Join(", ", module.Motivatees));
+                sb.AppendLine();
+            }
+
+            return sb.ToString();
+        }
+    }
+
+    public class DtmInfo
+    {
+        public Linear.Matrix<double> Matrix { get; }
+        public IReadOnlyCollection<Module> Modules { get; }
+
+        public DtmInfo(Linear.Matrix<double> matrix, IReadOnlyCollection<Module> modules)
+        {
+            Matrix = matrix;
+            Modules = modules;
+        }
+
+        public string Summary => Module.Summarize(Modules);
+        public string Description => Module.Describe(Modules);
+    }
+
+    public class DtmGraphNode : IComparable<DtmGraphNode>
+    {
+        public ExpansionFunction<DtmGraphNode> Forward = dgn => dgn._motivatees.Select(e => new Step<DtmGraphNode>(e, 1));
+        public ExpansionFunction<DtmGraphNode> Backward = dgn => dgn._motivators.Select(e => new Step<DtmGraphNode>(e, 1));
+
+        public int Index { get; }
+
+        private List<DtmGraphNode> _motivatees = new List<DtmGraphNode>();
+        public IReadOnlyList<DtmGraphNode> Motivatees => _motivatees;
+
+        private List<DtmGraphNode> _motivators = new List<DtmGraphNode>();
+        public IReadOnlyList<DtmGraphNode> Motivators => _motivators;
+
+        public DtmGraphNode(int index)
+        {
+            Index = index;
+        }
+
+        public void Motivates(DtmGraphNode other)
+        {
+            _motivatees.Add(other);
+            other._motivators.Add(this);
+        }
+
+        public IEnumerable<DtmGraphNode> EnumerateAllMotivators()
+        {
+            return AStar<DtmGraphNode>.Expand(Motivators, Backward, _ => 0f).Select(p => p.GraphNode);
+        }
+
+        public IEnumerable<DtmGraphNode> EnumerateAllMotivatees()
+        {
+            return AStar<DtmGraphNode>.Expand(_motivatees, Forward, _ => 0f).Select(p => p.GraphNode);
+        }
+
+        public int CompareTo(DtmGraphNode other)
+        {
+            return Index.CompareTo(other.Index);
+        }
+    }
+
+    public class DtmGraph
+    {
+        public int Size => Traits.Count;
+        public IReadOnlyList<DtmGraphNode> Traits { get; }
+
+        public DtmGraph(Linear.Matrix<double> dtm, double absThreshold)
+        {
+            var traits  = new List<DtmGraphNode>();
+            Traits = traits;
+
+            int s = Math.Max(dtm.ColumnCount, dtm.RowCount);
+            for (int i = 0; i < s; i++)
+            {
+                traits.Add(new DtmGraphNode(i));
+            }
+
+            for (int c = 0; c < dtm.ColumnCount; c++)
+            {
+                for (int r = 0; r < dtm.RowCount; r++)
+                {
+                    if (Math.Abs(dtm[r, c]) >= absThreshold)
+                    {
+                        traits[c].Motivates(traits[r]);
+                    }
+                }
+            }
+        }
+    }
+
+    public class DtmClassification
+    {
+        public static IReadOnlyCollection<Motivator> ThresholdMotivators(Linear.Matrix<double> dtm, double absThreshold)
+        {
+            List<Motivator> motivators = new List<Motivator>();
+
+            var motivatees = new List<int>();
+
+            for (int c = 0; c < dtm.ColumnCount; c++)
+            {
+                motivatees.Clear();
+
+                for (int r = 0; r < dtm.RowCount; r++)
+                {
+                    if (Math.Abs(dtm[r, c]) >= absThreshold)
+                        motivatees.Add(r);
+                }
+
+                if (motivatees.Count > 0)
+                    motivators.Add(new Motivator(c, motivatees));
+            }
+
+            return motivators;
+        }
+
+        public static IReadOnlyCollection<IReadOnlyCollection<Motivator>> ClusterMotivators(IEnumerable<Motivator> motivators)
+        {
+            Dictionary<int, Motivator> table = motivators.ToDictionary(m => m.Index);
+
+            DataStructures.DisjointSets<Motivator> djs = new DataStructures.DisjointSets<Motivator>();
+
+            foreach (var motivator in motivators)
+            {
+                djs.Add(motivator);
+            }
+
+            foreach (var motivator in motivators)
+            {
+                foreach (var motivatee in motivator.Motivatees)
+                {
+                    if (table.TryGetValue(motivatee, out var other))
+                        djs.Join(motivator, other);
+                }
+            }
+
+            return djs.EnumerateSets().Select(m => m.ToArray()).ToArray();
+        }
+
+        public static IReadOnlyCollection<Module> DiscerneTrueModules(Linear.Matrix<double> dtm, double absThreshold)
+        {
+            DtmGraph dg = new DtmGraph(dtm, absThreshold);
+            return DiscerneTrueModules(dg);
+        }
+        
+        /// <summary>
+        /// A 'true module' is a collection of traits driven by the same set of 'true motivators'
+        /// A 'true motivator' is a trait which has a feedback loop to itself
+        /// </summary>
+        /// <param name="dg"></param>
+        /// <returns></returns>
+        public static IReadOnlyCollection<Module> DiscerneTrueModules(DtmGraph dg)
+        {
+            // find true motivators
+            HashSet<DtmGraphNode> trueMotivators = new HashSet<DtmGraphNode>();
+
+            foreach (DtmGraphNode trait in dg.Traits)
+            {
+                if (trait.EnumerateAllMotivators().Contains(trait))
+                    trueMotivators.Add(trait);
+            }
+
+            // group by true motivators
+            Dictionary<ComparableList<DtmGraphNode>, List<DtmGraphNode>> groups = new Dictionary<ComparableList<DtmGraphNode>, List<DtmGraphNode>>();
+            List<DtmGraphNode> lonelys = new List<DtmGraphNode>();
+
+            foreach (DtmGraphNode trait in dg.Traits)
+            {
+                var motivators = trait.EnumerateAllMotivators().Where(pm => trueMotivators.Contains(pm)).ToArray();
+
+                if (motivators.Length == 0)
+                {
+                    lonelys.Add(trait);
+                }
+                else
+                {
+                    var cm = new ComparableList<DtmGraphNode>(motivators);
+
+                    if (groups.TryGetValue(cm, out var list))
+                    {
+                        list.Add(trait);
+                    }
+                    else
+                    {
+                        list = new List<DtmGraphNode> { trait };
+                        groups.Add(cm, list);
+                    }
+                }
+            }
+
+            // now we start accumulating
+            List<Module> modules = new List<Module>();
+
+            // accumulate lonelys
+            int[] empty = new int[0];
+            foreach (var lonely in lonelys)
+            {
+                modules.Add(new Module(empty, new[] { lonely.Index }));
+            }
+
+            // assemble and accumulate others
+            foreach (var grp in groups)
+            {
+                var motivators = grp.Key;
+                var motivatees = grp.Value;
+
+                List<DtmGraphNode> roots = new List<DtmGraphNode>();
+
+                foreach (var motivator in motivators)
+                {
+                    var allMotivators = new HashSet<DtmGraphNode>(motivator.EnumerateAllMotivators());
+                    var allMotivatees = new HashSet<DtmGraphNode>(motivator.EnumerateAllMotivatees());
+
+                    if (allMotivators.IsSubsetOf(allMotivatees))
+                        roots.Add(motivator);
+                }
+
+                modules.Add(new Module(roots.Select(r => r.Index), motivatees.Select(m => m.Index)));
+            }
+
+            return modules.OrderByDescending(m => m).ToArray();
+        }
+
+        // this one is iffy
+        public static IReadOnlyCollection<Module> DiscerneModulesOld(IEnumerable<Motivator> motivators)
+        {
+            var clusteredModules = ClusterMotivators(motivators);
+
+            List<Module> modules = new List<Module>();
+
+            foreach (var cm in clusteredModules)
+            {
+                // build graph
+                Dictionary<int, GraphNode<Motivator>> forwardNodes = cm.Select(m => new GraphNode<Motivator>(m)).ToDictionary(n => n.Payload.Index);
+                Dictionary<int, GraphNode<Motivator>> backwardNodes = cm.Select(m => new GraphNode<Motivator>(m)).ToDictionary(n => n.Payload.Index);
+
+                foreach (var motivator in cm)
+                {
+                    var fn = forwardNodes[motivator.Index];
+                    var bn = backwardNodes[motivator.Index];
+
+                    foreach (var motivatee in motivator.Motivatees)
+                    {
+                        if (forwardNodes.TryGetValue(motivatee, out var of))
+                            fn.JoinTo(of, 1);
+                        if (backwardNodes.TryGetValue(motivatee, out var ob))
+                            ob.JoinTo(bn, 1);
+                    }
+                }
+
+                // find root nodes
+                List<Motivator> roots = new List<Motivator>();
+
+                foreach (var motivator in cm)
+                {
+                    var fn = forwardNodes[motivator.Index];
+                    var bn = backwardNodes[motivator.Index];
+
+                    var motivates = new HashSet<Motivator>(fn.Expand().Select(n => n.GraphNode.Payload));
+                    var motivatedBy = new HashSet<Motivator>(bn.Expand().Select(n => n.GraphNode.Payload));
+
+                    if (motivatedBy.IsSubsetOf(motivates))
+                        roots.Add(motivator);
+                }
+
+                // assemble module
+                var motivatees = motivators.SelectMany(m => m.Motivatees).Distinct();
+                Module module = new Module(roots.Select(r => r.Index), motivatees);
+
+                modules.Add(module);
+            }
+
+            return modules;
+        }
+
+        public static string Type1Classify(Linear.Matrix<double> dtm, double thresholdFactor = 10.0)
+        {
+            if (dtm.Enumerate().Max(e => Math.Abs(e)) < 0.1)
+                return "type1nothing";
+
+            double threshold = ComputeAutoThreshold(dtm, thresholdFactor);
+            var dtmInfo = new DtmInfo(dtm, DtmClassification.DiscerneTrueModules(dtm, threshold));
+
+            return Type1Classify(dtmInfo, threshold);
+        }
+
+        public static string Type1Classify(DtmInfo dtmInfo, double threshold)
+        {
+            var dtm = dtmInfo.Matrix;
+
+            if (dtmInfo.Modules.Count == 1)
+            {
+                var sole = dtmInfo.Modules.First();
+
+                // need to check it's a 1/4 or a 2/4, and whether it's +ve or -ve
+                if (sole.Motivators.Count > 2)
+                    return "type1dense";
+
+                int pc = dtm.Enumerate().Count(e => e > threshold);
+                int nc = dtm.Enumerate().Count(e => e < -threshold);
+
+                if (pc > nc * 3) // p4 distinction: over 3/4 of correlations must be positive
+                {
+                    return "type1p4columnpositive";
+                }
+                else
+                {
+                    return "type1p4columnnegative";
+                }
+            }
+            else if (dtmInfo.Modules.Count > 1)
+            {
+                // can't compare summaries... they are good for me to look at as a screening, but they are not reliable enough
+
+                if (dtmInfo.Modules.All(m => m.Motivators.Count == 1) &&
+                    dtmInfo.Modules.All(m => m.Motivatees.Count >= 2) &&
+                    dtmInfo.Modules.All(m => dtmInfo.Modules.All(o => m == o || !m.Motivators.Intersect(o.Motivators).Any())) &&
+                    dtmInfo.Modules.All(m => dtmInfo.Modules.All(o => m == o || !m.Motivatees.Intersect(o.Motivatees).Any()))
+                    )
+                {
+                    // all distinct, and have one leader leading more than one, and more than one of them, so it must be hierarchy
+                    return "type1husky" + dtmInfo.Modules.Count;
+                }
+            }
+
+            return "type1notsure";
+        }
+
+        public static double ComputeAutoThreshold(Linear.Matrix<double> dtm, double thresholdFactor)
+        {
+            return dtm.Enumerate().Max(e => Math.Abs(e)) / thresholdFactor;
+        }
+    }
+
+    public class DtmTimeClassification
+    {
+        public DtmTimeClassification(long time, DtmInfo dtmInfo)
+        {
+            Time = time;
+            DtmInfo = dtmInfo;
+        }
+
+        public long Time { get; }
+        public DtmInfo DtmInfo { get; }
+        
+        /// <summary>
+        /// Notes: returns the same matrix each time (reuse)
+        /// </summary>
+        /// <param name="trajectories"></param>
+        /// <param name="samplePeriod"></param>
+        /// <param name="startTime"></param>
+        /// <param name="thresholdFactor"></param>
+        /// <param name="observationDelay"></param>
+        /// <returns></returns>
+        public static IEnumerable<DtmTimeClassification> ClassifyTrajectories(double[][] trajectories, int samplePeriod, long startTime, double thresholdFactor = 10.0, int observationDelay = 5)
+        {
+            long time = startTime;
+
+            int N = (int)Math.Round(Math.Sqrt(trajectories.Length));
+
+            long lastTime = -1;
+            int hcount = 0;
+            DtmInfo lastDtmAnalysis = null;
+            foreach (var dtm in Analysis.EnumerateMatricies(trajectories, N, N, 0, trajectories[0].Length, true))
+            {
+                // classify
+                var dtmInfo = new DtmInfo(dtm, DtmClassification.DiscerneTrueModules(dtm, DtmClassification.ComputeAutoThreshold(dtm, thresholdFactor)));
+                
+                if (lastDtmAnalysis == null || lastDtmAnalysis.Summary != dtmInfo.Summary)
+                {
+                    // if we are different, then note it
+                    lastTime = time;
+                    lastDtmAnalysis = dtmInfo;
+                    hcount = 0;
+                }
+                else if (lastDtmAnalysis.Summary == dtmInfo.Summary)
+                {
+                    // if we are the same, then note it
+                    hcount++;
+
+                    if (hcount == observationDelay)
+                    {
+                        yield return new DtmTimeClassification(lastTime, lastDtmAnalysis);
+                    }
+                }
+                
+                time += samplePeriod;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/BBNK.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/BBNK.cs
new file mode 100644
index 0000000..1289570
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/BBNK.cs
@@ -0,0 +1,516 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using System.Diagnostics;
+using M4M.State;
+using MathNet.Numerics.Random;
+
+namespace M4M.Epistatics
+{
+    public class CbbnkKindVectorTarget : VectorTarget
+    {
+        public CbbnkKindVectorTarget(int N, int K, double ω, double ψ, Linear.Vector<double> target, string friendlyName)
+            : base(target, friendlyName, new CbbnkKindVectorTargetJudger(N, K, ω, ψ), false)
+        {
+            Debug.Assert(N * K == target.Count, $"Epistatics/CbbnkKindVectorTarget.ctor(Vector<double>) target vector must have length N*K={N * K}");
+            Debug.Assert(ω != 1.0, $"Epistatics/CbbnkKindVectorTarget.ctor(Vector<double>) ω must not be 1");
+        }
+
+        public CbbnkKindVectorTarget(int N, int K, double ω, double ψ, double[] target, string friendlyName)
+            : base(target, friendlyName, new CbbnkKindVectorTargetJudger(N, K, ω, ψ), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/CbbnkKindVectorTarget.ctor(double[]) target array must have length N*K={N * K}");
+            Debug.Assert(ω != 1.0, $"Epistatics/CbbnkKindVectorTarget.ctor(double[]) ω must not be 1");
+        }
+
+        public CbbnkKindVectorTarget(int N, int K, double ω, double ψ, string target, string friendlyName)
+            : base(target, friendlyName, new CbbnkKindVectorTargetJudger(N, K, ω, ψ), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/CbbnkKindVectorTarget.ctor(string) target string '{target}' must have length N*K={N * K}");
+            Debug.Assert(ω != 1.0, $"Epistatics/CbbnkKindVectorTarget.ctor(string) ω must not be 1");
+        }
+    }
+
+    [StateClass]
+    public class CbbnkKindVectorTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected CbbnkKindVectorTargetJudger()
+        { }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+        
+        public string Description => $"CbbnkKindVectorTargetJudger, Module Count={N}, Module Size={K}, Slow cut-off ω={ω}, Slow gradient ψ={ψ}";
+
+        public CbbnkKindVectorTargetJudger(int N, int K, double ω, double ψ)
+        {
+            this.N = N;
+            this.K = K;
+            this.ω = ω;
+            this.ψ = ψ;
+
+            Name = $"CbbnkKindVectorTargetJudger(N={N}, K={K}, ω={ω}, ψ={ψ})";
+        }
+
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        [SimpleStateProperty("ω")]
+        public double ω { get; private set; }
+
+        [SimpleStateProperty("ψ")]
+        public double ψ { get; private set; }
+
+        public double Judge(Linear.Vector<Double> target, Phenotype phenotype)
+        {
+            double fitnessAcc = 0;
+
+            for (int mi = 0; mi < N * K; mi += K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + K; i++)
+                    acc += phenotype[i] * target[i];
+
+                acc /= K;
+
+                double moduleFitness;
+                if (acc > ω) // ideal
+                {
+                    moduleFitness = ω * ψ + (Math.Abs(acc) - ω) * (1 - ω * ψ) / (1 - ω);
+                }
+                else
+                {
+                    moduleFitness = Math.Abs(acc) * ψ;
+                }
+
+                fitnessAcc += moduleFitness;
+            }
+
+            return fitnessAcc / N;
+        }
+    }
+
+    [StateClass]
+    public class CbbnkKindTarget : ITarget
+    {
+        [Obsolete]
+        private CbbnkKindTarget()
+        { }
+
+        [SimpleStateProperty("FriendlyName")]
+        public string FriendlyName { get; private set; }
+
+        [SimpleStateProperty("FullName")]
+        public string FullName { get; private set; }
+        
+        /// <summary>
+        /// A detailed description of the target
+        /// </summary>
+        public virtual string Description => $"Cbbnk Kind Target, Size={Size}, Number of Modules={N}, Module Size={K}, Slow cut-off ω={ω}, Slow gradient ψ={ψ}";
+
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        /// <summary>
+        /// Number of modules (NOT Size)
+        /// </summary>
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        /// <summary>
+        /// Module dimension
+        /// </summary>
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        /// <summary>
+        /// Slow cut-off
+        /// </summary>
+        [SimpleStateProperty("ω")]
+        public double ω { get; private set; }
+
+        /// <summary>
+        /// Slow gradient
+        /// </summary>
+        [SimpleStateProperty("ψ")]
+        public double ψ { get; private set; }
+
+        public CbbnkKindTarget(int N, int K, double ω, double ψ, string friendlyName)
+        {
+            this.N = N;
+            this.K = K;
+            this.ω = ω;
+            this.ψ = ψ;
+
+            Size = N * K;
+            FriendlyName = friendlyName;
+            FullName = $"{nameof(CbbnkKindTarget)} {friendlyName}, N={N}, K={K}, ω={ω}, ψ={ψ}";
+        }
+
+        public double Judge(Phenotype phenotype)
+        {
+            double fitnessAcc = 0;
+
+            for (int mi = 0; mi < N * K; mi += K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + K; i++)
+                    acc += phenotype[i];
+
+                acc /= K;
+
+                double moduleFitness;
+                if (acc > ω) // ideal
+                {
+                    moduleFitness = ω * ψ + (Math.Abs(acc) - ω) * (1 - ω * ψ) / (1 - ω);
+                }
+                else
+                {
+                    moduleFitness = Math.Abs(acc) * ψ;
+                }
+
+                fitnessAcc += moduleFitness;
+            }
+
+            return fitnessAcc / N;
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            return false;
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return false;
+        }
+    }
+
+    [StateClass]
+    public class CbbnkCruelVectorTarget : VectorTarget
+    {
+        [Obsolete]
+        protected CbbnkCruelVectorTarget()
+        { }
+
+        public CbbnkCruelVectorTarget(int N, int K, double ω, double ψ, Linear.Vector<double> target, string friendlyName)
+            : base(target, friendlyName, new CbbnkCruelVectorTargetJudger(N, K, ω, ψ), false)
+        {
+            Debug.Assert(N * K == target.Count, $"Epistatics/CbbnkCruelVectorTarget.ctor(Vector<double>) target vector must have length N*K={N * K}");
+        }
+
+        public CbbnkCruelVectorTarget(int N, int K, double ω, double ψ, double[] target, string friendlyName)
+            : base(target, friendlyName, new CbbnkCruelVectorTargetJudger(N, K, ω, ψ), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/CbbnkCruelVectorTarget.ctor(double[]) target array must have length N*K={N * K}");
+        }
+
+        public CbbnkCruelVectorTarget(int N, int K, double ω, double ψ, string target, string friendlyName)
+            : base(target, friendlyName, new CbbnkCruelVectorTargetJudger(N, K, ω, ψ), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/CbbnkCruelVectorTarget.ctor(string) target string '{target}' must have length N*K={N * K}");
+        }
+    }
+
+    [StateClass]
+    public class CbbnkCruelVectorTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected CbbnkCruelVectorTargetJudger()
+        { }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+        
+        public string Description => $"CbbnkCruelVectorTargetJudger, Module Count={N}, Module Size={K}, Slow cut-off ω={ω}, Slow gradient ψ={ψ}";
+
+        public CbbnkCruelVectorTargetJudger(int N, int K, double ω, double ψ)
+        {
+            this.N = N;
+            this.K = K;
+            this.ω = ω;
+            this.ψ = ψ;
+
+            Name = $"CbbnkCruelVectorTargetJudger(N={N}, K={K}, ω={ω}, ψ={ψ})";
+        }
+
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        [SimpleStateProperty("ω")]
+        public double ω { get; private set; }
+
+        [SimpleStateProperty("ψ")]
+        public double ψ { get; private set; }
+
+        public double Judge(Linear.Vector<Double> target, Phenotype phenotype)
+        {
+            double fitnessAcc = 0;
+
+            for (int mi = 0; mi < N * K; mi += K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + K; i++)
+                    acc += phenotype[i] * target[i];
+
+                acc /= K;
+
+                double moduleFitness;
+                if (acc > ω) // ideal
+                {
+                    moduleFitness = 1.0;
+                }
+                else
+                {
+                    moduleFitness = Math.Abs(acc) * ψ;
+                }
+
+                fitnessAcc += moduleFitness;
+            }
+
+            return fitnessAcc / N;
+        }
+    }
+
+    [StateClass]
+    public class CbbnkCruelTarget : ITarget
+    {
+        [Obsolete]
+        protected CbbnkCruelTarget()
+        { }
+        
+        [SimpleStateProperty("FriendlyName")]
+        public string FriendlyName { get; private set; }
+        
+        [SimpleStateProperty("FullName")]
+        public string FullName { get; private set; }
+        
+        /// <summary>
+        /// A detailed description of the target
+        /// </summary>
+        public virtual string Description => $"Cbbnk Cruel Target, Size={Size}, Number of Modules={N}, Module Size={K}, Slow cut-off ω={ω}, Slow gradient ψ={ψ}";
+
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        /// <summary>
+        /// Number of modules (NOT Size)
+        /// </summary>
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        /// <summary>
+        /// Module dimension
+        /// </summary>
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        /// <summary>
+        /// Slow cut-off
+        /// </summary>
+        [SimpleStateProperty("ω")]
+        public double ω { get; private set; }
+
+        /// <summary>
+        /// Slow gradient
+        /// </summary>
+        [SimpleStateProperty("ψ")]
+        public double ψ { get; private set; }
+
+        public CbbnkCruelTarget(int N, int K, double ω, double ψ, string friendlyName)
+        {
+            this.N = N;
+            this.K = K;
+            this.ω = ω;
+            this.ψ = ψ;
+
+            Size = N * K;
+            FriendlyName = friendlyName;
+            FullName = $"{nameof(CbbnkCruelTarget)} {friendlyName}, N={N}, K={K}, ω={ω}, ψ={ψ}";
+        }
+
+        public double Judge(Phenotype phenotype)
+        {
+            double fitnessAcc = 0;
+
+            for (int mi = 0; mi < N * K; mi += K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + K; i++)
+                    acc += phenotype[i];
+
+                acc /= K;
+
+                double moduleFitness;
+                if (acc > ω) // ideal
+                {
+                    moduleFitness = 1.0;
+                }
+                else
+                {
+                    moduleFitness = Math.Abs(acc) * ψ;
+                }
+
+                fitnessAcc += moduleFitness;
+            }
+
+            return fitnessAcc / N;
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            return false;
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return false;
+        }
+    }
+
+    [StateClass]
+    public class CbbnkStepVectorTarget : VectorTarget
+    {
+        [Obsolete]
+        protected CbbnkStepVectorTarget() : base()
+        { }
+
+        public CbbnkStepVectorTarget(int N, int K, double ω, double ψ, double ξ, Linear.Vector<double> target, string friendlyName)
+            : base(target, friendlyName, new CbbnkStepVectorTargetJudger(N, K, ω, ψ, ξ), false)
+        {
+            Debug.Assert(N * K == target.Count, $"Epistatics/CbbnkStepVectorTarget.ctor(Vector<double>) target vector must have length N*K={N * K}");
+        }
+
+        public CbbnkStepVectorTarget(int N, int K, double ω, double ψ, double ξ, double[] target, string friendlyName)
+            : base(target, friendlyName, new CbbnkStepVectorTargetJudger(N, K, ω, ψ, ξ), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/CbbnkStepVectorTarget.ctor(double[]) target array must have length N*K={N * K}");
+        }
+
+        public CbbnkStepVectorTarget(int N, int K, double ω, double ψ, double ξ, string target, string friendlyName)
+            : base(target, friendlyName, new CbbnkStepVectorTargetJudger(N, K, ω, ψ, ξ), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/CbbnkStepVectorTarget.ctor(string) target string '{target}' must have length N*K={N * K}");
+        }
+    }
+    
+    [StateClass]
+    public class CbbnkStepVectorTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected CbbnkStepVectorTargetJudger()
+        { }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        public string Description => $"CbbnkStepVectorTargetJudger, Module Count={N}, Module Size={K}, Slow cut-off ω={ω}, Slow gradient ψ={ψ}, Step ξ={ξ}";
+
+        public CbbnkStepVectorTargetJudger(int N, int K, double ω, double ψ, double ξ)
+        {
+            this.N = N;
+            this.K = K;
+            this.ω = ω;
+            this.ψ = ψ;
+            this.ξ = ξ;
+
+            Name = $"CbbnkStepVectorTargetJudger(N={N}, K={K}, ω={ω}, ψ={ψ}, ξ={ξ})";
+        }
+        
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        /// <summary>
+        /// Step Offset
+        /// </summary>
+        [SimpleStateProperty("ω")]
+        public double ω { get; private set; }
+
+        /// <summary>
+        /// Slow Gradient
+        /// </summary>
+        [SimpleStateProperty("ψ")]
+        public double ψ { get; private set; }
+
+        /// <summary>
+        /// Step size
+        /// </summary>
+        [SimpleStateProperty("ξ")]
+        public double ξ { get; private set; }
+
+        public double Judge(Linear.Vector<Double> target, Phenotype phenotype)
+        {
+            double fitnessAcc = 0;
+
+            for (int mi = 0; mi < N * K; mi += K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + K; i++)
+                    acc += phenotype[i] * target[i];
+
+                acc /= K;
+
+                double moduleFitness;
+                if (acc > ω) // ideal
+                {
+                    moduleFitness = Math.Abs(acc) * ψ + ξ;
+                }
+                else
+                {
+                    moduleFitness = Math.Abs(acc) * ψ;
+                }
+
+                fitnessAcc += moduleFitness;
+            }
+
+            return fitnessAcc / N;
+        }
+    }
+    
+    public class BBNKTargetPackages
+    {
+        // Step
+        public static EpistaticTargetPackage CbbnkStep4x4Notvariable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkStep4x4P{ω}_{ψ}_{ξ}_2", new[] { new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "----++++++++----", "LHHL"), new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "----++++++++----", "LHHL") });
+        public static EpistaticTargetPackage CbbnkStep2x2Variable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkCruel2x2P{ω}_{ψ}_{ξ}_4MVG", new[] { new CbbnkStepVectorTarget(2, 2, ω, ψ, ξ, "++++", "HH"), new CbbnkStepVectorTarget(2, 2, ω, ψ, ξ, "++--", "HL"), new CbbnkStepVectorTarget(2, 2, ω, ψ, ξ, "----", "LL"), new CbbnkStepVectorTarget(2, 2, ω, ψ, ξ, "--++", "LH") });
+        public static EpistaticTargetPackage CbbnkStep2x3Variable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkCruel2x3P{ω}_{ψ}_{ξ}_4MVG", new[] { new CbbnkStepVectorTarget(2, 3, ω, ψ, ξ, "++++++", "HH"), new CbbnkStepVectorTarget(2, 3, ω, ψ, ξ, "+++---", "HL"), new CbbnkStepVectorTarget(2, 3, ω, ψ, ξ, "------", "LL"), new CbbnkStepVectorTarget(2, 3, ω, ψ, ξ, "---+++", "LH") });
+        public static EpistaticTargetPackage CbbnkStep3x2Variable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkStep3x2P{ω}_{ψ}_{ξ}_3MVG", new[] { new CbbnkStepVectorTarget(3, 2, ω, ψ, ξ, "++++--", "HHL"), new CbbnkStepVectorTarget(3, 2, ω, ψ, ξ, "++--++", "HLH"), new CbbnkStepVectorTarget(3, 2, ω, ψ, ξ, "--++++", "LHH") });
+        public static EpistaticTargetPackage CbbnkStep3x3Variable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkStep3x3P{ω}_{ψ}_{ξ}_3MVG", new[] { new CbbnkStepVectorTarget(3, 3, ω, ψ, ξ, "++++++---", "HHL"), new CbbnkStepVectorTarget(3, 3, ω, ψ, ξ, "+++---+++", "HLH"), new CbbnkStepVectorTarget(3, 3, ω, ψ, ξ, "---++++++", "LHH") });
+        public static EpistaticTargetPackage CbbnkStep4x4Variable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkStep4x4P{ω}_{ψ}_{ξ}_3MVG", new[] { new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "----++++++++----", "LHHL"), new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "++++----++++----", "HLHL"), new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "++++++++--------", "HHLL") });
+        public static EpistaticTargetPackage CbbnkStep4x4SemiVariable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkStep4x4P{ω}_{ψ}_{ξ}_2SemiMVG", new[] { new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "++++++++--------", "HHLL"), new CbbnkStepVectorTarget(4, 4, ω, ψ, ξ, "++++++++++++++++", "HHHH") });
+        public static EpistaticTargetPackage CbbnkStep5x4Variable(double ω, double ψ, double ξ) => new EpistaticTargetPackage($"CbbnkStep5x4P{ω}_{ψ}_{ξ}_3MVG", new[] { new CbbnkStepVectorTarget(4, 5, ω, ψ, ξ, "-----++++++++++-----", "LHHL"), new CbbnkStepVectorTarget(4, 5, ω, ψ, ξ, "+++++-----+++++-----", "HLHL"), new CbbnkStepVectorTarget(4, 5, ω, ψ, ξ, "++++++++++----------", "HHLL") });
+
+        // Kind
+        public static EpistaticTargetPackage CbbnkKind3x3Variable(double ω, double ψ) => new EpistaticTargetPackage("CbbnkKind3x3P" + ω + "_" + ψ + "_3MVG", new[] { new CbbnkKindVectorTarget(3, 3, ω, ψ, "---++++++", "LHH"), new CbbnkKindVectorTarget(3, 3, ω, ψ, "+++---+++", "HLH"), new CbbnkKindVectorTarget(3, 3, ω, ψ, "++++++---", "HHL") });
+        public static EpistaticTargetPackage CbbnkKind2x2Variable(double ω, double ψ) => new EpistaticTargetPackage("CbbnkKind2x2P" + ω + "_" + ψ + "_4MVG", new[] { new CbbnkKindVectorTarget(2, 2, ω, ψ, "++++", "HH"), new CbbnkKindVectorTarget(2, 2, ω, ψ, "++--", "HL"), new CbbnkKindVectorTarget(2, 2, ω, ψ, "----", "LL"), new CbbnkKindVectorTarget(2, 2, ω, ψ, "--++", "LH") });
+
+        // Cruel
+        public static EpistaticTargetPackage CbbnkCruel4x4(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel4x4P" + ω + "_" + ψ + "_2", new[] { new CbbnkCruelTarget(4, 4, ω, ψ, "Cruel2x2"), new CbbnkCruelTarget(4, 4, ω, ψ, "Cruel2x2") });
+        public static EpistaticTargetPackage CbbnkCruel3x3(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel3x3P" + ω + "_" + ψ + "_2", new[] { new CbbnkCruelTarget(3, 3, ω, ψ, "Cruel2x2"), new CbbnkCruelTarget(3, 3, ω, ψ, "Cruel2x2") });
+        public static EpistaticTargetPackage CbbnkCruel2x2(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel2x2P" + ω + "_" + ψ + "_2", new[] { new CbbnkCruelTarget(2, 2, ω, ψ, "Cruel2x2"), new CbbnkCruelTarget(2, 2, ω, ψ, "Cruel2x2") });
+
+        public static EpistaticTargetPackage CbbnkCruel2x2NonVariable(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel2x2P" + ω + "_" + ψ + "_2NVG", new[] { new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++++", "HH"),  new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++++", "HH") });
+        
+        public static EpistaticTargetPackage CbbnkCruel2x2Variable(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel2x2P" + ω + "_" + ψ + "_4MVG", new[] { new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++++", "HH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++--", "HL"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "----", "LL"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "--++", "LH") });
+
+        public static EpistaticTargetPackage CbbnkCruel2x2VariablePositiveBias(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel2x2P" + ω + "_" + ψ + "_6MVGPB", new[] { new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++++", "HH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++--", "HL"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "----", "LL"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++++", "HH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "--++", "LH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "----", "LL") });
+        public static EpistaticTargetPackage CbbnkCruel2x2VariableNegativeBias(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel2x2P" + ω + "_" + ψ + "_6MVGNB", new[] { new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++++", "HH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++--", "HL"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "--++", "LH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "----", "LL"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "--++", "LH"), new CbbnkCruelVectorTarget(2, 2, ω, ψ, "++--", "HL") });
+
+        public static EpistaticTargetPackage CbbnkCruel3x2Variable(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel3x2P" + ω + "_" + ψ + "_3MVG", new[] { new CbbnkCruelVectorTarget(3, 2, ω, ψ, "++++--", "HHL"), new CbbnkCruelVectorTarget(3, 2, ω, ψ, "++--++", "HLH"), new CbbnkCruelVectorTarget(3, 2, ω, ψ, "--++++", "LHH") });
+
+        public static EpistaticTargetPackage CbbnkCruel2x3Variable(double ω, double ψ) => new EpistaticTargetPackage("CbbnkCruel2x3P" + ω + "_" + ψ + "_4MVG", new[] { new CbbnkCruelVectorTarget(2, 3, ω, ψ, "++++++", "HH"), new CbbnkCruelVectorTarget(2, 3, ω, ψ, "+++---", "HL"), new CbbnkCruelVectorTarget(2, 3, ω, ψ, "------", "LL"), new CbbnkCruelVectorTarget(2, 3, ω, ψ, "---+++", "LH") });
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/BinaryPuzzleTarget.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/BinaryPuzzleTarget.cs
new file mode 100644
index 0000000..aa597f8
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/BinaryPuzzleTarget.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using MathNet.Numerics.Random;
+
+namespace M4M.Epistatics
+{
+    [State.StateClass]
+    public class BinaryPuzzleTarget : ITarget
+    {
+        [Obsolete]
+        protected BinaryPuzzleTarget()
+        { }
+
+        public BinaryPuzzleTarget(double[] targetVector, int width, int height, double relativeConstraintCost, double relativeLineConstraintCost)
+        {
+            if (targetVector.Length != width * height)
+                throw new ArgumentException($"Target vector must have legnth width*height = {width * height}");
+
+            TargetVector = targetVector;
+            Width = width;
+            Height = height;
+            RelativeConstraintCost = relativeConstraintCost;
+            RelativeLineConstraintCost = relativeLineConstraintCost;
+        }
+
+        public int Size => Width * Height;
+        public string FriendlyName => "BinaryPuzzleTarget" + Size;
+        public string FullName => "BinaryPuzzleTarget" + Size;
+        public string Description => $"BinaryPuzzleTarget (W={Width}, H={Height}, Target={String.Join(";", TargetVector)}, RCC={RelativeConstraintCost}, RLCC={RelativeLineConstraintCost})";
+
+        [State.SimpleStateProperty("Width")]
+        public int Width { get; private set; }
+
+        [State.SimpleStateProperty("Height")]
+        public int Height { get; private set; }
+
+        [State.SimpleStateProperty("TargetVector")]
+        public double[] TargetVector { get; private set; }
+
+        [State.SimpleStateProperty("RelativeConstraintCost")]
+        public double RelativeConstraintCost { get; private set; }
+
+        [State.SimpleStateProperty("RelativeLineConstraintCost")]
+        public double RelativeLineConstraintCost { get; private set; }
+
+        public double Judge(Phenotype phenotype)
+        {
+            var w = Width;
+            var h = Height;
+            double p(int r, int c) => phenotype[r * w + c];
+
+            // target
+            double fitness = 0.0;
+            for (int i = 0; i < phenotype.Size; i++)
+                fitness += phenotype[i] * TargetVector[i];
+
+            // no-threes in a row
+            for (int r = 0; r < h; r++)
+            {
+                for (int c = 0; c < w; c++)
+                {
+                    if (r > 0 && r < h - 1)
+                    {
+                        var bad = Math.Sign(p(r - 1, c)) == Math.Sign(p(r, c)) && Math.Sign(p(r + 1, c)) == Math.Sign(p(r, c));
+                        var diff = RelativeConstraintCost * (Math.Abs(p(r - 1, c)) + Math.Abs(p(r, c)) + Math.Abs(p(r + 1, c)));
+                        if (bad)
+                            fitness -= diff;
+                        else
+                            fitness += diff;
+                    }
+
+                    if (c > 0 && c < w - 1)
+                    {
+                        var bad = Math.Sign(p(r, c - 1)) == Math.Sign(p(r, c)) && Math.Sign(p(r, c + 1)) == Math.Sign(p(r, c));
+                        var diff = RelativeConstraintCost * (Math.Abs(p(r, c - 1)) + Math.Abs(p(r, c)) + Math.Abs(p(r, c + 1)));
+                        if (bad)
+                            fitness -= diff;
+                        else
+                            fitness += diff;
+                    }
+                }
+            }
+
+            // parity rows
+            int rs1 = h % 2;
+            for (int r = 0; r < h; r++)
+            {
+                int n = 0;
+                double sum = 0.0;
+
+                for (int c = 0; c < w; c++)
+                {
+                    var v = p(r, c);
+                    sum += Math.Abs(v);
+                    if (v > 0)
+                        n++;
+                    else
+                        n--;
+                }
+
+                fitness -= sum * (Math.Abs(n) - rs1);
+            }
+
+            // parity columns
+            int cs1 = w % 2;
+            for (int c = 0; c < w; c++)
+            {
+                int n = 0;
+                double sum = 0.0;
+
+                for (int r = 0; r < h; r++)
+                {
+                    var v = p(r, c);
+                    sum += Math.Abs(v);
+                    if (v > 0)
+                        n++;
+                    else
+                        n--;
+                }
+
+                fitness -= sum * (Math.Abs(n) - cs1);
+            }
+
+            return fitness;
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return false;
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            return false;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/CorrelationLandscape.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/CorrelationLandscape.cs
new file mode 100644
index 0000000..3e37565
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/CorrelationLandscape.cs
@@ -0,0 +1,531 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using System.Diagnostics;
+using M4M.State;
+
+namespace M4M.Epistatics
+{
+    public enum NeighbourType
+    {
+        // combinations
+        One = 1,
+        Four = 2,
+        Five = 3,
+        Eight = 6,
+        Nine = 7,
+
+        // flags
+        Self = 1,
+        Square = 2,
+        Cross = 4,
+    }
+
+    public delegate bool CorrelationFilter(int rows, int cols, MatrixEntryAddress e0, MatrixEntryAddress e1);
+    public delegate void DrDcFunc(int rows, int cols, MatrixEntryAddress e0, MatrixEntryAddress e1, out int dr, out int dc);
+    public delegate bool DrDcFilter(int dr, int dc);
+
+    public static class Neighbours
+    {
+        public static bool SquareDrDcFilter(int dr, int dc) => dr + dc == 1;
+        public static bool CrossDrDcFilter(int dr, int dc) => dr == 1 && dc == 1;
+        public static bool SelfDrDcFilter(int dr, int dc) => dr == 0 && dc == 0;
+
+        public static void NonTorusDrDc(int rows, int cols, MatrixEntryAddress e0, MatrixEntryAddress e1, out int dr, out int dc)
+        {
+            dr = Math.Abs(e0.Row - e1.Row);
+            dc = Math.Abs(e0.Col - e1.Col);
+        }
+
+        public static void TorusDrDc(int rows, int cols, MatrixEntryAddress e0, MatrixEntryAddress e1, out int dr, out int dc)
+        {
+            dr = Math.Abs(e0.Row - e1.Row);
+            dc = Math.Abs(e0.Col - e1.Col);
+
+            if (dr * 2 > rows)
+                dr = rows - dr;
+            if (dc * 2 > cols)
+                dc = cols - dc;
+        }
+
+        /// <summary>
+        /// Filters pairs of MatrixEntryAddresses to those which are the same entry, or are 4-neighbours of each other
+        /// Provided for convience
+        /// </summary>
+        public static bool FiveNeighbourFilter(int rows, int cols, MatrixEntryAddress e0, MatrixEntryAddress e1)
+        {
+            int dr = Math.Abs(e0.Row - e1.Row);
+            int dc = Math.Abs(e0.Col - e1.Col);
+
+            return dr + dc <= 1; // only self-connections and direct (4) neighbours
+        }
+
+        public static NeighbourType ParseNeightbourType(string str)
+        {
+            switch (str.ToLowerInvariant())
+            {
+                case "four":
+                    return NeighbourType.Four;
+                case "five":
+                    return NeighbourType.Five;
+                case "eight":
+                    return NeighbourType.Eight;
+                case "nine":
+                    return NeighbourType.Nine;
+            }
+
+            if (int.TryParse(str, out var i))
+                return (NeighbourType)i;
+
+            throw new ArgumentException($"Unrecognised neighbour type \"{str}\"");
+        }
+
+        public static CorrelationFilter GetNeightbourFilter(NeighbourType neighbourType, bool torus)
+        {
+            DrDcFunc drDcFunc = torus ? (DrDcFunc)TorusDrDc: NonTorusDrDc;
+
+            List<DrDcFilter> filters = new List<DrDcFilter>();
+            if ((neighbourType & NeighbourType.Self) > 0)
+                filters.Add(SelfDrDcFilter);
+            if ((neighbourType & NeighbourType.Square) > 0)
+                filters.Add(SquareDrDcFilter);
+            if ((neighbourType & NeighbourType.Cross) > 0)
+                filters.Add(CrossDrDcFilter);
+
+            return (rows, cols, e0, e1) =>
+            {
+                drDcFunc(rows, cols, e0, e1, out var dr, out var dc);
+                foreach (var f in filters)
+                    if (f(dr, dc))
+                        return true;
+
+                return false;
+            };
+        }
+    }
+
+    public interface IMatrixPattern
+    {
+        string Name { get; }
+        Linear.Matrix<double> Generate(int rows, int cols);
+    }
+
+    public class FlatPattern : IMatrixPattern
+    {
+        public static readonly FlatPattern Instance = new FlatPattern();
+
+        public string Name => "Flat";
+
+        public Linear.Matrix<double> Generate(int rows, int cols)
+        {
+            return Linear.CreateMatrix.Dense<double>(rows, cols, (i, j) => 1.0);
+        }
+    }
+
+    public class CheckPattern : IMatrixPattern
+    {
+        public static readonly CheckPattern Instance = new CheckPattern();
+
+        public string Name => "Check";
+
+        public Linear.Matrix<double> Generate(int rows, int cols)
+        {
+            return Linear.CreateMatrix.Dense<double>(rows, cols, (i, j) => (i + j) % 2 == 0 ? 1.0 : -1.0);
+        }
+    }
+
+    public class ConcentricSquaresPattern : IMatrixPattern
+    {
+        public static readonly ConcentricSquaresPattern Instance = new ConcentricSquaresPattern();
+
+        public string Name => "ConcentricSquares";
+
+        public Linear.Matrix<double> Generate(int rows, int cols)
+        {
+            int halfv = rows / 2;
+            int halfh = cols / 2;
+
+            double p(int r, int c)
+            {
+                int v = r >= halfv ? rows - r - 1 : r;
+                int h = c >= halfh ? cols - c - 1 : c;
+
+                int m = Math.Min(v, h);
+                return m % 2 == 0 ? +1.0 : -1.0;
+            }
+
+            return Linear.CreateMatrix.Dense<double>(rows, cols, p);
+        }
+    }
+
+    public static class MatrixPatterns
+    {
+        public static Dictionary<string, IMatrixPattern> Patterns = new Dictionary<string, IMatrixPattern>(StringComparer.InvariantCultureIgnoreCase);
+
+        static MatrixPatterns()
+        {
+            RegisterPattern(new FlatPattern());
+            RegisterPattern(new CheckPattern());
+            RegisterPattern(new ConcentricSquaresPattern());
+        }
+
+        public static void RegisterPattern(IMatrixPattern pattern)
+        {
+            Patterns.Add(pattern.Name, pattern);
+        }
+
+        public static IMatrixPattern GetPattern(string name)
+        {
+            if (Patterns.TryGetValue(name, out var found))
+                return found;
+            else
+                throw new ArgumentException($"No pattern with name \"{name}\"", nameof(name));
+        }
+    }
+
+    [StateClass]
+    public class CorrelationMatrixTarget : ITarget
+    {
+        [MatrixStateProperty("CorrelationMatrix")]
+        public Linear.Matrix<double> CorrelationMatrix { get; private set; }
+
+        [MatrixStateProperty("NoisyMatrix")]
+        private Linear.Matrix<double> NoisyMatrix { get; set; }
+
+
+        [VectorStateProperty("ForcingVector")]
+        public Linear.Vector<double> ForcingVector { get; private set; }
+
+        public int Size => CorrelationMatrix.RowCount;
+
+        [SimpleStateProperty("NormaliseFitness")]
+        public bool NormaliseFitness { get; private set; } = true; // default (legacy)
+
+        [SimpleStateProperty("FriendlyName")]
+        public string FriendlyName { get; private set; }
+        [SimpleStateProperty("FullName")]
+        public string FullName { get; private set; }
+        
+        public string Description => $"CorrelationMatrixTarget, Size={Size}";
+
+        [Obsolete]
+        protected CorrelationMatrixTarget()
+        {
+        }
+
+        public CorrelationMatrixTarget(Linear.Matrix<double> correlationMatrix, string friendlyName, bool normaliseFitness, Linear.Vector<double> forcingVector = null)
+        {
+            Debug.Assert(correlationMatrix.RowCount == correlationMatrix.ColumnCount, $"Epistatics/CorrelationMatrixTarget.ctor: correlationMatrix must be square");
+            if (forcingVector != null)
+                Debug.Assert(correlationMatrix.RowCount == forcingVector.Count, $"Epistatics/CorrelationMatrixTarget.ctor: forcingVector must have the same dimension as the correlationMatrix");
+
+            CorrelationMatrix = correlationMatrix;
+            ForcingVector = forcingVector;
+
+            FriendlyName = friendlyName;
+            NormaliseFitness = normaliseFitness;
+            FullName = friendlyName;
+        }
+
+        public double Judge(Phenotype p)
+        {
+            var pv = p.Vector;
+
+            Debug.Assert(NoisyMatrix != null, $"Epistatics/CorrelationMatrixTarget.Judge NoisyMatrix must be set!!! call NextGeneration(rand, jrules) before each generation");
+            Debug.Assert(p.Size == Size, $"Epistatics/CorrelationMatrixTarget.Judge (size = {p.Size}) must be the same size as the Target (size = {Size})");
+
+            double forcingBenefit = 0.0;
+            if (ForcingVector != null)
+            {
+                forcingBenefit = pv.DotProduct(ForcingVector);
+            }
+
+            double provisional = NoisyMatrix.LeftMultiply(pv).DotProduct(pv);
+            if (NormaliseFitness)
+                return 0.5 + (provisional / (Size * Size)) / 2.0 + forcingBenefit;
+            else
+                return provisional + forcingBenefit;
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            // reset NoisyMatrix
+            if (NoisyMatrix == null)
+            {
+                NoisyMatrix = CorrelationMatrix.Clone();
+            }
+            else
+            {
+                CorrelationMatrix.CopyTo(NoisyMatrix);
+            }
+
+            // if κ != 0, then add gaussian noise to NoisyVector
+            if (jrules.NoiseFactor != 0)
+            {
+                NoisyMatrix.MapInplace(d => d + MathNet.Numerics.Distributions.Normal.Sample(rand, 0, 1) * jrules.NoiseFactor, Linear.Zeros.Include);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return false;
+        }
+    }
+
+    public struct Pair<T>
+    {
+        public Pair(T a, T b)
+        {
+            A = a;
+            B = b;
+        }
+
+        public T A { get; }
+        public T B { get; }
+    }
+
+    public static class StandardCorrelationMatrixTargets
+    {
+        public static IEnumerable<MatrixEntryAddress> EnumerateUnhappyCorrelations(Linear.Matrix<double> constraintMatrix, Linear.Vector<double> vector)
+        {
+            foreach (var e in MatrixEntryAddress.CompleteLazy(constraintMatrix.RowCount, constraintMatrix.ColumnCount))
+            {
+                var correlation = constraintMatrix[e.Row, e.Col];
+                if (correlation != 0.0)
+                {
+                    var vr = vector[e.Row];
+                    var vc = vector[e.Col];
+
+                    if (Math.Sign(vr * vc) != Math.Sign(correlation))
+                        yield return e;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Creates a LinearLocalConstraints correlation matrix target.
+        /// </summary>
+        /// <param name="width">One dimension.</param>
+        /// <param name="height">The other dimension.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        public static CorrelationMatrixTarget LinearLocalConstraints(int width, int height, IMatrixPattern matrixPattern, NeighbourType neighbourType, bool torus, bool normaliseFitness)
+        {
+            Linear.Matrix<double> pattern = matrixPattern.Generate(height, width);
+
+            var correlationMatrix = CreateCorrelationMatrix(pattern, Neighbours.GetNeightbourFilter(neighbourType, torus));
+            return new CorrelationMatrixTarget(correlationMatrix, $"Linear{matrixPattern.Name}Local{neighbourType}Constraints{width}x{height}{(torus ? "Torus" : "")}{(normaliseFitness ? "Nrm" : "")}", normaliseFitness);
+        }
+
+        /// <summary>
+        /// Creates a ConcentricSquares correlation matrix target.
+        /// </summary>
+        /// <param name="size">The dimension of the (square) pattern matrix.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        public static CorrelationMatrixTarget ConcentricSquares(int size, bool normaliseFitness)
+        {
+            int half = size / 2;
+
+            double p(int r, int c)
+            {
+                int v = r >= half ? size - r - 1 : r;
+                int h = c >= half ? size - c - 1 : c;
+
+                int m = Math.Min(v, h);
+                return m % 2 == 0 ? +1.0 : -1.0;
+            }
+
+            Linear.Matrix<double> pattern = Linear.CreateMatrix.Dense<double>(size, size, p);
+
+            var correlationMatrix = CreateCorrelationMatrix(pattern, Neighbours.FiveNeighbourFilter);
+            return new CorrelationMatrixTarget(correlationMatrix, "ConcentricSquares" + size + (normaliseFitness ? "Nrm" : ""), normaliseFitness);
+        }
+
+        /// <summary>
+        /// Creates a PartialConcentricSquares correlation matrix target, where each constraint is removed with probability pruneProbability.
+        /// </summary>
+        /// <param name="size">The dimension of the (square) pattern matrix.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        /// <param name="pruneProbability">The probability of pruning each entry in the problem matrix.</param>
+        /// <param name="rand">The random source to use.</param>
+        public static CorrelationMatrixTarget PartialConcentricSquares(int size, bool normaliseFitness, double pruneProbability, RandomSource rand)
+        {
+            var cs = ConcentricSquares(size, normaliseFitness);
+            var prunedMatrix = Misc.Prune(rand, cs.CorrelationMatrix, pruneProbability);
+            return new CorrelationMatrixTarget(prunedMatrix, "ConcentricSquares" + size + (normaliseFitness ? "Nrm" : "") + "Partial" + pruneProbability, normaliseFitness);
+        }
+
+        /// <summary>
+        /// Creates a random contraint correlation matrix target.
+        /// </summary>
+        /// <param name="size">The dimension of the correlation matrix.</param>
+        /// <param name="offDiagonalMag">The maximum magnitude of the off-diagonal matrix elements</param>
+        /// <param name="noiseType">The type of noise on the off-diagonal matrix elements</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        public static CorrelationMatrixTarget RandomInconsistentConstraintTarget(RandomSource rand, int size, double offDiagonalMag, NoiseType noiseType, bool normaliseFitness)
+        {
+            double corr(int r, int c)
+            {
+                if (r == c)
+                    return 1.0; // 1 on the diagonal
+
+                return Misc.NextNoise(rand, offDiagonalMag, noiseType);
+            }
+
+            var constraintMatrix = Linear.CreateMatrix.Dense<double>(size, size, corr);
+            return new CorrelationMatrixTarget(constraintMatrix, "RandomInconsistent" + size + (normaliseFitness ? "Nrm" : ""), normaliseFitness);
+        }
+
+        /// <summary>
+        /// Creates a 'consistent' correlation matrix from the given matrix pattern.
+        /// Only correlations which pass the filter will be included in the (n*m)*(n*m) correlation matrix.
+        /// </summary>
+        /// <param name="pattern">The n*m pattern matrix to correlate.</param>
+        /// <param name="filter">The correlation filter.</param>
+        /// <returns></returns>
+        public static Linear.Matrix<double> CreateCorrelationMatrix(Linear.Matrix<double> pattern, CorrelationFilter filter)
+        {
+            int width = pattern.ColumnCount;
+            int size = pattern.ColumnCount * pattern.RowCount;
+
+            double corr(int r, int c)
+            {
+                var e0 = new MatrixEntryAddress(r / width, r % width);
+                var e1 = new MatrixEntryAddress(c / width, c % width);
+
+                if (filter(pattern.RowCount, pattern.ColumnCount, e0, e1))
+                {
+                    return pattern[e0.Row, e0.Col] * pattern[e1.Row, e1.Col];
+                }
+                else
+                {
+                    return 0.0;
+                }
+            }
+
+            var mat = Linear.CreateMatrix.Dense<double>(size, size, corr);
+            return mat;
+        }
+
+        /// <summary>
+        /// Creates a ContinousHiff correlation matrix target.
+        /// </summary>
+        /// <param name="levels">The number of levels.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        /// <param name="downFactor">The factor by which constraints are multiplied each level.</param>
+        /// <param name="chaff">Whether to 'checkerboard' the matrix.</param>
+        public static CorrelationMatrixTarget ContinuousHiff(int levels, bool normaliseFitness, double downFactor, bool chaff)
+        {
+            int size = 1 << (levels);
+
+            Linear.Matrix<double> chiff = Linear.CreateMatrix.Dense<double>(size, size, 0.0);
+
+            double p = 1.0;
+            int k = 1;
+
+            for (int l = 0; l < levels + 1; l++)
+            {
+                for (int i = 0; i < size; i += k)
+                {
+                    for (int r = i; r < i + k; r++)
+                    {
+                        for (int c = i; c < i + k; c++)
+                        {
+                            if (chiff[r, c] == 0.0)
+                                chiff[r, c] = p;
+                        }
+                    }
+                }
+
+                if (l != 0)
+                    p *= downFactor;
+                k *= 2;
+            }
+
+            if (chaff)
+            {
+                for (int r = 0; r < size; r++)
+                {
+                    for (int c = 0; c < size; c++)
+                    {
+                        if ((r + c) % 2 == 1)
+                            chiff[r, c] = -chiff[r, c];
+                    }
+                }
+            }
+
+            return new CorrelationMatrixTarget(chiff, "ContinuousHiff" + size + "DF" + downFactor, normaliseFitness);
+        }
+
+        /// <summary>
+        /// Creates a Flat correlation matrix target.
+        /// Diagonal has all ones. Everything else has value agreement.
+        /// </summary>
+        /// <param name="size">The width and height of the matrix.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        /// <param name="agreement">The non-diagonal entry value.</param>
+        public static CorrelationMatrixTarget Flat(int size, bool normaliseFitness, double agreement)
+        {
+            Linear.Matrix<double> flat = Linear.CreateMatrix.Dense<double>(size, size, (i, j) => i == j ? 1.0 : agreement);
+            return new CorrelationMatrixTarget(flat, "Flat" + size + "Ag" + agreement, normaliseFitness);
+        }
+
+        /// <summary>
+        /// Creates a Funky MC correlation matrix target.
+        /// </summary>
+        /// <param name="ascendingModuleCount">The number of 'ascending' modules.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        /// <param name="agreement">The inter-module interaction strength.</param>
+        public static CorrelationMatrixTarget FunkyMcAscending(int ascendingModuleCount, bool normaliseFitness, double agreement)
+        {
+            var assignments = new List<List<int>>();
+            int o = 0;
+            for (int i = 1; i <= ascendingModuleCount; i++)
+            {
+                var module = new List<int>();
+                for (int j = 0; j < i ; j++)
+                {
+                    module.Add(o++);
+                }
+                assignments.Add(module);
+            }
+            var modules = new Modular.Modules(assignments);
+            return FunkyMc(modules, normaliseFitness, agreement);
+        }
+
+        /// <summary>
+        /// Creates a Funky MC correlation matrix target.
+        /// </summary>
+        /// <param name="modules">The modules.</param>
+        /// <param name="normaliseFitness">Whether to normalise fitness.</param>
+        /// <param name="agreement">The inter-module interaction strength.</param>
+        public static CorrelationMatrixTarget FunkyMc(Modular.Modules modules, bool normaliseFitness, double agreement)
+        {
+            int size = modules.ElementCount;
+            Linear.Matrix<double> funkymc = Linear.CreateMatrix.Dense<double>(size, size, agreement);
+
+            foreach (var module in modules.ModuleAssignments)
+            {
+                for (int i = 0; i < module.Count; i++)
+                {
+                    int r = module[i];
+                    for (int j = 0; j < module.Count; j++)
+                    {
+                        int c = module[j];
+                        funkymc[r,c] = 1.0;
+                    }
+                }
+            }
+
+            return new CorrelationMatrixTarget(funkymc, "FunkyMC" + modules?.ToString() + "Ag" + agreement, normaliseFitness);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/Epistatics.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/Epistatics.cs
new file mode 100644
index 0000000..37fd4d3
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/Epistatics.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using System.Diagnostics;
+using M4M.State;
+
+namespace M4M.Epistatics
+{
+    public interface IModuleBenefitFunction
+    {
+        string Name { get; }
+        double ComputeModuleBenefit(double moduleAvg, double cminus, double cplus);
+    }
+
+    public static class ModuleBenefitFunctions
+    {
+        public static IModuleBenefitFunction ParseModuleBenefitFunction(string name)
+        {
+            if (name.Equals("proper", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return new ProperModuleBenefitFunction();
+            }
+            else if (name.Equals("split", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return new SplitModuleBenefitFunction();
+            }
+
+            throw new ArgumentException("Unrecognised ModuleBenefitFunction: " + name);
+        }
+    }
+
+    [StateClass]
+    public class ProperModuleBenefitFunction : IModuleBenefitFunction
+    {
+        public ProperModuleBenefitFunction()
+        {
+        }
+
+        public string Name => "ProperModuleBenefitFunction";
+
+        public double ComputeModuleBenefit(double acc, double cminus, double cplus)
+        {
+            double modulesBenefit = 0.0;
+
+            double positiveness = 0.5 + acc * 0.5;
+            modulesBenefit += positiveness * positiveness * cplus;
+
+            double negativeness = 0.5 - acc * 0.5;
+            modulesBenefit += negativeness * negativeness * cminus;
+
+            return modulesBenefit;
+        }
+    }
+
+    [StateClass]
+    public class SplitModuleBenefitFunction : IModuleBenefitFunction
+    {
+        public SplitModuleBenefitFunction()
+        {
+        }
+
+        public string Name => "SplitModuleBenefitFunction";
+
+        public double ComputeModuleBenefit(double acc, double cminus, double cplus)
+        {
+            double moduleBenefit = acc > 0
+                ? acc * cplus
+                : -acc * cminus;
+
+            return moduleBenefit;
+        }
+    }
+
+    public class EpistaticTargetPackage : ITargetPackage
+    {
+        public EpistaticTargetPackage(string name, ITarget[] targets)
+        {
+            Debug.Assert(targets != null, $"Epistatics/EpistaticTargetPackage.ctor targets must be set");
+
+            int size = targets[0].Size;
+            Debug.Assert(targets.All(t => t.Size == size), $"Epistatics/EpistaticTargetPackage.ctor targets must all be the same size");
+
+            Name = name;
+            Targets = targets;
+        }
+
+        /// <summary>
+        /// The name of the target package
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// The targets in the target package
+        /// </summary>
+        public ITarget[] Targets { get; }
+
+        /// <summary>
+        /// The Size of the targets
+        /// </summary>
+        public int TargetSize => Targets[0].Size;
+
+        IReadOnlyList<ITarget> ITargetPackage.Targets => Targets;
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/HTOP.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/HTOP.cs
new file mode 100644
index 0000000..46fc8dc
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/HTOP.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using M4M.State;
+using MathNet.Numerics.Random;
+
+namespace M4M.Epistatics
+{
+    [StateClass]
+    public class Type1DHTOPTarget : ITarget
+    {
+        [Obsolete]
+        protected Type1DHTOPTarget()
+        {
+            // nix
+        }
+
+        public Type1DHTOPTarget(int size)
+        {
+            if ((int)Math.Round(Math.Pow(2, Math.Log(size, 2))) != size)
+                throw new Exception("Invalid Type1DHTOP Target Size");
+
+            Size = size;
+        }
+
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        public string FriendlyName => "Type1DHTOP" + Size;
+
+        public string FullName => "Type1DHTOP" + Size;
+        
+        public string Description => $"Type1 DHTOP Target, Size={Size}";
+
+        public double Judge(Phenotype p)
+        {
+            double f = 0.0;
+
+            int l = Size;
+            int max = 0;
+            while (l >= 4)
+            {
+                max = 1 + max * 2;
+                ReduceInplace(p.Vector.Select(d => d > 0 ? 1 : -1).ToArray(), ref l, ref f); // not quite Sign (we don't want any zeros)
+            }
+
+            return (f / max) * (p.Vector.Sum(x => Math.Abs(x)) / Size);
+        }
+
+        private static void ReduceInplace(int[] arr, ref int l, ref double f)
+        {
+            for (int i = 0; i < l; i += 4)
+            {
+                bool nozeros = arr.Skip(i).Take(4).Count(x => x == 0) == 0;
+                bool oneone = arr.Skip(i).Take(4).Count(x => x > 0) == 1;
+                bool ok = nozeros && oneone;
+
+                if (ok)
+                {
+                    f++;
+                    arr[i/2+0] = arr[i+2] + arr[i+3];
+                    arr[i/2+1] = arr[i+1] + arr[i+1];
+                }
+                else
+                {
+                    f--;
+                    arr[i/2+0] = 0;
+                    arr[i/2+1] = 0;
+                }
+            }
+
+            l /= 2;
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            // no change
+            return false;
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return false;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/IVMC.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/IVMC.cs
new file mode 100644
index 0000000..faf2dfe
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/IVMC.cs
@@ -0,0 +1,1490 @@
+using M4M.State;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M.Epistatics
+{
+    [StateClass]
+    public class ConstantIvmc : VectorTarget
+    {
+        [Obsolete]
+        protected ConstantIvmc() : base()
+        { }
+
+        public ConstantIvmc(int N, int K, double V, Linear.Vector<double> target, bool[] lowModules, bool[] highModules, string friendlyName)
+            : base(target, friendlyName, new ConstantIvmcVectorTargetJudger(N, K, V, lowModules, highModules), false)
+        {
+            Debug.Assert(N * K == target.Count, $"Epistatics/ConstantIvmc.ctor(Vector<double>) target vector must have length N*K={N * K}");
+            Debug.Assert(K == lowModules.Length, $"Epistatics/ConstantIvmc.ctor(Vector<double>) low-modules vector must have length K={K}");
+            Debug.Assert(K == highModules.Length, $"Epistatics/ConstantIvmc.ctor(Vector<double>) high-modules vector must have length K={K}");
+        }
+
+        public ConstantIvmc(int N, int K, double V, string target, string lowModules, string highModules, string friendlyName)
+            : base(target, friendlyName, new ConstantIvmcVectorTargetJudger(N, K, V, lowModules, highModules), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/ConstantIvmc.ctor(string) target string must have length N*K={N * K}");
+            Debug.Assert(K == lowModules.Length, $"Epistatics/ConstantIvmc.ctor(string) low-modules string must have length K={K}");
+            Debug.Assert(K == highModules.Length, $"Epistatics/ConstantIvmc.ctor(string) high-modules string must have length K={K}");
+        }
+    }
+
+    [StateClass]
+    public class ConstantIvmcVectorTargetJudger : IVectorTargetJudger
+    {
+        public ConstantIvmcVectorTargetJudger(int n, int k, double v, string lowModules, string highModules)
+            : this(n, k, v,
+                  lowModules.Select(c => c == 'T' || c == 't' || c == '1').ToArray(),
+                  highModules.Select(c => c == 'T' || c == 't' || c == '1').ToArray())
+        {
+        }
+
+        public ConstantIvmcVectorTargetJudger(int n, int k, double v, bool[] lowModules, bool[] highModules)
+        {
+            N = n;
+            K = k;
+            V = v;
+
+            LowModules = lowModules;
+            HighModules = highModules;
+
+            string lms = string.Join("", LowModules.Select(enabled => enabled ? "1" : "0"));
+            string hms = string.Join("", HighModules.Select(enabled => enabled ? "1" : "0"));
+
+            Name = $"ConstantIvmcVectorTargetJudger(N={N}, K={K}, V={V}, Low={lms}, High={hms})";
+        }
+
+        [Obsolete]
+        protected ConstantIvmcVectorTargetJudger()
+        { }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        public string Description => $"ConstantIvmc VectorTargetJudger (Number of modules={N}, Modules size={K}, Highness bias={V}, LowModules={string.Join("", LowModules.Select(b => b ? "1" : "0"))}, HighModules={string.Join("", HighModules.Select(b => b ? "1" : "0"))})";
+
+        /// <summary>
+        /// Number of modules (NOT number of traits)
+        /// </summary>
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        /// <summary>
+        /// Module size
+        /// </summary>
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        /// <summary>
+        /// High-module fitness bias
+        /// </summary>
+        [SimpleStateProperty("V")]
+        public double V { get; private set; }
+
+        /// <summary>
+        /// Whether the low (by trait0 -) modules are enabled
+        /// </summary>
+        [SimpleStateProperty("LowModules")]
+        public IReadOnlyList<bool> LowModules { get; private set; }
+        
+        /// <summary>
+        /// Whether the high (by trait0 +) modules are enabled
+        /// </summary>
+        [SimpleStateProperty("HighModules")]
+        public IReadOnlyList<bool> HighModules { get; private set; }
+        
+        public double Judge(Linear.Vector<double> target, Phenotype phenotype)
+        {
+            double fitnessAcc = 0;
+
+            for (int mi = 0; mi < N * K; mi += K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + K; i++)
+                    acc += phenotype[i] * target[i];
+
+                acc /= K; // average
+                acc *= target[mi];
+                
+                double moduleFitness = 0.0;
+
+                if (HighModules[mi / K])
+                {
+                    double highbonus = target[mi] > 0 ? V : 1.0;
+                    double highness = 0.5 + acc * 0.5;
+                    moduleFitness += highness * highness * highbonus;
+                }
+                
+                if (LowModules[mi / K])
+                {
+                    double lowbonus = target[mi] < 0 ? V : 1.0;
+                    double lowness = 0.5 - acc * 0.5;
+                    moduleFitness += lowness * lowness * lowbonus;
+                }
+                
+                fitnessAcc += moduleFitness;
+            }
+
+            return fitnessAcc / N;
+        }
+    }
+    
+    // This class is badly named now.
+    // Sorry.
+    [StateClass]
+    public class ExpIvmc : VectorTarget
+    {
+        [Obsolete]
+        protected ExpIvmc() : base()
+        { }
+        
+        /// <summary>
+        /// Number of modules (NOT number of traits)
+        /// </summary>
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        /// <summary>
+        /// Module size
+        /// </summary>
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        /// <summary>
+        /// High-module fitness bias
+        /// </summary>
+        [SimpleStateProperty("V")]
+        public double V { get; private set; }
+        
+        /// <summary>
+        /// Dual Resource Probability
+        /// </summary>
+        [SimpleStateProperty("Z")]
+        public double Z { get; private set; }
+
+        /// <summary>
+        /// Whether the low (by trait0 -) modules are enabled
+        /// </summary>
+        [SimpleStateProperty("LowModules")]
+        public bool[] LowModules { get; private set; }
+        
+        /// <summary>
+        /// Whether the high (by trait0 +) modules are enabled
+        /// </summary>
+        [SimpleStateProperty("HighModules")]
+        public bool[] HighModules { get; private set; }
+
+        /// <summary>
+        /// Whether the high modules are inverted
+        /// </summary>
+        [SimpleStateProperty("HighModuleInversions")]
+        public bool[] HighModuleInversions { get; private set; }
+        
+        /// <summary>
+        /// The probability with which to change high modules invertions with each exposure
+        /// </summary>
+        [SimpleStateProperty("ChangeInversionProbability")]
+        public double ChangeInversionProbability { get; private set; }
+
+        public ExpIvmc(int N, int K, double V, double Z, double F, Linear.Vector<double> target, bool[] lowModules, bool[] highModules, string friendlyName, double changeInversionProbability)
+            : base(target, friendlyName, new ExpIvmcVectorTargetJudger(), false)
+        {
+            Debug.Assert(N * K == target.Count, $"Epistatics/ExpIvmc.ctor(Vector<double>) target vector must have length N*K={N * K}");
+            Debug.Assert(K == lowModules.Length, $"Epistatics/ExpIvmc.ctor(Vector<double>) low-modules vector must have length K={K}");
+            Debug.Assert(K == highModules.Length, $"Epistatics/ExpIvmc.ctor(Vector<double>) high-modules vector must have length K={K}");
+
+            ((ExpIvmcVectorTargetJudger)CustomJudger).Ivmc = this; // horrid
+
+            this.N = N;
+            this.K = K;
+            this.V = V;
+            this.Z = Z;
+            LowModules = lowModules;
+            HighModules = highModules;
+
+            ChangeInversionProbability = changeInversionProbability;
+            HighModuleInversions = new bool[N]; // (false)
+        }
+        
+        public ExpIvmc(int N, int K, double V, double Z, string target, string lowModules, string highModules, string friendlyName, double changeInversionProbability)
+            : base(target, friendlyName, new ExpIvmcVectorTargetJudger(), false)
+        {
+            Debug.Assert(N * K == target.Length, $"Epistatics/ExpIvmc.ctor(string) target string must have length N*K={N * K}");
+            Debug.Assert(K == lowModules.Length, $"Epistatics/ExpIvmc.ctor(string) low-modules string must have length K={K}");
+            Debug.Assert(K == highModules.Length, $"Epistatics/ExpIvmc.ctor(string) high-modules string must have length K={K}");
+
+            ((ExpIvmcVectorTargetJudger)CustomJudger).Ivmc = this; // horrid
+
+            this.N = N;
+            this.K = K;
+            this.V = V;
+            this.Z = Z;
+            LowModules = lowModules.Select(c => c == 'T' || c == 't' || c == '1').ToArray();
+            HighModules = highModules.Select(c => c == 'T' || c == 't' || c == '1').ToArray();
+
+            ChangeInversionProbability = changeInversionProbability;
+            HighModuleInversions = new bool[N]; // (false)
+        }
+        
+        public override bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            base.NextExposure(rand, jrules, epoch, ref exposureInformation);
+            
+            for (int i = 0; i < HighModules.Length; i++)
+            {
+                bool dual = rand.NextDouble() < Z;
+                bool high = rand.NextBoolean();
+
+                HighModules[i] = dual || high;
+                LowModules[i] = dual || (!high);
+            }
+
+            if (rand.NextDouble() < ChangeInversionProbability)
+            {
+                for (int i = 0; i < HighModuleInversions.Length; i++)
+                    HighModuleInversions[i] = rand.NextBoolean();
+            }
+
+            return true;
+        }
+    }
+    
+    [StateClass]
+    public class ExpIvmcVectorTargetJudger : IVectorTargetJudger
+    {
+        public ExpIvmcVectorTargetJudger(ExpIvmc expIvmc)
+        {
+            Ivmc = expIvmc;
+            Name = "ExpIvmcVectorTargetJudger";
+        }
+
+        public ExpIvmcVectorTargetJudger()
+        {
+            Ivmc = null;
+            Name = "ExpIvmcVectorTargetJudger";
+        }
+
+        [SimpleStateProperty("ExpIvmc")]
+        public ExpIvmc Ivmc { get; set; } // this is awful
+        
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+        
+        public string Description => "ExpIvmcVectorTargetJudger";
+
+        public double Judge(Linear.Vector<double> target, Phenotype phenotype)
+        {
+            double benefitAcc = 0;
+
+            for (int mi = 0; mi < Ivmc.N * Ivmc.K; mi += Ivmc.K)
+            {
+                double acc = 0;
+                for (int i = mi; i < mi + Ivmc.K; i++)
+                    acc += phenotype[i] * target[i];
+
+                acc /= Ivmc.K; // average
+                acc *= target[mi]; // TODO: FIXME: this is wrong... thankfully we've never varied... is it wrong?????? it doesn't look wrong...
+                
+                double moduleFitness = 0.0;
+
+                bool highIsV = Ivmc.HighModuleInversions == null // backwards compatibility
+                    ? target[mi / Ivmc.K] > 0
+                    : (target[mi / Ivmc.K] > 0) != Ivmc.HighModuleInversions[mi / Ivmc.K];
+                
+                if (Ivmc.HighModules[mi / Ivmc.K])
+                {
+                    double highbonus = highIsV ? Ivmc.V : 1.0;
+                    double highness = 0.5 + acc * 0.5;
+                    moduleFitness += highness * highness * highbonus;
+                }
+                
+                if (Ivmc.LowModules[mi / Ivmc.K])
+                {
+                    double lowbonus = highIsV ? 1.0 : Ivmc.V;
+                    double lowness = 0.5 - acc * 0.5;
+                    moduleFitness += lowness * lowness * lowbonus;
+                }
+                
+                benefitAcc += moduleFitness;
+            }
+
+            return benefitAcc / Ivmc.N;
+        }
+    }
+    
+    // ExpImvc is a nightmare in every way
+    // -> let's do this properly...
+
+    [StateClass]
+    public class IvmcProper
+    {
+        [Obsolete]
+        protected IvmcProper() : base()
+        { }
+
+        public IvmcProper(int moduleCount, int moduleSize) : this (moduleCount, moduleSize, new double[moduleCount], new double[moduleCount])
+        {
+        }
+
+        public IvmcProper(int moduleCount, int moduleSize, double[] cminus, double[] cplus)
+        {
+            Debug.Assert(moduleCount == cminus.Length, $"Epistatics/IvmcProper.ctor(int, int, double[], double[]) cminus must have length M={moduleCount}");
+            Debug.Assert(moduleCount == cplus.Length, $"Epistatics/IvmcProper.ctor(int, int, double[], double[]) cplus and cmonus must have length M={moduleCount}");
+
+            ModuleCount = moduleCount;
+            ModuleSize = moduleSize;
+            
+            Cminus = cminus;
+            Cplus = cplus;
+
+            Modules = null;
+        }
+
+        public IvmcProper(Modular.Modules modules) : this(modules, new double[modules.ModuleCount], new double[modules.ModuleCount])
+        {
+        }
+
+        public IvmcProper(Modular.Modules modules, double[] cminus, double[] cplus)
+        {
+            Debug.Assert(modules.ModuleCount == cminus.Length, $"Epistatics/IvmcProper.ctor(Modular.Modules, double[], double[]) cminus must have length M={ModuleCount}");
+            Debug.Assert(modules.ModuleCount == cplus.Length, $"Epistatics/IvmcProper.ctor(Modular.Modules, double[], double[]) cplus and cmonus must have length M={ModuleCount}");
+            
+            ModuleCount = modules.ModuleCount;
+            ModuleSize = -1;
+            
+            Cminus = cminus;
+            Cplus = cplus;
+
+            Modules = modules;
+        }
+
+        /// <summary>
+        /// Per-module Negative match fitnesses
+        /// </summary>
+        [SimpleStateProperty("Cminus")]
+        public double[] Cminus { get; private set; }
+
+        /// <summary>
+        /// Per-module Positive match fitnesses
+        /// </summary>
+        [SimpleStateProperty("Cplus")]
+        public double[] Cplus { get; private set; }
+        
+        /// <summary>
+        /// Module Count
+        /// defined for both BlockModules and FreeModules configuration
+        /// </summary>
+        [SimpleStateProperty("ModuleCount")]
+        public int ModuleCount { get; private set; }
+        
+        /// <summary>
+        /// Module Size
+        /// undefined if not BlockModules configuration
+        /// </summary>
+        [SimpleStateProperty("ModuleSize")]
+        private int ModuleSize { get; set; }
+
+        /// <summary>
+        /// Describes the Free modules in the FreeModules configuration
+        /// null if not FreeModules configuration
+        /// </summary>
+        [SimpleStateProperty("Modules")]
+        private Modular.Modules Modules { get; set; }
+
+        /// <summary>
+        /// Decomposes a BlockModules type IvmcProper
+        /// Throws if of the wrong ModuleType
+        /// </summary>
+        /// <param name="moduleCount">The number of modules</param>
+        /// <param name="moduleSize">The size of each module</param>
+        public void DecomposeBlockModules(out int moduleCount, out int moduleSize)
+        {
+            if (ModulesType != IvmcProperModulesType.BlockModules)
+                throw new Exception("Attempted to decompose an IvmcProper as BlockModules with differet ModuleType");
+
+            moduleCount = ModuleCount;
+            moduleSize = ModuleSize;
+        }
+
+        /// <summary>
+        /// Decomposes a FreeModules type IvmcProper
+        /// Throws if of the wrong ModuleType
+        /// </summary
+        /// <param name="modules">The Modules</param>
+        public void DecomposeFreeModules(out Modular.Modules modules)
+        {
+            if (ModulesType != IvmcProperModulesType.FreeModules)
+                throw new Exception("Attempted to decompose an IvmcProper as BlockModules with differet ModuleType");
+
+            modules = Modules;
+        }
+
+        /// <summary>
+        /// Returns the type of modules used by this IvmcProper
+        /// Use this information to call the appropriate Decompose method
+        /// </summary>
+        public IvmcProperModulesType ModulesType
+        {
+            get
+            {
+                // !!! switching on ModuleType
+                return Modules == null ? IvmcProperModulesType.BlockModules : IvmcProperModulesType.FreeModules;
+            }
+        }
+
+        /// <summary>
+        /// Retrieves the total number of elements
+        /// </summary>
+        public int ElementCount
+        {
+            get
+            {
+                // !!! switching on ModuleType
+                if (Modules != null)
+                    return Modules.ElementCount;
+                else
+                    return ModuleCount * ModuleSize;
+            }
+        }
+
+        /// <summary>
+        /// no-save
+        /// We use this to cache the result of a call to AsModules
+        /// </summary>
+        private Modular.Modules _generatedModulesCache;
+
+        /// <summary>
+        /// Retrieves a Modular.Modules representation of the Module Assignment
+        /// Supported for BlockModules and FreeModules
+        /// Result is cached
+        /// </summary>
+        public Modular.Modules ModulesAsFreeModules()
+        {
+            if (_generatedModulesCache == null)
+            {
+                // !!! switching on ModuleType
+                if (Modules != null)
+                {
+                    _generatedModulesCache = Modules;
+                }
+                else
+                { // BlockModules
+                    _generatedModulesCache = Modular.Modules.CreateBlockModules(ModuleCount, ModuleSize);
+                }
+            }
+
+            return _generatedModulesCache;
+        }
+    }
+
+    public enum IvmcProperModulesType
+    {
+        BlockModules = 0,
+        FreeModules = 1,
+    }
+
+    // TODO: obsolve this wherever possible
+    public enum IvmcProperJudgementMode
+    {
+        Proper = 0,
+        Split = 1,
+        StackedSplit = 3,
+        Stacked = 4,
+    }
+
+    public interface IIvmcProperJudgementPreparer
+    {
+        string Name { get; }
+        string Description { get; }
+        IVectorTargetJudger PrepareJudger(IvmcProper proper);
+    }
+
+    public class IvmcStackedProperJudgementPreparer : IIvmcProperJudgementPreparer
+    {
+        public IvmcStackedProperJudgementPreparer(IModuleBenefitFunction moduleBenefitFunction, double decayFactor, bool rescaleFitness, int scaleFactor)
+        {
+            ModuleBenefitFunction = moduleBenefitFunction ?? throw new ArgumentNullException(nameof(moduleBenefitFunction));
+            DecayFactor = decayFactor;
+            RescaleFitness = rescaleFitness;
+            ScaleFactor = scaleFactor;
+        }
+
+        public string Name => "Stacked" + ModuleBenefitFunction.Name;
+
+        public string Description => Name + "Df" + DecayFactor + "Rf" + RescaleFitness;
+
+        public IVectorTargetJudger PrepareJudger(IvmcProper proper)
+        {
+            return new IvmcStackedVectorTargetJudger(ModuleBenefitFunction, proper, DecayFactor, RescaleFitness, ScaleFactor);
+        }
+
+        public IModuleBenefitFunction ModuleBenefitFunction { get; }
+        public double DecayFactor { get; }
+        public bool RescaleFitness { get; }
+        public int ScaleFactor { get; }
+    }
+
+    public class TraditionalIvmcProperJudgementPreparer : IIvmcProperJudgementPreparer
+    {
+        public TraditionalIvmcProperJudgementPreparer(IvmcProperJudgementMode properJudgementMode)
+        {
+            ProperJudgementMode = properJudgementMode;
+        }
+
+        public IvmcProperJudgementMode ProperJudgementMode { get; }
+
+        public string Name => ProperJudgementMode.ToString();
+
+        public string Description => Name;
+
+        public IVectorTargetJudger PrepareJudger(IvmcProper proper)
+        {
+            switch (ProperJudgementMode)
+            {
+                case IvmcProperJudgementMode.Proper:
+                    return new IvmcProperVectorTargetJudger(proper);
+                case IvmcProperJudgementMode.Split:
+                    return new IvmcSplitVectorTargetJudger(proper);
+                case IvmcProperJudgementMode.StackedSplit:
+                    return new IvmcStackedVectorTargetJudger(new SplitModuleBenefitFunction(), proper);
+                default:
+                    throw new ArgumentException(nameof(ProperJudgementMode), "Illegal ProperJudgementMode (or non-Traditional)");
+            }
+        }
+
+        public static IvmcProperJudgementMode ParseTraditionalIvmcProperJudgementMode(string name)
+        {
+            if (string.Equals(name, "Proper", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return IvmcProperJudgementMode.Proper;
+            }
+            else if (string.Equals(name, "Split", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return IvmcProperJudgementMode.Split;
+            }
+            else if (string.Equals(name, "StackedSplit", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return IvmcProperJudgementMode.StackedSplit;
+            }
+            else if (string.Equals(name, "Stacked", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return IvmcProperJudgementMode.Stacked;
+            }
+            else if (int.TryParse(name, out int n))
+            {
+                // for backwards compatability
+                return (IvmcProperJudgementMode)n;
+            }
+            else
+            {
+                throw new ArgumentException($"Unrecognised Traditional ImvcProperJudgementMode \"{name}\"");
+            }
+        }
+    }
+
+    public interface IIvmcProperTarget
+    {
+        IvmcProper Proper { get; }
+    }
+
+    [StateClass]
+    public class IvmcProperSinusoid : VectorTarget, IIvmcProperTarget
+    {
+        private static string _ds(double[] darr) => string.Join(";", darr);
+
+        public override string Description => $"IvmcProperSinusoid (Size={Size}, NormaliseCustomJudger={NormaliseCustomJudger}, Proper.Modules={Proper.ModulesAsFreeModules().ToString()}, PlusPhases={_ds(PlusPhases)}, MinusPhases={_ds(MinusPhases)}, PlusAmps={_ds(PlusAmps)}, MinusAmps={_ds(MinusAmps)}, PlusFreqs={_ds(PlusFreqs)}, MinusFreqs={_ds(MinusFreqs)}, PlusMeans={_ds(PlusMeans)}, MinusMeans={_ds(MinusMeans)}), (CJ: {CustomJudger?.Description})";
+
+        [Obsolete]
+        protected IvmcProperSinusoid() : base()
+        { }
+
+        /// <summary>
+        /// My IvmcProper instance (seen by the custom judger)
+        /// </summary>
+        [SimpleStateProperty("Proper")]
+        public IvmcProper Proper { get; private set; }
+
+        [SimpleStateProperty("PlusPhases")]
+        public double[] PlusPhases { get; private set; }
+
+        [SimpleStateProperty("MinusPhases")]
+        public double[] MinusPhases { get; private set; }
+
+        [SimpleStateProperty("PlusAmps")]
+        public double[] PlusAmps { get; private set; }
+
+        [SimpleStateProperty("MinusAmps")]
+        public double[] MinusAmps { get; private set; }
+
+        [SimpleStateProperty("PlusFreqs")]
+        public double[] PlusFreqs { get; private set; }
+
+        [SimpleStateProperty("MinusFreqs")]
+        public double[] MinusFreqs { get; private set; }
+
+        [SimpleStateProperty("PlusMeans")]
+        public double[] PlusMeans { get; private set; }
+
+        [SimpleStateProperty("MinusMeans")]
+        public double[] MinusMeans { get; private set; }
+
+        private IvmcProperSinusoid(IIvmcProperJudgementPreparer properJudgementPreparer, Linear.Vector<double> target, string friendlyName, IvmcProper proper)
+            : base(target, friendlyName, properJudgementPreparer.PrepareJudger(proper), false)
+        {
+            Proper = proper;
+        }
+        
+        
+        public IvmcProperSinusoid(IIvmcProperJudgementPreparer properJudgementPreparer, Modular.Modules modules, double[] plusPhases, double[] minusPhases, double[] plusAmps, double[] minusAmps, double[] plusFreqs, double[] minusFreqs, double[] plusMeans, double[] minusMeans, Linear.Vector<double> target, string friendlyName)
+            : this(properJudgementPreparer, target, friendlyName, new IvmcProper(modules))
+        {
+            PlusPhases = plusPhases;
+            MinusPhases = minusPhases;
+            PlusAmps = plusAmps;
+            MinusAmps = minusAmps;
+            PlusFreqs = plusFreqs;
+            MinusFreqs = minusFreqs;
+            PlusMeans = plusMeans;
+            MinusMeans = minusMeans;
+        }
+
+        public override bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            base.NextExposure(rand, jrules, epoch, ref exposureInformation);
+
+            double twoPi = Math.PI * 2.0;
+
+            for (int mi = 0; mi < Proper.ModuleCount; mi++)
+            {
+                Proper.Cplus[mi] = PlusMeans[mi] + Math.Sin((epoch + PlusPhases[mi]) * PlusFreqs[mi] * twoPi) * PlusAmps[mi];
+                Proper.Cminus[mi] = MinusMeans[mi] + Math.Sin((epoch + MinusPhases[mi]) * MinusFreqs[mi] * twoPi) * MinusAmps[mi];
+            }
+
+            return true;
+        }
+        
+        public override Linear.Vector<double> PreparePerfectP()
+        {
+            if (Proper.ModulesType == IvmcProperModulesType.BlockModules)
+            {
+                Proper.DecomposeBlockModules(out int M, out int K);
+
+                // clone target vector
+                Linear.Vector<double> p = Vector.Clone();
+
+                // flip according to module conditions
+                for (int i = 0; i < M * K; i++)
+                {
+                    if (Proper.Cminus[i / K] > Proper.Cplus[i / K])
+                        p[i] = -p[i];
+                }
+
+                return p;
+            }
+            else if (Proper.ModulesType == IvmcProperModulesType.FreeModules)
+            {
+                Proper.DecomposeFreeModules(out var modules);
+
+                Linear.Vector<double> p = Vector.Clone();
+                
+                for (int i = 0; i < modules.ModuleCount; i++)
+                {
+                    if (Proper.Cminus[i] > Proper.Cplus[i])
+                    {
+                        foreach (var j in modules[i])
+                            p[j] = -p[j];
+                    }
+                }
+
+                return p;
+            }
+            else
+            {
+                throw new Exception("Unrecognised IvmcProperModulesType: " + Proper.ModulesType);
+            }
+        }
+    }
+
+    // the only 'problem' with this is that it is over-parameterised
+    [StateClass]
+    public class IvmcProperChanging : VectorTarget, IIvmcProperTarget
+    {
+        public override string Description => $"IvmcProper Changing Vector Target (Size={Size}, NormaliseCustomJudger={NormaliseCustomJudger}, CH={CH}, CL={CL}, C0={C0}, Z={Z}, Proper.Modules={Proper.ModulesAsFreeModules().ToString()}, VariationModules={VariationModules?.ToString()}, VariationModulePlusOverrides={VariationModuleOverridesString(VariationModulePlusOverrides)}, VariationModuleMinusOverrides={VariationModuleOverridesString(VariationModuleMinusOverrides)}) (CJ: {CustomJudger?.Description})";
+
+        private static string VariationModuleOverridesString(double[] vmos)
+        {
+            if (vmos == null)
+                return "";
+            return string.Join(";", vmos.Select(d => double.IsNaN(d) ? "_" : d.ToString()));
+        }
+
+        [Obsolete]
+        protected IvmcProperChanging() : base()
+        { }
+
+        /// <summary>
+        /// Number of modules
+        /// </summary>
+        [Obsolete]
+        [DeprecatedSimpleStateProperty("M")]
+        private int M
+        {
+            get
+            {
+                throw new InvalidOperationException("Property M is deprecated, and cannot be used for serialisation");
+            }
+            set
+            {
+                // use Proper instead
+            }
+        }
+
+        /// <summary>
+        /// Module size
+        /// </summary>
+        [Obsolete]
+        [DeprecatedSimpleStateProperty("K")]
+        private int K
+        {
+            get
+            {
+                throw new InvalidOperationException("Property K is deprecated, and cannot be used for serialisation");
+            }
+            set
+            {
+                // use Proper instead
+            }
+        }
+
+        /// <summary>
+        /// High-module fitness
+        /// </summary>
+        [SimpleStateProperty("CH")]
+        public double CH { get; private set; }
+
+        /// <summary>
+        /// Low-module fitness
+        /// </summary>
+        [SimpleStateProperty("CL")]
+        public double CL { get; private set; }
+
+        /// <summary>
+        /// No-module fitness
+        /// </summary>
+        [SimpleStateProperty("C0")]
+        public double C0 { get; private set; }
+
+        /// <summary>
+        /// Dual Resource Probability
+        /// </summary>
+        [SimpleStateProperty("Z")]
+        public double Z { get; private set; }
+
+        /// <summary>
+        /// My IvmcProper instance (seen by the custom judger)
+        /// </summary>
+        [SimpleStateProperty("Proper")]
+        public IvmcProper Proper { get; private set; }
+        
+        /// <summary>
+        /// The modules of modules which vary together
+        /// Null impies they all change independently
+        /// </summary>
+        [SimpleStateProperty("VariationModules")]
+        public Modular.Modules VariationModules { get; private set; }
+        
+        /// <summary>
+        /// Unchanging (overriden) variation module plus coefficiencients
+        /// </summary>
+        [SimpleStateProperty("VariationModulePlusOverrides")]
+        public double[] VariationModulePlusOverrides { get; private set; }
+        
+        /// <summary>
+        /// Unchanging (overriden) variation module minus coefficiencients
+        /// </summary>
+        [SimpleStateProperty("VariationModuleMinusOverrides")]
+        public double[] VariationModuleMinusOverrides { get; private set; }
+
+        private IvmcProperChanging(IIvmcProperJudgementPreparer properJudgementPreparer, Linear.Vector<double> target, string friendlyName, IvmcProper proper)
+            : base(target, friendlyName, properJudgementPreparer.PrepareJudger(proper), false)
+        {
+            Proper = proper;
+        }
+        
+        private IvmcProperChanging(IIvmcProperJudgementPreparer properJudgementPreparer, string target, string friendlyName, IvmcProper proper)
+            : base(target, friendlyName, properJudgementPreparer.PrepareJudger(proper), false)
+        {
+            Proper = proper;
+        }
+        
+        public IvmcProperChanging(IIvmcProperJudgementPreparer properJudgementPreparer, int M, int K, double CH, double CL, double C0, double Z, Linear.Vector<double> target, string friendlyName)
+            : this(properJudgementPreparer, target, friendlyName, new IvmcProper(M, K))
+        {
+            Debug.Assert(M * K == target.Count, $"Epistatics/IvmcProperChanging.ctor(Vector<double>, int, int) target vector must have length M*K={M * K}");
+            
+            this.CH = CH;
+            this.CL = CL;
+            this.C0 = C0;
+            this.Z = Z;
+
+            this.VariationModules = null;
+            this.VariationModulePlusOverrides = null;
+            this.VariationModuleMinusOverrides = null;
+        }
+        
+        public IvmcProperChanging(IIvmcProperJudgementPreparer properJudgementPreparer, Modular.Modules modules, Modular.Modules variationModules, double[] variationModulePlusOverrides, double[] variationModuleMinusOverrides, double CH, double CL, double C0, double Z, Linear.Vector<double> target, string friendlyName)
+            : this(properJudgementPreparer, target, friendlyName, new IvmcProper(modules))
+        {
+            Debug.Assert(variationModules != null && variationModules.ElementCount == modules.ModuleCount, $"Epistatics/IvmcProperChanging.ctor(Vector<double>, Modular.Modules) variationModules should have the same number of elements as modules has modules: {modules.ModuleCount}");
+            Debug.Assert(modules.ElementCount == target.Count, $"Epistatics/IvmcProperChanging.ctor(Vector<double>, Modular.Modules) target vector must have length {modules.ElementCount}");
+            
+            this.CH = CH;
+            this.CL = CL;
+            this.C0 = C0;
+            this.Z = Z;
+
+            this.VariationModules = variationModules;
+            this.VariationModulePlusOverrides = variationModulePlusOverrides;
+            this.VariationModuleMinusOverrides = variationModuleMinusOverrides;
+        }
+        
+        public IvmcProperChanging(IIvmcProperJudgementPreparer properJudgementPreparer, int M, int K, double CH, double CL, double C0, double Z, string target, string friendlyName)
+            : this(properJudgementPreparer, target, friendlyName, new IvmcProper(M, K))
+        {
+            Debug.Assert(M * K == target.Length, $"Epistatics/IvmcProperChanging.ctor(string, int, int) target string must have length M*K={M * K}");
+            
+            this.CH = CH;
+            this.CL = CL;
+            this.C0 = C0;
+            this.Z = Z;
+
+            this.VariationModules = null;
+            this.VariationModulePlusOverrides = null;
+            this.VariationModuleMinusOverrides = null;
+        }
+        
+        public IvmcProperChanging(IIvmcProperJudgementPreparer properJudgementPreparer, Modular.Modules modules, Modular.Modules variationModules, double[] variationModulePlusOverrides, double[] variationModuleMinusOverrides, double CH, double CL, double C0, double Z, string target, string friendlyName)
+            : this(properJudgementPreparer, target, friendlyName, new IvmcProper(modules))
+        {
+            Debug.Assert(variationModules == null || variationModules.ElementCount == modules.ModuleCount, $"Epistatics/IvmcProperChanging.ctor(string, Modular.Modules) variationModules should have the same number of elements as modules has modules: {modules.ModuleCount}");
+            
+            this.CH = CH;
+            this.CL = CL;
+            this.C0 = C0;
+            this.Z = Z;
+
+            this.VariationModules = variationModules;
+            this.VariationModulePlusOverrides = variationModulePlusOverrides;
+            this.VariationModuleMinusOverrides = variationModuleMinusOverrides;
+        }
+
+        public override bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            base.NextExposure(rand, jrules, epoch, ref exposureInformation);
+
+            if (VariationModules == null)
+            {
+                for (int i = 0; i < Proper.ModuleCount; i++)
+                {
+                    // use overrides by default
+                    int vmi = i;
+                    double cplus = VariationModulePlusOverrides != null ? VariationModulePlusOverrides[vmi] : double.NaN;
+                    double cminus = VariationModuleMinusOverrides != null ? VariationModuleMinusOverrides[vmi] : double.NaN;
+
+                    // don't generate randoms if we can help it
+                    if (double.IsNaN(cplus) || double.IsNaN(cminus))
+                    {
+                        bool dual = rand.NextDouble() < Z;
+                        bool neg = rand.NextBoolean();
+
+                        // replace non-existant overrides
+                        if (double.IsNaN(cplus))
+                            cplus = !neg ? CH : dual ? CL : C0;
+                        if (double.IsNaN(cminus))
+                            cminus = neg ? CH : dual ? CL : C0;
+                    }
+
+                    // insert
+                    Proper.Cplus[i] = cplus;
+                    Proper.Cminus[i] = cminus;
+                }
+            }
+            else
+            {
+                int vmi = 0;
+                foreach (var vm in VariationModules.ModuleAssignments)
+                {
+                    double cplus = VariationModulePlusOverrides != null ? VariationModulePlusOverrides[vmi] : double.NaN;
+                    double cminus = VariationModuleMinusOverrides != null ? VariationModuleMinusOverrides[vmi] : double.NaN;
+
+                    // don't generate randoms if we can help it
+                    if (double.IsNaN(cplus) || double.IsNaN(cminus))
+                    {
+                        bool dual = rand.NextDouble() < Z;
+                        bool neg = rand.NextBoolean();
+
+                        // replace non-existant overrides
+                        if (double.IsNaN(cplus))
+                            cplus = !neg ? CH : dual ? CL : C0;
+                        if (double.IsNaN(cminus))
+                            cminus = neg ? CH : dual ? CL : C0;
+                    }
+
+                    // insert
+                    foreach (var i in vm)
+                    {
+                        Proper.Cplus[i] = cplus;
+                        Proper.Cminus[i] = cminus;
+                    }
+
+                    vmi++;
+                }
+            }
+
+            return true;
+        }
+        
+        public override Linear.Vector<double> PreparePerfectP()
+        {
+            if (Proper.ModulesType == IvmcProperModulesType.BlockModules)
+            {
+                Proper.DecomposeBlockModules(out int M, out int K);
+
+                // clone target vector
+                Linear.Vector<double> p = Vector.Clone();
+
+                // flip according to module conditions
+                for (int i = 0; i < M * K; i++)
+                {
+                    if (Proper.Cminus[i / K] > Proper.Cplus[i / K])
+                        p[i] = -p[i];
+                }
+
+                return p;
+            }
+            else if (Proper.ModulesType == IvmcProperModulesType.FreeModules)
+            {
+                Proper.DecomposeFreeModules(out var modules);
+
+                Linear.Vector<double> p = Vector.Clone();
+                
+                for (int i = 0; i < modules.ModuleCount; i++)
+                {
+                    if (Proper.Cminus[i] > Proper.Cplus[i])
+                    {
+                        foreach (var j in modules[i])
+                            p[j] = -p[j];
+                    }
+                }
+
+                return p;
+            }
+            else
+            {
+                throw new Exception("Unrecognised IvmcProperModulesType: " + Proper.ModulesType);
+            }
+        }
+    }
+
+    [StateClass]
+    public class IvmcProperVectorTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected IvmcProperVectorTargetJudger()
+        {
+        }
+
+        public IvmcProperVectorTargetJudger(IvmcProper proper)
+        {
+            Proper = proper;
+            Name = "IvmcProperVectorTargetJudger";
+        }
+
+        [SimpleStateProperty("Proper")]
+        public IvmcProper Proper { get; private set; }
+        
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        public string Description => "IvmcProperVectorTargetJudger";
+        
+        public double Judge(Linear.Vector<double> target, Phenotype phenotype)
+        {
+            if (Proper.ModulesType == IvmcProperModulesType.BlockModules)
+            {
+                Proper.DecomposeBlockModules(out int M, out int K);
+                
+                double fitnessAcc = 0;
+
+                for (int i = 0; i < M * K; i += K)
+                {
+                    double acc = 0;
+                    for (int j = i; j < i + K; j++)
+                        acc += phenotype[j] * target[j];
+
+                    acc /= K; // average
+
+                    double moduleFitness = 0.0;
+
+                    double positiveness = 0.5 + acc * 0.5;
+                    moduleFitness += positiveness * positiveness * Proper.Cplus[i / K];
+
+                    double negativeness = 0.5 - acc * 0.5;
+                    moduleFitness += negativeness * negativeness * Proper.Cminus[i / K];
+
+                    fitnessAcc += moduleFitness;
+                }
+
+                return fitnessAcc / M; // mean fitness of all modules
+            }
+            else if (Proper.ModulesType == IvmcProperModulesType.FreeModules)
+            {
+                Proper.DecomposeFreeModules(out var modules);
+                
+                double fitnessAcc = 0;
+
+                for (int i = 0; i < modules.ModuleCount; i++)
+                {
+                    var m = modules[i];
+
+                    double acc = 0;
+                    foreach (var j in m)
+                        acc += phenotype[j] * target[j];
+
+                    acc /= m.Count; // average
+
+                    double moduleFitness = 0.0;
+
+                    double positiveness = 0.5 + acc * 0.5;
+                    moduleFitness += positiveness * positiveness * Proper.Cplus[i];
+
+                    double negativeness = 0.5 - acc * 0.5;
+                    moduleFitness += negativeness * negativeness * Proper.Cminus[i];
+
+                    fitnessAcc += moduleFitness;
+                }
+
+                return fitnessAcc / modules.ModuleCount; // mean fitness of all modules
+            }
+            else
+            {
+                throw new Exception("Unrecognised IvmcProperModulesType: " + Proper.ModulesType);
+            }
+        }
+    }
+    
+    [StateClass]
+    public class IvmcSplitVectorTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected IvmcSplitVectorTargetJudger()
+        {
+        }
+
+        public IvmcSplitVectorTargetJudger(IvmcProper proper)
+        {
+            Proper = proper;
+            Name = "IvmcSplitVectorTargetJudger";
+        }
+
+        [SimpleStateProperty("Proper")]
+        public IvmcProper Proper { get; private set; }
+        
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+        
+        public string Description => "IvmcSplitVectorTargetJudger";
+
+        public double Judge(Linear.Vector<double> target, Phenotype phenotype)
+        {
+            if (Proper.ModulesType == IvmcProperModulesType.BlockModules)
+            {
+                Proper.DecomposeBlockModules(out int M, out int K);
+
+                double benefitAcc = 0;
+
+                for (int i = 0; i < M * K; i += K)
+                {
+                    double acc = 0;
+                    for (int j = i; j < i + K; j++)
+                        acc += phenotype[j] * target[j];
+
+                    acc /= K; // average
+
+                    double moduleBenefit = acc > 0
+                        ? acc * Proper.Cplus[i / K]
+                        : -acc * Proper.Cminus[i / K];
+
+                    benefitAcc += moduleBenefit;
+                }
+
+                return benefitAcc / M; // mean fitness of all modules
+            }
+            else if (Proper.ModulesType == IvmcProperModulesType.FreeModules)
+            {
+                Proper.DecomposeFreeModules(out var modules);
+                
+                double benefitAcc = 0;
+
+                for (int i = 0; i < modules.ModuleCount; i++)
+                {
+                    var m = modules[i];
+
+                    double acc = 0;
+                    foreach (int j in m)
+                        acc += phenotype[j] * target[j];
+
+                    acc /= m.Count; // average
+
+                    double moduleBenefit = acc > 0
+                        ? acc * Proper.Cplus[i]
+                        : -acc * Proper.Cminus[i];
+
+                    benefitAcc += moduleBenefit;
+                }
+
+                return benefitAcc / modules.ModuleCount; // mean fitness of all modules
+            }
+            else
+            {
+                throw new Exception("Unrecognised IvmcProperModulesType: " + Proper.ModulesType);
+            }
+        }
+    }
+
+    public class IvmcProperFeedback
+    {
+        public IvmcProperFeedback(int resolution, int plotPeriod, bool recordPropers, int moduleCount)
+        {
+            Resolution = resolution;
+            PlotPeriod = plotPeriod;
+            RecordPropers = recordPropers;
+
+            ModuleCount = moduleCount;
+
+            SwitchCountSamples = new List<int>();
+            SolveCountSamples = new List<int>();
+            PerModuleSwitchCountSamples = Misc.Create<List<int>>(moduleCount, i => new List<int>());
+            PerModuleSolveCountSamples = Misc.Create<List<int>>(moduleCount, i => new List<int>());
+
+            IvmcProperCMinusSamples = Misc.Create<List<double>>(moduleCount, i => new List<double>());
+            IvmcProperCPlusSamples = Misc.Create<List<double>>(moduleCount, i => new List<double>());
+
+            CurrentSwitchCount = 0;
+            CurrentPerModuleSwitchCount = new int[moduleCount];
+            CurrentSolveCount = 0;
+            CurrentPerModuleSolveCount = new int[moduleCount];
+        }
+
+        public int Resolution { get; }
+        public int PlotPeriod { get; }
+        public bool RecordPropers { get; }
+
+        private int ModuleCount { get; }
+
+        private List<int> SwitchCountSamples { get; }
+        private List<int> SolveCountSamples { get; }
+        private List<int>[] PerModuleSwitchCountSamples { get; }
+        private List<int>[] PerModuleSolveCountSamples { get; }
+        private List<double>[] IvmcProperCPlusSamples { get; }
+        private List<double>[] IvmcProperCMinusSamples { get; }
+
+        public void Attach(IDensePopulationExperimentFeedback densePopulationExperimentFeedback)
+        {
+            var feedback = densePopulationExperimentFeedback.Feedback;
+
+            feedback.EndTarget.Register(EndTarget);
+            feedback.Finished.Register(Finished);
+            feedback.EndEpoch.Register(EndEpoch);
+        }
+
+        private int CurrentSwitchCount = 0;
+        private int[] CurrentPerModuleSwitchCount;
+        private int CurrentSolveCount = 0;
+        private int[] CurrentPerModuleSolveCount;
+        private Phenotype OldPhenotype = null;
+        private void EndTarget(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            Sample(target, epoch, oldPopulationJudgements);
+
+            // epoch resolution, sub-epoch detail
+
+            // record sample
+            if (epoch % Resolution == 0)
+            {
+                // switches & solves
+                SwitchCountSamples.Add(CurrentSwitchCount);
+                CurrentSwitchCount = 0;
+
+                SolveCountSamples.Add(CurrentSolveCount);
+                CurrentSolveCount = 0;
+
+                for (int i = 0; i < CurrentPerModuleSwitchCount.Length; i++)
+                {
+                    PerModuleSwitchCountSamples[i].Add(CurrentPerModuleSwitchCount[i]);
+                    CurrentPerModuleSwitchCount[i] = 0;
+
+                    PerModuleSolveCountSamples[i].Add(CurrentPerModuleSolveCount[i]);
+                    CurrentPerModuleSolveCount[i] = 0;
+                }
+
+                // target (IvmcProper)
+
+                if (RecordPropers)
+                {
+                    if (target is Epistatics.IIvmcProperTarget ivmcProperTarget)
+                    {
+                        var proper = ivmcProperTarget.Proper;
+
+                        for (int i = 0; i < ModuleCount; i++)
+                        {
+                            IvmcProperCMinusSamples[i].Add(proper.Cminus[i]);
+                            IvmcProperCPlusSamples[i].Add(proper.Cplus[i]);
+                        }
+                    }
+                }
+            }
+        }
+
+        private void Finished(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount)
+        {
+            // write out
+            SaveSwitchCounts(stuff, "_t");
+            SaveSolveCounts(stuff, "_t");
+            if (RecordPropers)
+                SaveIvmcProper(stuff, "_t");
+        }
+
+        private void EndEpoch(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            if (epochCount % PlotPeriod == 0)
+            {
+                SaveSwitchCounts(stuff, "" + epochCount);
+                SaveSolveCounts(stuff, "" + epochCount);
+                if (RecordPropers)
+                    SaveIvmcProper(stuff, "" + epochCount);
+            }
+        }
+
+        private void SaveSwitchCounts(FileStuff stuff, string name)
+        {
+            double[][] switchCounts = PerModuleSwitchCountSamples.Select(l => l.Select(x => (double)x).ToArray()).Concat(new[] { SwitchCountSamples.Select(x => (double)x).ToArray() }).ToArray();
+            Analysis.SaveTrajectories(stuff.File("ivmcswitches" + name + ".dat"), switchCounts, Resolution);
+        }
+
+        private void SaveSolveCounts(FileStuff stuff, string name)
+        {
+            double[][] solveCounts = PerModuleSolveCountSamples.Select(l => l.Select(x => (double)x).ToArray()).Concat(new[] { SolveCountSamples.Select(x => (double)x).ToArray() }).ToArray();
+            Analysis.SaveTrajectories(stuff.File("ivmcsolves" + name + ".dat"), solveCounts, Resolution);
+        }
+
+        private void SaveIvmcProper(FileStuff stuff, string name)
+        {
+            var cminus = IvmcProperCMinusSamples.Select(l => l.ToArray());
+            var cplus = IvmcProperCPlusSamples.Select(l => l.ToArray());
+            double[][] both = cminus.Concat(cplus).ToArray();
+            Analysis.SaveTrajectories(stuff.File("ivmcproper" + name + ".dat"), both, Resolution);
+        }
+
+        private void Sample(ITarget target, int epoch, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            var newPhenotype = oldPopulationJudgements[0].Individual.Phenotype;
+            bool allSolved = true;
+
+            // TODO: need a general purpose version of this which works for other IIvmcProperTarget types
+            if (OldPhenotype != null && target is IvmcProperChanging ivmcProperChanging)
+            {
+                var proper = ivmcProperChanging.Proper;
+                bool isHard(int moduleIndex)
+                {
+                    // it's hard if both C+ and C- are >= CL
+                    return proper.Cminus[moduleIndex] >= ivmcProperChanging.CL && proper.Cplus[moduleIndex] >= ivmcProperChanging.CL;
+                }
+
+                var modules = proper.ModulesAsFreeModules();
+                var targetVector = ivmcProperChanging.Vector;
+
+                bool switched(int moduleIndex)
+                {
+                    // it's switched if the module sum has changed sign
+                    double oldSum = modules[moduleIndex].Sum(i => OldPhenotype[i] * targetVector[i]);
+                    double newSum = modules[moduleIndex].Sum(i => newPhenotype[i] * targetVector[i]);
+                    return Math.Sign(oldSum) != Math.Sign(newSum);
+                }
+
+                bool solved(int moduleIndex)
+                {
+                    var problemSign = Math.Sign(proper.Cplus[moduleIndex] - proper.Cminus[moduleIndex]);
+                    var solSign = Math.Sign(modules[moduleIndex].Sum(i => newPhenotype[i] * targetVector[i]));
+
+                    return problemSign == solSign;
+                }
+
+                // foreach module, check if it is 'hard', and then check if we switched
+                for (int i = 0; i < proper.ModuleCount; i++)
+                {
+                    if (isHard(i) && switched(i))
+                    {
+                        CurrentPerModuleSwitchCount[i]++;
+                        CurrentSwitchCount++;
+                    }
+
+                    if (solved(i))
+                    {
+                        CurrentPerModuleSolveCount[i]++;
+                    }
+                    else
+                    {
+                        allSolved = false;
+                    }
+                }
+            }
+
+            if (allSolved)
+            {
+                CurrentSolveCount++;
+            }
+
+            OldPhenotype = newPhenotype;
+        }
+    }
+
+    public class IvmcTargetPackages
+    {
+        // Example1_Tiny
+        public static EpistaticTargetPackage IvmcExample1_1(double v, double z, double changeInversionProbability) => new EpistaticTargetPackage($"IvmcExample1_4_V{v}_2", new[]
+            {
+                new Epistatics.ExpIvmc(1, 4, v, z, "++++", "t", "t", $"Example1TinyV{v}Z{z}ciP{changeInversionProbability}", changeInversionProbability)
+            });
+
+        // Example1
+        public static EpistaticTargetPackage IvmcExample1_4(double v, double z, double changeInversionProbability) => new EpistaticTargetPackage($"IvmcExample1_4_V{v}_2", new[]
+            {
+                new Epistatics.ExpIvmc(4, 4, v, z, "++++++++++++++++", "tttt", "tttt", $"Example1V{v}Z{z}ciP{changeInversionProbability}", changeInversionProbability)
+            });
+
+        // Example1_Big
+        public static EpistaticTargetPackage IvmcExample1_8(double v, double z, double changeInversionProbability) => new EpistaticTargetPackage($"IvmcExample1_8_V{v}_2", new[]
+            {
+                new Epistatics.ExpIvmc(8, 4, v, z, "++++++++++++++++++++++++++++++++", "tttttttt", "tttttttt", $"Example1BigV{v}Z{z}ciP{changeInversionProbability}", changeInversionProbability)
+            });
+
+        // Proper1
+        public static EpistaticTargetPackage IvmcProper1_4x4(IvmcProperJudgementMode properJudgementMode, double ch, double cl, double c0, double z) => new EpistaticTargetPackage($"IvmcProper1_4x4CH{ch}CL{cl}Z{z}", new[]
+            {
+                IvmcProper1_4x4target(properJudgementMode, ch, cl, c0, z)
+            });
+
+        public static Epistatics.IvmcProperChanging IvmcProper1_4x4target(IvmcProperJudgementMode properJudgementMode, double ch, double cl, double c0, double z)
+        {
+            return new Epistatics.IvmcProperChanging(new TraditionalIvmcProperJudgementPreparer(properJudgementMode), 4, 4, ch, cl, c0, z, "++++++++++++++++", $"IvmcProper{properJudgementMode.ToString()}4x4CH{ch}CL{cl}C0{c0}Z{z}");
+        }
+        
+        // Proper1Custom
+        public static EpistaticTargetPackage IvmcProper1_Custom(string modulesString, string variationModulesString, string variationModulePlusOverridesString, string variationModuleMinusOverridesString, string targetString, IIvmcProperJudgementPreparer properJudgementPreparer, double ch, double cl, double c0, double z, string name) => new EpistaticTargetPackage($"IvmcProper1_CustomCH{ch}CL{cl}CZ{c0}Z{z}", new[]
+            {
+                IvmcProper1_CustomTarget(modulesString, variationModulesString, variationModulePlusOverridesString, variationModuleMinusOverridesString, targetString, properJudgementPreparer, ch, cl, c0, z, name)
+            });
+        
+        //public static Epistatics.IvmcProperChanging IvmcProper1_CustomTarget(string modulesString, string variationModulesString, string variationModulePlusOverridesString, string variationModuleMinusOverridesString, string targetString, IvmcProperJudgementMode properJudgementMode, double ch, double cl, double c0, double z)
+        public static Epistatics.IvmcProperChanging IvmcProper1_CustomTarget(string modulesString, string variationModulesString, string variationModulePlusOverridesString, string variationModuleMinusOverridesString, string targetString, IIvmcProperJudgementPreparer properJudgementPreparer, double ch, double cl, double c0, double z, string name)
+        {
+            return new Epistatics.IvmcProperChanging(
+                properJudgementPreparer,
+                Modular.MultiModulesStringParser.Instance.Parse(modulesString),
+                variationModulesString != null ? Modular.MultiModulesStringParser.Instance.Parse(variationModulesString) : null,
+                variationModulePlusOverridesString != null ? ParseOverrides(variationModulePlusOverridesString, ch, cl, c0) : null,
+                variationModuleMinusOverridesString != null ? ParseOverrides(variationModuleMinusOverridesString, ch, cl, c0) : null,
+                ch, cl, c0, z,
+                targetString,
+                name ?? $"IvmcProper{properJudgementPreparer.Name}CH{ch}CL{cl}C0{c0}Z{z}_{modulesString}_{variationModulesString}_{targetString}");
+        }
+
+        public static void GenerateRandomIvmcHL0Overrides(RandomSource rand, int moduleCount, double Z, int targetCount, out string ivmcvmmos, out string ivmcvmpos)
+        {
+            var m = new StringBuilder();
+            var p = new StringBuilder();
+
+            for (int i = 0; i < targetCount; i++)
+            {
+                if (i > 0)
+                {
+                    m.Append('|');
+                    p.Append('|');
+                }
+
+                GenerateRandomIvmcHL0Overrides(rand, moduleCount, Z, out ivmcvmmos, out ivmcvmpos);
+
+                m.Append(ivmcvmmos);
+                p.Append(ivmcvmpos);
+            }
+
+            ivmcvmmos = m.ToString();
+            ivmcvmpos = p.ToString();
+        }
+
+        public static void GenerateRandomIvmcHL0Overrides(RandomSource rand, int moduleCount, double Z, out string ivmcvmmos, out string ivmcvmpos)
+        {
+            char[] m = new char[moduleCount];
+            char[] p = new char[moduleCount];
+
+            for (int mi = 0; mi < moduleCount; mi++)
+            {
+                bool phigh = rand.NextBoolean(); // ¬mhigh
+                bool mtype = rand.NextDouble() < Z; // ¬stype
+
+                var high = phigh ? p : m;
+                var low = phigh ? m : p;
+
+                high[mi] = 'H';
+                low[mi] = mtype ? 'L' : '0';
+            }
+
+            ivmcvmmos = new string(m);
+            ivmcvmpos = new string(p);
+        }
+
+        public static double[] ParseOverrides(string str, double ch, double cl, double c0)
+        {
+            if (string.IsNullOrEmpty(str))
+                return null; // bleh
+
+            if (str.Contains(";"))
+            {
+                return str.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s == "_" ? double.NaN : double.Parse(s)).ToArray();
+            }
+
+            double[] res = new double[str.Length];
+            for (int i = 0; i < res.Length; i++)
+            {
+                char c = str[i];
+                
+                switch (c)
+                {
+                    case 'H':
+                    case 'h':
+                        res[i] = ch;
+                        break;
+                    case 'L':
+                    case 'l':
+                        res[i] = cl;
+                        break;
+                    case '0':
+                        res[i] = c0;
+                        break;
+                    case '_':
+                        res[i] = double.NaN;
+                        break;
+                    default:
+                        throw new ArgumentException("Invalid IvmcOverride String: \"" + str + "\"", nameof(str));
+                }
+            }
+
+            return res;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/MC.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/MC.cs
new file mode 100644
index 0000000..a824dbd
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/MC.cs
@@ -0,0 +1,207 @@
+using M4M.State;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M.Epistatics
+{
+    public class MCTargetPackages
+    {
+        public static EpistaticTargetPackage MCPlainOnPlain(int n, int k, double p) => new EpistaticTargetPackage($"MC{n},{k},{p}_2", new[] { new MCTargetPlainOnPlain(n, k, p), new MCTargetPlainOnPlain(n, k, p) });
+        public static EpistaticTargetPackage MCCheckOnPlain(int n, int k, double p) => new EpistaticTargetPackage($"MC{n},{k},{p}_2", new[] { new MCTargetCheckOnPlain(n, k, p), new MCTargetCheckOnPlain(n, k, p) });
+        public static EpistaticTargetPackage MCCheckOnCheck(int n, int k, double p) => new EpistaticTargetPackage($"MC{n},{k},{p}_2", new[] { new MCTargetCheckOnCheck(n, k, p), new MCTargetCheckOnCheck(n, k, p) });
+    }
+
+    [StateClass]
+    public class MCTargetPlainOnPlain : CorrelationMatrixTarget, IPerfectPhenotypeTarget
+    {
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        [SimpleStateProperty("P")]
+        public double P { get; private set; }
+
+        [Obsolete]
+        protected MCTargetPlainOnPlain()
+        {
+        }
+
+        public MCTargetPlainOnPlain(int n, int k, double p) : base(PrepareMCMatrixPlainPlain(n, k, p), $"Plain{n}Plain{k}_{p}", true)
+        {
+            N = n;
+            K = k;
+            P = p;
+        }
+
+        public static Linear.Vector<double> PrepareMCIdealVectorPlainPlain(int n, int k)
+        {
+            Linear.Vector<double> vec = Linear.CreateVector.Dense(n * k, i => 1.0);
+            return vec;
+        }
+
+        public static Linear.Matrix<double> PrepareMCMatrixPlainPlain(int n, int k, double p)
+        {
+            Linear.Matrix<double> mat = Linear.CreateMatrix.Dense(n * k, n * k, (i, j) =>
+            {
+                int mi = i / k;
+                int mj = j / k;
+
+                if (mi == mj)
+                { // same module
+                    return 1.0;
+                }
+                else
+                { // different modules
+                    return p;
+                }
+            });
+            return mat;
+        }
+
+        public Vector<double> PreparePerfectP()
+        {
+            return PrepareMCIdealVectorPlainPlain(N, K);
+        }
+    }
+
+    [StateClass]
+    public class MCTargetCheckOnPlain : CorrelationMatrixTarget, IPerfectPhenotypeTarget
+    {
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        [SimpleStateProperty("P")]
+        public double P { get; private set; }
+
+        [Obsolete]
+        protected MCTargetCheckOnPlain()
+        {
+        }
+
+        public MCTargetCheckOnPlain(int n, int k, double p) : base(PrepareMCMatrixCheckPlain(n, k, p), $"Check{n}Plain{k}_{p}", true)
+        {
+            N = n;
+            K = k;
+            P = p;
+        }
+
+        public static Linear.Vector<double> PrepareMCIdealVectorCheckPlain(int n, int k)
+        {
+            Linear.Vector<double> vec = Linear.CreateVector.Dense(n * k, i =>
+            {
+                int mi = i / k;
+
+                double mc = (mi) % 2 == 0 ? 1 : -1;
+
+                return mc;
+            });
+            return vec;
+        }
+
+        public static Linear.Matrix<double> PrepareMCMatrixCheckPlain(int n, int k, double p)
+        {
+            Linear.Matrix<double> mat = Linear.CreateMatrix.Dense(n * k, n * k, (i, j) =>
+            {
+                int mi = i / k;
+                int mj = j / k;
+
+                double mc = (mi + mj) % 2 == 0 ? 1 : -1;
+
+                if (mi == mj)
+                { // same module
+                    return 1.0;
+                }
+                else
+                { // different modules
+                    return mc * p;
+                }
+            });
+            return mat;
+        }
+
+        public Vector<double> PreparePerfectP()
+        {
+            return PrepareMCIdealVectorCheckPlain(N, K);
+        }
+    }
+
+    [StateClass]
+    public class MCTargetCheckOnCheck : CorrelationMatrixTarget, IPerfectPhenotypeTarget
+    {
+        [SimpleStateProperty("N")]
+        public int N { get; private set; }
+
+        [SimpleStateProperty("K")]
+        public int K { get; private set; }
+
+        [SimpleStateProperty("P")]
+        public double P { get; private set; }
+
+        [Obsolete]
+        protected MCTargetCheckOnCheck()
+        {
+        }
+
+        public MCTargetCheckOnCheck(int n, int k, double p) : base(PrepareMCMatrixCheckCheck(n, k, p), $"Check{n}Check{k}_{p}", true)
+        {
+            N = n;
+            K = k;
+            P = p;
+        }
+
+        public static Linear.Vector<double> PrepareMCIdealVectorCheckCheck(int n, int k)
+        {
+            Linear.Vector<double> vec = Linear.CreateVector.Dense(n * k, i =>
+            {
+                int mi = i / k;
+                int ti = i % k;
+
+                double mc = (mi) % 2 == 0 ? 1 : -1;
+                double tc = (ti) % 2 == 0 ? 1 : -1;
+
+                return mc * tc;
+            });
+            return vec;
+        }
+
+        public static Linear.Matrix<double> PrepareMCMatrixCheckCheck(int n, int k, double p)
+        {
+            Linear.Matrix<double> mat = Linear.CreateMatrix.Dense(n * k, n * k, (i, j) =>
+                {
+                    int mi = i / k;
+                    int mj = j / k;
+                    
+                    int ti = i % k;
+                    int tj = j % k;
+
+                    double mc = (mi + mj) % 2 == 0 ? 1 : -1;
+                    double tc = (ti + tj) % 2 == 0 ? 1 : -1;
+
+                    if (mi == mj)
+                    { // same module
+                        return tc;
+                    }
+                    else
+                    { // different modules
+                        return tc * mc * p;
+                    }
+                });
+            return mat;
+        }
+
+        public Vector<double> PreparePerfectP()
+        {
+            return PrepareMCIdealVectorCheckCheck(N, K);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/NQueens.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/NQueens.cs
new file mode 100644
index 0000000..ba1c77b
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/NQueens.cs
@@ -0,0 +1,304 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M.Epistatics
+{
+    public static class NQueens
+    {
+        /// <summary>
+        /// -1 (ascii /) corresponds to blank
+        /// </summary>
+        public static int[] ParsePartialSolution(string str)
+        {
+            int[] ints;
+
+            if (str.Contains(";") || str.Contains(","))
+            {
+                ints = str.Split(new[] { ',', ';' }).Select(int.Parse).ToArray();
+            }
+            else
+            {
+                ints = str.Select(c => c - '0').ToArray(); // '/' -> -1
+            }
+
+            return ints;
+        }
+
+        public static Linear.Vector<double> PrepareForcingVector(int[] partialSolution, bool full)
+        {
+            int n = (int)partialSolution.Length;
+
+            var forcingVector = Linear.CreateVector.Dense<double>(n * n);
+
+            for (int i = 0; i < partialSolution.Length; i++)
+            {
+                if (partialSolution[i] < 0)
+                    continue;
+
+                if (full)
+                {
+                    for (int j = 0; j < n; j++)
+                    {
+                        forcingVector[i + j] = -1;
+                    }
+                }
+
+                forcingVector[i * n + partialSolution[i]] = 1;
+            }
+
+            return forcingVector;
+        }
+
+        public static Linear.Matrix<double> GeneralNQueens(int n, double diagonalCoef, double constraintCoef, bool proportionalConstraints)
+        {
+            int dim = n * n;
+
+            Linear.Matrix<double> cmat = Linear.CreateMatrix.Dense<double>(dim, dim);
+
+            for (int i = 0; i < dim; i++)
+            {
+                for (int j = 0; j < dim; j++)
+                {
+                    if (i == j)
+                    {
+                        cmat[i, j] = diagonalCoef;
+                        continue;
+                    }
+
+                    double constraintSum = 0;
+
+                    int ci = i % n;
+                    int cj = j % n;
+
+                    if (ci == cj) // same col
+                        constraintSum += proportionalConstraints ? (double)n / n : 1.0;
+
+                    int ri = i / n;
+                    int rj = j / n;
+
+                    if (ri == rj) // same row
+                        constraintSum += proportionalConstraints ? (double)n / n : 1.0;
+
+                    int sei = ri - ci;
+                    int sej = rj - cj;
+
+                    if (sei == sej) // same diff-diagonal
+                        constraintSum += proportionalConstraints ? (double)n / (n - Math.Abs(sei)) : 1.0;
+
+                    int kei = ri + ci;
+                    int kej = rj + cj;
+
+                    if (kei == kej) // same sum-diagonal
+                        constraintSum += proportionalConstraints ? (double)n / (n - Math.Abs(kei - (n - 1))) : 1.0;
+
+                    cmat[i, j] = constraintSum * constraintCoef;
+                }
+            }
+
+            return cmat;
+        }
+
+        public static int[,] RenderSolution(Linear.Vector<double> densities, double threshold, out int n)
+        {
+            int dim = densities.Count;
+
+            n = (int)Math.Round(Math.Sqrt(dim));
+
+            if (n * n != dim)
+                throw new ArgumentException("densities must be a square", nameof(densities));
+
+            var solution = new int[n, n];
+
+            for (int r = 0; r < n; r++)
+            {
+                for (int c = 0; c < n; c++)
+                {
+                    solution[r, c] = densities[r * n + c] > threshold ? 1 : 0;
+                }
+            }
+
+            return solution;
+        }
+
+        public static List<NQueensConflict> LocateConflicts(int[,] solution)
+        {
+            int n = solution.GetLength(0);
+
+            List<MatrixEntryAddress>[] seen;
+
+            void reset(int count)
+            {
+                seen = Misc.Create<List<MatrixEntryAddress>>(count, i => new List<MatrixEntryAddress>());
+
+                for (int i = 0; i < seen.Length; i++)
+                {
+                    seen[i].Clear();
+                }
+            }
+
+            void set(int i, MatrixEntryAddress mea)
+            {
+                if (i < 0)
+                    return;
+
+                var l = seen[i];
+
+                l.Add(mea);
+            }
+
+            var conflicts = new List<NQueensConflict>();
+
+            void accumulate(NQueensConflictType type, string prefix)
+            {
+                for (int i = 0; i < seen.Length; i++)
+                {
+                    if (seen[i].Count > 1)
+                    {
+                        conflicts.Add(new NQueensConflict($"{prefix}{i}", type, seen[i].ToArray()));
+                    }
+                }
+            }
+
+            // for each row
+            reset(n);
+            for (int r = 0; r < n; r++)
+            {
+                for (int c = 0; c < n; c++)
+                {
+                    if (solution[r, c] > 0)
+                        set(r, new MatrixEntryAddress(r, c));
+                }
+            }
+            accumulate(NQueensConflictType.Row, $"Row");
+
+            // for each column
+            reset(n);
+            for (int c = 0; c < n; c++)
+            {
+                for (int r = 0; r < n; r++)
+                {
+                    if (solution[r, c] > 0)
+                        set(c, new MatrixEntryAddress(r, c));
+                }
+            }
+            accumulate(NQueensConflictType.Column, $"Col");
+
+            // for each diff-diagonal
+            reset(n * 2 - 1);
+            for (int c = 0; c < n; c++)
+            {
+                for (int r = 0; r < n; r++)
+                {
+                    if (solution[r, c] > 0)
+                        set(r - c + n - 1, new MatrixEntryAddress(r, c));
+                }
+            }
+            accumulate(NQueensConflictType.DiffDiagonal, $"DiffDiagonal");
+
+            // for each sum-diagonal
+            reset(n * 2 - 1);
+            for (int c = 0; c < n; c++)
+            {
+                for (int r = 0; r < n; r++)
+                {
+                    if (solution[r, c] > 0)
+                        set(r + c, new MatrixEntryAddress(r, c));
+                }
+            }
+            accumulate(NQueensConflictType.SumDiagonal, $"SumDiagonal");
+
+            return conflicts;
+        }
+    }
+
+    public enum NQueensConflictType
+    {
+        Row,
+        Column,
+        DiffDiagonal,
+        SumDiagonal,
+    }
+
+    public class NQueensConflict
+    {
+        public NQueensConflict(string description, NQueensConflictType type, IReadOnlyList<MatrixEntryAddress> entries)
+        {
+            Description = description ?? throw new ArgumentNullException(nameof(description));
+            Type = type;
+            Entries = entries ?? throw new ArgumentNullException(nameof(entries));
+        }
+
+        public string Description { get; }
+        public NQueensConflictType Type { get; }
+        public IReadOnlyList<MatrixEntryAddress> Entries { get; }
+    }
+
+    public class NQueensFeedback
+    {
+        public NQueensFeedback(int resolution, int plotPeriod, double threshold)
+        {
+            Resolution = resolution;
+            PlotPeriod = plotPeriod;
+            Threshold = threshold;
+
+            NQueensScoreSamples = new List<int>();
+            NQueensConflictCountSamples = new List<int>();
+        }
+
+        public int Resolution { get; }
+        public int PlotPeriod { get; }
+        public double Threshold { get; }
+
+        private List<int> NQueensScoreSamples { get; }
+        private List<int> NQueensConflictCountSamples { get; }
+
+        public void Attach(IDensePopulationExperimentFeedback densePopulationExperimentFeedback)
+        {
+            var feedback = densePopulationExperimentFeedback.Feedback;
+
+            feedback.EndTarget.Register(EndTarget);
+            feedback.Finished.Register(Finished);
+            feedback.EndEpoch.Register(EndEpoch);
+        }
+
+        private void EndTarget(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            // record samples
+            if (epoch % Resolution == 0)
+            {
+                var p = oldPopulationJudgements[0].Individual.Phenotype;
+                var sol = NQueens.RenderSolution(p.Vector, Threshold, out int n);
+                var conflictCount = NQueens.LocateConflicts(sol).Count;
+                NQueensConflictCountSamples.Add(conflictCount);
+
+                var NQueensScore = n - conflictCount;
+                NQueensScoreSamples.Add(NQueensScore);
+            }
+        }
+
+        private void Finished(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount)
+        {
+            // write out
+            SaveNQueensSamples(stuff, "_t");
+        }
+
+        private void EndEpoch(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            if (epochCount % PlotPeriod == 0)
+            {
+                SaveNQueensSamples(stuff, "" + epochCount);
+            }
+        }
+
+        private void SaveNQueensSamples(FileStuff stuff, string name)
+        {
+            double[][] NQueensSamples = new double[][] {
+                NQueensScoreSamples.Select(x => (double)x).ToArray(),
+                NQueensConflictCountSamples.Select(x => (double)x).ToArray()
+            };
+            Analysis.SaveTrajectories(stuff.File("nqueenssamples" + name + ".dat"), NQueensSamples, Resolution);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/Stacked.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/Stacked.cs
new file mode 100644
index 0000000..4fffe6f
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/Stacked.cs
@@ -0,0 +1,138 @@
+using M4M.State;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M.Epistatics
+{
+    [StateClass]
+    public class IvmcStackedVectorTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected IvmcStackedVectorTargetJudger()
+        {
+        }
+
+        public IvmcStackedVectorTargetJudger(IModuleBenefitFunction moduleBenefitFunction, IvmcProper proper)
+            : this(moduleBenefitFunction, proper, 1.0, true, 2)
+        {
+        }
+
+        public IvmcStackedVectorTargetJudger(IModuleBenefitFunction moduleBenefitFunction, IvmcProper proper, double decayFactor, bool rescaleFitness, int scaleFactor)
+        {
+            ModuleBenefitFunction = moduleBenefitFunction;
+            Proper = proper;
+            Name = "IvmcStackedVectorTargetJudger";
+            DecayFactor = decayFactor;
+            RescaleFitness = rescaleFitness;
+            ScaleFactor = scaleFactor;
+        }
+
+        [SimpleStateProperty("StackedModules")]
+        private int[][] StackedModules { get; set; } // not used
+
+        [SimpleStateProperty("ModuleBenefitFunction")]
+        public IModuleBenefitFunction ModuleBenefitFunction { get; private set; }
+
+        [SimpleStateProperty("Proper")]
+        public IvmcProper Proper { get; private set; }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        [SimpleStateProperty("DecayFactor")]
+        public double DecayFactor { get; private set; }
+
+        [SimpleStateProperty("RescaleFitness")]
+        public bool RescaleFitness { get; private set; } = true;
+
+        [SimpleStateProperty("ScaleFactor")]
+        public int ScaleFactor { get; private set; } = 2;
+
+        public string Description => $"IvmcStackedVectorTargetJudger (ModuleBenefitFunction={ModuleBenefitFunction.Name}, DecayFactor={DecayFactor}, RescaleFitness={RescaleFitness}, ScaleFactor={ScaleFactor})";
+
+        public double Judge(Vector<double> target, Phenotype phenotype)
+        {
+            double[] moduleAvgs;
+
+            if (Proper.ModulesType == IvmcProperModulesType.BlockModules)
+            {
+                Proper.DecomposeBlockModules(out int M, out int K);
+                moduleAvgs = new double[M];
+
+                for (int i = 0; i < M * K; i += K)
+                {
+                    double acc = 0;
+                    for (int j = i; j < i + K; j++)
+                        acc += phenotype[j] * target[j];
+
+                    acc /= K; // average
+
+                    moduleAvgs[i / K] = acc;
+                }
+            }
+            else if (Proper.ModulesType == IvmcProperModulesType.FreeModules)
+            {
+                Proper.DecomposeFreeModules(out var modules);
+                moduleAvgs = new double[modules.ModuleCount];
+                
+                for (int i = 0; i < modules.ModuleCount; i++)
+                {
+                    var m = modules[i];
+
+                    double acc = 0;
+                    foreach (int j in m)
+                        acc += phenotype[j] * target[j];
+
+                    acc /= m.Count; // average
+
+                    moduleAvgs[i] = acc;
+                }
+            }
+            else
+            {
+                throw new Exception("Unrecognised IvmcProperModulesType: " + Proper.ModulesType);
+            }
+            
+            double fitnessAcc = 0;
+            int smCount = 0;
+
+            double decay = 1.0;
+            double idealAcc = 0.0;
+            for (int s = 1; s <= moduleAvgs.Length; s *= ScaleFactor)
+            {
+                for (int i = 0; i < moduleAvgs.Length; i += s)
+                {
+                    if (s > 1)
+                    {
+                        double tot = 0.0;
+                        int stride = s / ScaleFactor;
+                        for (int j = 0; j < ScaleFactor; j++)
+                        {
+                            tot += moduleAvgs[i + stride * j];
+                        }
+                        moduleAvgs[i] = tot / ScaleFactor;
+                    }
+
+                    var acc = moduleAvgs[i];
+                    
+                    // this will reward being correlated, without biasing the system
+                    double moduleFitness = ModuleBenefitFunction.ComputeModuleBenefit(acc, 0.5, 0.5);
+
+                    fitnessAcc += moduleFitness * decay;
+                    idealAcc += decay;
+                    smCount++;
+                }
+
+                decay *= DecayFactor;
+            }
+
+            if (RescaleFitness)
+                return fitnessAcc / idealAcc;
+            else
+                return fitnessAcc;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Epistatics/Sudoku.cs b/M4MCode/M4M_MkI/M4M.Model/Epistatics/Sudoku.cs
new file mode 100644
index 0000000..e5fa8b5
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Epistatics/Sudoku.cs
@@ -0,0 +1,378 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M.Epistatics
+{
+    public static class Sudoku
+    {
+        /// <summary>
+        /// takes 0+, maps to -1+ (i.e. 0 (blank) -> -1, 1 -> 0, n -> n-1)
+        /// -1 corresponds to blank
+        /// </summary>
+        public static int[] ParsePartialSolution(string str)
+        {
+            int[] ints;
+
+            if (str.Contains(";") || str.Contains(","))
+            {
+                ints = str.Split(new[] { ',', ';' }).Select(int.Parse).ToArray();
+            }
+            else
+            {
+                ints = str.Select(c => c - '0').ToArray();
+            }
+
+            int n = (int)Math.Round(Math.Sqrt(ints.Length));
+
+            int d = (int)Math.Sqrt(n);
+
+            if (d * d != n)
+                throw new ArgumentException("N must be a square number", nameof(n));
+
+            return ints.Select(i => i - 1).ToArray();
+        }
+
+        public static Linear.Vector<double> PrepareForcingVector(int[] partialSolution, bool full)
+        {
+            int n = (int)Math.Round(Math.Sqrt(partialSolution.Length));
+
+            int d = (int)Math.Sqrt(n);
+
+            if (d * d != n)
+                throw new ArgumentException("N must be a square number", nameof(n));
+
+            int dim = n * n * n;
+
+            var forcingVector = Linear.CreateVector.Dense<double>(dim);
+
+            for (int i = 0; i < partialSolution.Length; i++)
+            {
+                if (partialSolution[i] < 0)
+                    continue;
+
+                if (full)
+                {
+                    for (int j = 0; j < n; j++)
+                    {
+                        forcingVector[i + j] = -1;
+                    }
+                }
+
+                forcingVector[i * n + partialSolution[i]] = 1;
+            }
+
+            return forcingVector;
+        }
+
+        public static Linear.Matrix<double> GeneralSudoku(int n, double diagonalCoef, double constraintCoef, bool additiveConstaints)
+        {
+            int d = (int)Math.Sqrt(n);
+
+            if (d * d != n)
+                throw new ArgumentException("N must be a square number", nameof(n));
+
+            int dim = n * n * n;
+
+            Linear.Matrix<double> cmat = Linear.CreateMatrix.Dense<double>(dim, dim);
+
+            for (int i = 0; i < dim; i++)
+            {
+                for (int j = 0; j < dim; j++)
+                {
+                    if (i == j)
+                    {
+                        cmat[i, j] = diagonalCoef;
+                        continue;
+                    }
+
+                    int ci = i / n;
+                    int cj = j / n;
+
+                    int constraintCount = 0;
+                    if (ci == cj) // same cell
+                    {
+                        constraintCount++;
+
+                        // don't accumulate
+                        goto constrain;
+                    }
+
+                    int m = i % n;
+                    int o = j % n;
+
+                    if (m != o) // different number
+                        goto constrain;
+
+                    int cxi = ci % n;
+                    int cxj = cj % n;
+
+                    if (cxi == cxj) // same col
+                        constraintCount++;
+
+                    int cyi = ci / n;
+                    int cyj = cj / n;
+
+                    if (cyi == cyj) // same row
+                        constraintCount++;
+
+                    int bxi = cxi / d;
+                    int bxj = cxj / d;
+
+                    int byi = cyi / d;
+                    int byj = cyj / d;
+
+                    if (bxi == bxj && byi == byj) // same block
+                        constraintCount++;
+
+                    constrain:
+                    cmat[i, j] = additiveConstaints
+                        ? constraintCount * constraintCoef
+                        : Math.Sign(constraintCount) * constraintCoef;
+                }
+            }
+
+            return cmat;
+        }
+
+        public static int[,] RenderSolution(Linear.Vector<double> densities, out int n)
+        {
+            var arr = densities.ToArray();
+
+            int dim = densities.Count;
+
+            n = (int)Math.Round(Math.Pow(dim, 1 / 3.0));
+
+            int d = (int)Math.Round(Math.Sqrt(n));
+
+            if (d * d != n)
+                throw new ArgumentException("N must be a square number", nameof(n));
+
+            var solution = new int[n, n];
+
+            for (int r = 0; r < n; r++)
+            {
+                for (int c = 0; c < n; c++)
+                {
+                    if (new ArraySegment<double>(arr, r * n * n + c * n, n).All(x => x == 0))
+                        solution[r, c] = -1;
+                    else
+                        solution[r, c] = Misc.IndexMax(new ArraySegment<double>(arr, r * n * n + c * n, n));
+                }
+            }
+
+            return solution;
+        }
+
+        public static List<SudokuConflict> LocateConflicts(int[,] solution)
+        {
+            int n = solution.GetLength(0);
+
+            int d = (int)Math.Round(Math.Sqrt(n));
+
+            if (d * d != n)
+                throw new ArgumentException("N must be a square number", nameof(n));
+
+            var seen = Misc.Create<List<MatrixEntryAddress>>(n, i => new List<MatrixEntryAddress>());
+            bool conflict = false;
+
+            void reset()
+            {
+                conflict = false;
+
+                for (int i = 0; i < seen.Length; i++)
+                {
+                    seen[i].Clear();
+                }
+            }
+
+            void set(int i, MatrixEntryAddress mea)
+            {
+                if (i < 0)
+                    return;
+
+                var l = seen[i];
+
+                l.Add(mea);
+
+                if (l.Count > 1)
+                {
+                    conflict = true;
+                }
+            }
+
+            var conflicts = new List<SudokuConflict>();
+
+            void accumulate(SudokuConflictType type, string prefix)
+            {
+                if (!conflict)
+                    return;
+
+                for (int i = 0; i < seen.Length; i++)
+                {
+                    if (seen[i].Count > 1)
+                    {
+                        conflicts.Add(new SudokuConflict($"{prefix}{i}", type, seen[i].ToArray()));
+                    }
+                }
+            }
+
+            // for each row
+            for (int r = 0; r < n; r++)
+            {
+                reset();
+
+                for (int c = 0; c < n; c++)
+                {
+                    set(solution[r, c], new MatrixEntryAddress(r, c));
+                }
+
+                if (conflict)
+                {
+                    accumulate(SudokuConflictType.Row, $"Row{r}: ");
+                }
+            }
+
+            // for each column
+            for (int c = 0; c < n; c++)
+            {
+                reset();
+
+                for (int r = 0; r < n; r++)
+                {
+                    set(solution[r, c], new MatrixEntryAddress(r, c));
+                }
+
+                if (conflict)
+                {
+                    accumulate(SudokuConflictType.Column, $"Col{c}: ");
+                }
+            }
+
+            // for each block
+            for (int ro = 0; ro < d; ro++)
+            {
+                for (int co = 0; co < d; co++)
+                {
+                    reset();
+
+                    for (int ri = 0; ri < d; ri++)
+                    {
+                        for (int ci = 0; ci < d; ci++)
+                        {
+                            var r = ro * d + ri;
+                            var c = co * d + ci;
+                            set(solution[r, c], new MatrixEntryAddress(r, c));
+                        }
+                    }
+
+                    if (conflict)
+                    {
+                        accumulate(SudokuConflictType.Block, $"Block{ro},{co}: ");
+                    }
+                }
+            }
+
+            // for each entry
+            for (int c = 0; c < n; c++)
+            {
+                for (int r = 0; r < n; r++)
+                {
+                    if (solution[r, c] < 0)
+                    {
+                        conflicts.Add(new SudokuConflict($"Unassigned{solution[r, c]}", SudokuConflictType.UnassignedCell, new[] { new MatrixEntryAddress(r, c) }));
+                    }
+                }
+            }
+
+            return conflicts;
+        }
+    }
+
+    public enum SudokuConflictType
+    {
+        Block,
+        Row,
+        Column,
+        UnassignedCell,
+    }
+
+    public class SudokuConflict
+    {
+        public SudokuConflict(string description, SudokuConflictType type, IReadOnlyList<MatrixEntryAddress> entries)
+        {
+            Description = description ?? throw new ArgumentNullException(nameof(description));
+            Type = type;
+            Entries = entries ?? throw new ArgumentNullException(nameof(entries));
+        }
+
+        public string Description { get; }
+        public SudokuConflictType Type { get; }
+        public IReadOnlyList<MatrixEntryAddress> Entries { get; }
+    }
+
+    public class SudokuFeedback
+    {
+        public SudokuFeedback(int resolution, int plotPeriod)
+        {
+            Resolution = resolution;
+            PlotPeriod = plotPeriod;
+
+            SudokuScoreSamples = new List<int>();
+            SudokuConflictCountSamples = new List<int>();
+        }
+
+        public int Resolution { get; }
+        public int PlotPeriod { get; }
+
+        private List<int> SudokuScoreSamples { get; }
+        private List<int> SudokuConflictCountSamples { get; }
+
+        public void Attach(IDensePopulationExperimentFeedback densePopulationExperimentFeedback)
+        {
+            var feedback = densePopulationExperimentFeedback.Feedback;
+
+            feedback.EndTarget.Register(EndTarget);
+            feedback.Finished.Register(Finished);
+            feedback.EndEpoch.Register(EndEpoch);
+        }
+
+        private void EndTarget(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            // record samples
+            if (epoch % Resolution == 0)
+            {
+                var sudokuScore = -1;
+                SudokuScoreSamples.Add(sudokuScore);
+
+                var p = oldPopulationJudgements[0].Individual.Phenotype;
+                var sol = Sudoku.RenderSolution(p.Vector, out int n);
+                SudokuConflictCountSamples.Add(Sudoku.LocateConflicts(sol).Count);
+            }
+        }
+
+        private void Finished(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount)
+        {
+            // write out
+            SaveSudokuSamples(stuff, "_t");
+        }
+
+        private void EndEpoch(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+        {
+            if (epochCount % PlotPeriod == 0)
+            {
+                SaveSudokuSamples(stuff, "" + epochCount);
+            }
+        }
+
+        private void SaveSudokuSamples(FileStuff stuff, string name)
+        {
+            double[][] sudokuSamples = new double[][] {
+                SudokuScoreSamples.Select(x => (double)x).ToArray(),
+                SudokuConflictCountSamples.Select(x => (double)x).ToArray()
+            };
+            Analysis.SaveTrajectories(stuff.File("sudokusamples" + name + ".dat"), sudokuSamples, Resolution);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/ExperimentConfiguration.cs b/M4MCode/M4M_MkI/M4M.Model/ExperimentConfiguration.cs
new file mode 100644
index 0000000..fe4d2de
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/ExperimentConfiguration.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enogh)
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using MathNet.Numerics.Random;
+using M4M.State;
+
+namespace M4M
+{
+    public interface ICycler
+    {
+        /// <summary>
+        /// The Name of the Cycler
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// Determines the next element
+        /// </summary>
+        /// <param name="rand">A random source</param>
+        /// <param name="current">The current element's index</param>
+        /// <param name="max">The number of elements and exclusive max index</param>
+        /// <returns>The index of the next chosen element</returns>
+        int Cycle(RandomSource rand, int current, int max);
+    }
+    
+    [StateClass]
+    public class LoopCycler : ICycler
+    {
+        public string Name => "Loop";
+
+        public int Cycle(RandomSource rand, int current, int max)
+        {
+            return (current + 1) % max;
+        }
+    }
+    
+    [StateClass]
+    public class RandomCycler : ICycler
+    {
+        public string Name => "Random";
+
+        public int Cycle(RandomSource rand, int current, int max)
+        {
+            return rand.Next(max);
+        }
+    }
+    
+    [StateClass]
+    public class RandomNoRepeatCycle : ICycler
+    {
+        public string Name => "RandomNoRepeat";
+
+        public int Cycle(RandomSource rand, int current, int max)
+        {
+            int p = rand.Next(max - 1); // grab a provisional
+            if (p >= current) // if the provisional c or above
+                p++; // add one
+            return p; // return the no-longer provisional
+        }
+    }
+
+    public class Cyclers
+    {
+        public static readonly ICycler Loop = new LoopCycler();
+        public static readonly ICycler Random = new RandomCycler();
+        public static readonly ICycler RandomNoRepeat = new RandomNoRepeatCycle();
+
+        public static ICycler GetStandardCycler(string name)
+        {
+            if (name.Equals("Loop", StringComparison.InvariantCultureIgnoreCase))
+                return Loop;
+            else if (name.Equals("Random", StringComparison.InvariantCultureIgnoreCase))
+                return Random;
+            else if (name.Equals("RandomNoRepeat", StringComparison.InvariantCultureIgnoreCase))
+                return RandomNoRepeat;
+            else
+                throw new ArgumentException("Unrecognised cycler \"" + name + "\"", nameof(name));
+        }
+    }
+
+    [StateClass]
+    public class ExperimentConfiguration
+    {
+        [Obsolete]
+        protected ExperimentConfiguration()
+        { }
+
+        public ExperimentConfiguration(int size, IReadOnlyList<ITarget> targets, ICycler targetCycler, int epochs, int generationsPerTargetPerEpoch, double initialStateResetProbability, Range initialStateResetRange, bool shuffleTargets, DevelopmentRules developmentRules, ReproductionRules reproductionRules, JudgementRules judgementRules)
+        {
+            Size = size;
+            Targets = targets;
+            TargetCycler = targetCycler;
+            Epochs = epochs;
+            GenerationsPerTargetPerEpoch = generationsPerTargetPerEpoch;
+            InitialStateResetProbability = initialStateResetProbability;
+            InitialStateResetRange = initialStateResetRange;
+            ShuffleTargets = shuffleTargets;
+            DevelopmentRules = developmentRules;
+            ReproductionRules = reproductionRules;
+            JudgementRules = judgementRules;
+        }
+
+        /// <summary>
+        /// The size of genomes and targets
+        /// </summary>
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        /// <summary>
+        /// The targets to epose the phenotypes to
+        /// </summary>
+        [SimpleStateProperty("Targets")]
+        public IReadOnlyList<ITarget> Targets { get; private set; }
+
+        /// <summary>
+        /// Whether to shuffle the targets each epoch
+        /// </summary>
+        [SimpleStateProperty("ShuffleTargets")]
+        public bool ShuffleTargets { get; private set; }
+
+        /// <summary>
+        /// The method by which to cycle targets
+        /// (If you can't think of anything better, Cyclers.Loop is probably fine)
+        /// </summary>
+        [SimpleStateProperty("TargetCycler")]
+        public ICycler TargetCycler { get; private set; }
+
+        /// <summary>
+        /// The number of epochs to run
+        /// </summary>
+        [SimpleStateProperty("Epochs")]
+        public int Epochs { get; private set; }
+
+        /// <summary>
+        /// The probaility of reseting the initial state at the start of each Epoch
+        /// (Is this defined for non-population experiments?)
+        /// </summary>
+        [SimpleStateProperty("InitialStateResetProbability")]
+        public double InitialStateResetProbability { get; private set; }
+
+        /// <summary>
+        /// The range in which to set a randomised initial state
+        /// </summary>
+        [SimpleStateProperty("InitialStateResetRange")]
+        public Range InitialStateResetRange { get; private set; }
+
+        /// <summary>
+        /// Switching interval
+        /// K in the paper
+        /// </summary>
+        [SimpleStateProperty("GenerationsPerTargetPerEpoch")]
+        public int GenerationsPerTargetPerEpoch { get; private set; }
+
+        /// <summary>
+        /// The Development Rules
+        /// </summary>
+        [SimpleStateProperty("DevelopmentRules")]
+        public DevelopmentRules DevelopmentRules { get; private set; }
+
+        /// <summary>
+        /// The Reproduction Rules
+        /// </summary>
+        [SimpleStateProperty("ReproductionRules")]
+        public ReproductionRules ReproductionRules { get; private set; }
+
+        /// <summary>
+        /// The Judgement Rules
+        /// </summary>
+        [SimpleStateProperty("JudgementRules")]
+        public JudgementRules JudgementRules { get; private set; }
+        
+        public void WriteOut(System.IO.StreamWriter cw)
+        {
+            cw.WriteLine();
+            cw.WriteLine($"Size (N) = {Size}");
+            cw.WriteLine($"Epochs = {Epochs}");
+            cw.WriteLine($"SwitchingRate (K) = {GenerationsPerTargetPerEpoch}");
+            cw.WriteLine($"Target Count = {Targets.Count}");
+            cw.WriteLine($"Target Cycler = {TargetCycler.Name}");
+            cw.WriteLine($"Shuffle Targets = {ShuffleTargets}");
+            cw.WriteLine($"Initial State Reset Probability = {InitialStateResetProbability}");
+            cw.WriteLine($"Initial State Reset Range = {InitialStateResetRange?.ToString() ?? "(none)"}");
+
+            cw.WriteLine();
+            cw.WriteLine($"DRules.{nameof(DevelopmentRules.UpdateRate)} (τ1) = {DevelopmentRules.UpdateRate}");
+            cw.WriteLine($"DRules.{nameof(DevelopmentRules.DecayRate)} (τ2) = {DevelopmentRules.DecayRate}");
+            cw.WriteLine($"DRules.{nameof(DevelopmentRules.TimeSteps)} (T) = {DevelopmentRules.TimeSteps}");
+            cw.WriteLine($"DRules.{nameof(DevelopmentRules.Squash)} (σ) = {DevelopmentRules.Squash.Name}");
+            cw.WriteLine($"DRules.{nameof(DevelopmentRules.RescaleScale)} = {DevelopmentRules.RescaleScale}");
+            cw.WriteLine($"DRules.{nameof(DevelopmentRules.RescaleOffset)} = {DevelopmentRules.RescaleOffset}");
+
+            cw.WriteLine();
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialStateMutationSize)} (M_G) = {ReproductionRules.InitialStateMutationSize}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialStateMutationType)} (M_G type) = {ReproductionRules.InitialStateMutationType}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixMutationSize)} (M_B) = {ReproductionRules.DevelopmentalTransformationMatrixMutationSize}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixMutationType)} (M_B type) = {ReproductionRules.DevelopmentalTransformationMatrixMutationType}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixRate)} (R_B) = {ReproductionRules.DevelopmentalTransformationMatrixRate}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.ExclusiveBMutation)} (BEx) = {ReproductionRules.ExclusiveBMutation}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialTraitUpdates)} (C_G) = {ReproductionRules.InitialTraitUpdates}");
+            cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialStateClamping)} = {ReproductionRules.InitialStateClamping}");
+
+            cw.WriteLine();
+            cw.WriteLine($"JRules.{nameof(JudgementRules.NoiseFactor)} (κ) = {JudgementRules.NoiseFactor}");
+            cw.WriteLine($"JRules.{nameof(JudgementRules.RegularisationFactor)} (λ) = {JudgementRules.RegularisationFactor}");
+            cw.WriteLine($"JRules.{nameof(JudgementRules.RegularisationFunction)} (ϕ) = {JudgementRules.RegularisationFunction.Name}");
+
+            cw.WriteLine();
+            cw.WriteLine($"Targets");
+            for (int i = 0; i < Targets.Count; i++)
+            {
+                cw.WriteLine($"Target{i}: {Targets[i].FullName} ({Targets[i].FriendlyName})");
+                cw.WriteLine($"{Targets[i].Description}");
+            }
+        }
+    }
+
+    /// <summary>
+    /// Signals that a new round is about to begin with a new target
+    /// </summary>
+    /// <param name="genome">The current genome</param>
+    /// <param name="target">The new target</param>
+    /// <param name="epoch">The current epoch (1-indexed)</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void EndTargetDelegate(DenseGenome genome, ITarget target, int epoch, int generationCount);
+    
+    /// <summary>
+    /// Signals that a round has just ended
+    /// </summary>
+    /// <param name="genome">The current genome</param>
+    /// <param name="target">The current (about to be swapped out) target</param>
+    /// <param name="epoch">The current epoch (1-indexed)</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void StartTargetDelegate(DenseGenome genome, ITarget target, int epoch, int generationCount);
+
+    /// <summary>
+    /// Signals that an epoch has just ended
+    /// </summary>
+    /// <param name="genome">The current genome</param>
+    /// <param name="epochCount">The number of epochs run</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void EndEpochDelegate(DenseGenome genome, int epochCount, int generationCount);
+    
+    /// <summary>
+    /// Signals that an experiment has just finished
+    /// </summary>
+    /// <param name="genome">The terminal genome</param>
+    /// <param name="epochCount">The number of epochs run</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void FinishedDelegate(DenseGenome genome, int epochCount, int generationCount);
+
+    public class ExperimentFeedback
+    {
+        // what is the point of Event<T>?
+        public Event<EndTargetDelegate> EndTarget => _endTarget;
+        private Event<EndTargetDelegate> _endTarget = new Event<EndTargetDelegate>();
+
+        public Event<StartTargetDelegate> StartTarget => _startTarget;
+        private Event<StartTargetDelegate> _startTarget = new Event<StartTargetDelegate>();
+        
+        public Event<EndEpochDelegate> EndEpoch => _endEpoch;
+        private Event<EndEpochDelegate> _endEpoch = new Event<EndEpochDelegate>();
+        
+        public Event<FinishedDelegate> Finished => _finished;
+        private Event<FinishedDelegate> _finished = new Event<FinishedDelegate>();
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Hebbian/Hebbian.cs b/M4MCode/M4M_MkI/M4M.Model/Hebbian/Hebbian.cs
new file mode 100644
index 0000000..ebc6f7c
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Hebbian/Hebbian.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.Hebbian
+{
+    // State Provider registered in GraphSerialisation.cs
+    public enum HebbianType : int
+    {
+        Standard,
+        IncreaseOnly,
+        DecreaseOnly,
+    }
+
+    // State Provider registered in GraphSerialisation.cs
+    public enum MatrixNormalisation : int
+    {
+        None,
+        IterativeNormalisation, // Sinkhorn-Knopp
+    }
+
+    /// <summary>
+    /// Hebbian Spinner.
+    /// Hill-climbs a population of 1, before performing a Hebbian update to the TransMat.
+    /// Doesn't mess with the rules: you probably want to provide a NullTransMatMutator.
+    /// </summary>
+    [State.StateClass]
+    public class HebbianSpinner : IPopulationSpinner<DenseIndividual>
+    {
+        [Obsolete]
+        protected HebbianSpinner()
+        {
+        }
+
+        public HebbianSpinner(double fitnessWeightingPower, HebbianType hebbianType, bool signedHebbian, IPopulationSpinner<DenseIndividual> innerSpinner, bool noDiagonal, MatrixNormalisation matrixNormalisation, double fitnessRescale, double fitnessOffset)
+        {
+            FitnessWeightingPower = fitnessWeightingPower;
+            HebbianType = hebbianType;
+            SignedHebbian = signedHebbian;
+            InnerSpinner = innerSpinner;
+            NoDiagonal = noDiagonal;
+            MatrixNormalisation = matrixNormalisation;
+            FitnessRescale = fitnessRescale;
+            FitnessOffset = fitnessOffset;
+        }
+
+        public string Name => "HebbianSpinner";
+
+        public string Description => $"{Name} (FitnessWeightingPower = {FitnessWeightingPower}, HebbianType = {HebbianType}, SignedHebbian = {SignedHebbian}, InnerSpinner = {InnerSpinner?.Description ?? "(null)"}, NoDiagonal = {NoDiagonal}, MatrixNormalisation = {MatrixNormalisation}, FitnessRescale = {FitnessRescale}, FitnessOffset = {FitnessOffset})";
+
+        /// <summary>
+        /// The power to which to raise the fitness for the fitness factor on update magnitude.
+        /// Fitness should be non-negative for non-zero power.
+        /// </summary>
+        [State.SimpleStateProperty("FitnessWeightingPower")]
+        public double FitnessWeightingPower { get; private set; } = 0.0;
+
+        [State.SimpleStateProperty("FitnessRescale")]
+        public double FitnessRescale { get; private set; } = 1.0;
+
+        [State.SimpleStateProperty("FitnessOffset")]
+        public double FitnessOffset { get; private set; } = 0.0;
+
+        [State.SimpleStateProperty("HebbianType")]
+        public HebbianType HebbianType { get; private set; }
+
+        [State.SimpleStateProperty("SignedHebbian")]
+        public bool SignedHebbian { get; private set; }
+
+        [State.SimpleStateProperty("InnerSpinner")]
+        public IPopulationSpinner<DenseIndividual> InnerSpinner { get; private set; }
+
+        [State.SimpleStateProperty("NoDiagonal")]
+        public bool NoDiagonal { get; private set; } = false;
+
+        [State.SimpleStateProperty("MatrixNormalisation")]
+        public MatrixNormalisation MatrixNormalisation { get; private set; } = MatrixNormalisation.None;
+
+        public IReadOnlyList<IndividualJudgement<DenseIndividual>> SpinPopulation(Population<DenseIndividual> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<DenseIndividual> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<DenseIndividual>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            if (population.Count != 1)
+                throw new ArgumentException("Population must have exactly 1 individual to use a HebbianSpinner");
+
+            // no Gen0
+            if (InnerSpinner != null)
+            {
+                InnerSpinner.SpinPopulation(population, context, rrules, drules, jrules, target, selectorPreparer, generations, judgementFeedback, eliteCount, hillclimberMode);
+            }
+            else
+            {
+                // spin as normal
+                population.SpinPopulation(context, rrules, drules, jrules, target, selectorPreparer, generations, judgementFeedback, eliteCount, hillclimberMode);
+            }
+
+            // now extract and perform Hebbian update
+            var individual = population.ExtractAll()[0].Clone(context);
+
+            double q = rrules.DevelopmentalTransformationMatrixMutationSize;
+            HebbianStepInplace(individual, SignedHebbian, q, HebbianType, context, rrules, drules, jrules, target, FitnessWeightingPower, NoDiagonal, MatrixNormalisation, FitnessRescale, FitnessOffset);
+            individual.DevelopInplace(context, drules);
+
+            var cloned = individual.Clone(context);
+            var j = cloned.Judge(jrules, target);
+
+            population.Introduce(individual);
+            judgementFeedback?.Invoke(generations, new[] { new IndividualJudgement<DenseIndividual>(individual, individual.Judge(jrules, target)) });
+
+            return new[] { new IndividualJudgement<DenseIndividual>(cloned, j) };
+        }
+
+        public static void HebbianStepInplace(DenseIndividual individual, bool signed, double mag, HebbianType hebbianType, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, double FitnessWeightingPower, bool noDiagonal, MatrixNormalisation matrixNormalisation, double fitnessRescale, double fitnessOffset)
+        {
+            var mat = individual.Genome.TransMat;
+            var p = individual.Phenotype.Vector;
+
+            var rowSums = matrixNormalisation == MatrixNormalisation.IterativeNormalisation
+                ? mat.RowSums()
+                : null;
+            var colSums = matrixNormalisation == MatrixNormalisation.IterativeNormalisation
+                ? mat.ColumnSums()
+                : null;
+
+            IEnumerable<MatrixEntryAddress> entries = individual.Genome.TransMatIndexOpenEntries ?? MatrixEntryAddress.CompleteLazy(individual.Genome.Size, individual.Genome.Size);
+
+            var rand = context.Rand;
+
+            double fitnessFactor;
+
+            if (FitnessWeightingPower == 0.0)
+            {
+                // don't measure
+                fitnessFactor = 1.0;
+            }
+            else
+            {
+                target.NextGeneration(rand, jrules);
+                individual.DevelopInplace(context, drules);
+                double w = individual.Judge(jrules, target).CombinedFitness;
+                w = w * fitnessRescale + fitnessOffset;
+                fitnessFactor = Math.Pow(w, FitnessWeightingPower);
+            }
+
+            foreach (var entry in entries)
+            {
+                if (noDiagonal && entry.Row == entry.Col)
+                {
+                    continue;
+                }
+
+                if (signed)
+                {
+                    int rs = Math.Sign(p[entry.Row]);
+                    int cs = Math.Sign(p[entry.Col]);
+                    int sign = Math.Sign(rs * cs);
+
+                    if (sign > 0)
+                    {
+                        if (hebbianType == HebbianType.DecreaseOnly)
+                            continue;
+
+                        mat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(mat[entry.Row, entry.Col] + mag * fitnessFactor);
+                    }
+                    else if (sign < 0)
+                    {
+                        if (hebbianType == HebbianType.IncreaseOnly)
+                            continue;
+
+                        mat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(mat[entry.Row, entry.Col] - mag * fitnessFactor);
+                    }
+                }
+                else
+                {
+                    var product = p[entry.Row] * p[entry.Col];
+                    int sign = Math.Sign(product);
+
+                    if (sign > 0)
+                    {
+                        if (hebbianType == HebbianType.DecreaseOnly)
+                            continue;
+
+                        mat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(mat[entry.Row, entry.Col] + product * mag * fitnessFactor);
+                    }
+                    else if (sign < 0)
+                    {
+                        if (hebbianType == HebbianType.IncreaseOnly)
+                            continue;
+
+                        mat[entry.Row, entry.Col] = rrules.DevelopmentalTransformationMatrixClamping.Clamp(mat[entry.Row, entry.Col] - product * mag * fitnessFactor);
+                    }
+                }
+            }
+
+            switch (matrixNormalisation)
+            {
+                case MatrixNormalisation.IterativeNormalisation:
+                    var normalised = Misc.IterativeNormalise(mat, rowSums, colSums);
+                    normalised.CopyTo(mat);
+                    break;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Hopfield/Hopfield.cs b/M4MCode/M4M_MkI/M4M.Model/Hopfield/Hopfield.cs
new file mode 100644
index 0000000..1d879e4
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Hopfield/Hopfield.cs
@@ -0,0 +1,295 @@
+using M4M.Epistatics;
+using M4M.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.Hopfield
+{
+    // State Provider registered in GraphSerialisation.cs
+    public enum BiasType : int
+    {
+        /// <summary>
+        /// No bias is to be added.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// A constant bias is added.
+        /// </summary>
+        Constant,
+
+        /// <summary>
+        /// A bias proportional to the state is added.
+        /// </summary>
+        Linear,
+
+        /// <summary>
+        /// A bias proportional to the square of the state is added.
+        /// </summary>
+        Quadratic,
+    }
+
+    // State Provider registered in GraphSerialisation.cs
+    public enum InteractionType : int
+    {
+        /// <summary>
+        /// No interaction is added.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// An interaction proportional to the interaction product is added.
+        /// </summary>
+        Linear,
+
+        /// <summary>
+        /// An interaction proportional to the product of interaction product and state is added.
+        /// </summary>
+        Quadratic,
+    }
+
+    // State Provider registered in GraphSerialisation.cs
+    public enum InteractionMatrixSource : int
+    {
+        /// <summary>
+        /// The interaction matrix is sourced from the genome's DTM.
+        /// </summary>
+        Genome,
+
+        /// <summary>
+        /// The interaction matrix is sourced from the correlation matrix target's constraint matrix.
+        /// </summary>
+        Target,
+
+        /// <summary>
+        /// The interaction matrix is the sum of the genome's DTM and the correlation matrix target's constraint matrix.
+        /// </summary>
+        GenomeAndTarget,
+    }
+
+    // State Provider registered in GraphSerialisation.cs
+    public enum BiasVectorSource : int
+    {
+        /// <summary>
+        /// The interaction matrix is sourced from the genome's bias vector.
+        /// </summary>
+        Genome,
+
+        /// <summary>
+        /// The interaction matrix is sourced from the correlation matrix target's forcing vector.
+        /// </summary>
+        Target,
+
+        /// <summary>
+        /// The interaction matrix is the sum of the genome's bias vector and the correlation matrix target's forcing vector.
+        /// </summary>
+        GenomeAndTarget,
+    }
+
+    /// <summary>
+    /// Hopfield Spinner.
+    /// Performs lots of relaxation steps, rather than mutating and developing
+    /// It's a total cludge, but who cares.
+    /// </summary>
+    [State.StateClass]
+    public class HopfieldSpinner : IPopulationSpinner<DenseIndividual>
+    {
+        [Obsolete]
+        protected HopfieldSpinner()
+        { }
+
+        public HopfieldSpinner(ISquash squash, double updateRate, double decayRate, BiasType biasType, InteractionType interactionType, InteractionMatrixSource interactionMatrixSource, BiasVectorSource biasVectorSource)
+        {
+            Squash = squash;
+            UpdateRate = updateRate;
+            DecayRate = decayRate;
+            BiasType = biasType;
+            InteractionType = interactionType;
+            InteractionMatrixSource = interactionMatrixSource;
+            BiasVectorSource = biasVectorSource;
+        }
+
+        public string Name => "HopfieldSpinner";
+
+        public string Description => $"{Name} (Squash={Squash.Name}, UpdateRate={UpdateRate}, DecayRate={DecayRate}, BiasType={BiasType}, InteractionType={InteractionType}, InteractionMatrixSource={InteractionMatrixSource}, BiasVectorSource={BiasVectorSource})";
+
+        [SimpleStateProperty("Squash")]
+        public ISquash Squash { get; private set; }
+
+        [SimpleStateProperty("UpdateRate")]
+        public double UpdateRate { get; private set; } = 0.1;
+
+        [SimpleStateProperty("DecayRate")]
+        public double DecayRate { get; private set; } = 0.1;
+
+        [SimpleStateProperty("BiasType")]
+        public BiasType BiasType { get; private set; } = BiasType.Constant;
+
+        [SimpleStateProperty("InteractionType")]
+        public InteractionType InteractionType { get; private set; } = InteractionType.Linear;
+
+        [SimpleStateProperty("InteractionMatrixSource")]
+        public InteractionMatrixSource InteractionMatrixSource { get; private set; } = InteractionMatrixSource.Genome;
+
+        [SimpleStateProperty("BiasVectorSource")]
+        public BiasVectorSource BiasVectorSource { get; private set; } = BiasVectorSource.Genome;
+
+        public IReadOnlyList<IndividualJudgement<DenseIndividual>> SpinPopulation(Population<DenseIndividual> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<DenseIndividual> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<DenseIndividual>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            MathNet.Numerics.LinearAlgebra.Matrix<double> mat = null;
+            MathNet.Numerics.LinearAlgebra.Vector<double> vec = null;
+            MathNet.Numerics.LinearAlgebra.Vector<double> update = null;
+            MathNet.Numerics.LinearAlgebra.Vector<double> update2 = null;
+            int count = population.Count;
+
+            var individuals = population.ExtractAll();
+            var judgements = new IndividualJudgement<DenseIndividual>[individuals.Length];
+
+            for (int generation = 0; generation < generations; generation++)
+            {
+                target.NextGeneration(context.Rand, jrules);
+                var cmt = target as CorrelationMatrixTarget;
+
+                if (generation == 0 && judgementFeedback != null)
+                {
+                    for (int i = 0; i < individuals.Length; i++)
+                    {
+                        var individual = individuals[i].Clone(context);
+                        individual.DevelopInplace(context, drules);
+                        judgements[i] = new IndividualJudgement<DenseIndividual>(individual, individual.Judge(jrules, target));
+                    }
+
+                    judgementFeedback.Invoke(0, judgements.ToArray());
+                }
+
+                for (int i = 0; i < individuals.Length; i++)
+                {
+                    Step(cmt, individuals[i].Genome, ref mat, ref vec, ref update, ref update2, Squash.Delegate, UpdateRate, DecayRate, BiasType, InteractionType, InteractionMatrixSource, BiasVectorSource);
+
+                    if (judgementFeedback != null || generation == generations - 1)
+                    {
+                        individuals[i].DevelopInplace(context, drules);
+                        var individual = individuals[i].Clone(context);
+                        judgements[i] = new IndividualJudgement<DenseIndividual>(individual, individual.Judge(jrules, target));
+                    }
+                }
+
+                judgementFeedback?.Invoke(generation + 1, judgements.ToArray());
+            }
+
+            population.IntroduceMany(individuals);
+
+            return judgements;
+        }
+
+        /// <summary>
+        /// Steps the initial state of the given genome.
+        /// </summary>
+        public static void Step(CorrelationMatrixTarget cmt, DenseGenome genome, ref MathNet.Numerics.LinearAlgebra.Matrix<double> mat2, ref MathNet.Numerics.LinearAlgebra.Vector<double> vec2, ref MathNet.Numerics.LinearAlgebra.Vector<double> update, ref MathNet.Numerics.LinearAlgebra.Vector<double> update2, Func<double, double> squash, double updateRate, double decayRate, BiasType biasType, InteractionType interactionType, InteractionMatrixSource interactionMatrixSource, BiasVectorSource biasVectorSource)
+        {
+            if (interactionType != InteractionType.None && (update == null || update.Count != genome.Size))
+            {
+                update = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(genome.Size);
+            }
+
+            var state = genome.InitialState;
+            MathNet.Numerics.LinearAlgebra.Matrix<double> mat = null;
+            MathNet.Numerics.LinearAlgebra.Vector<double> bias = null;
+
+            if (interactionType != InteractionType.None)
+            {
+                switch (interactionMatrixSource)
+                {
+                    case InteractionMatrixSource.Genome:
+                        mat = genome.TransMat;
+                        break;
+                    case InteractionMatrixSource.Target:
+                        mat = cmt.CorrelationMatrix;
+                        break;
+                    case InteractionMatrixSource.GenomeAndTarget:
+                        if (mat2 == null || mat2.RowCount != genome.Size || mat2.ColumnCount != genome.Size)
+                        {
+                            mat2 = MathNet.Numerics.LinearAlgebra.CreateMatrix.Dense<double>(genome.Size, genome.Size);
+                        }
+
+                        mat = mat2;
+                        genome.TransMat.CopyTo(mat);
+                        mat.Add(cmt.CorrelationMatrix, mat);
+                        break;
+                    default:
+                        throw new ArgumentOutOfRangeException(nameof(interactionMatrixSource));
+                }
+
+                mat.Multiply(state, update); // this is where the CPU spends most of its time (N^2)
+            }
+
+            if (interactionType == InteractionType.Quadratic)
+            {
+                update.PointwiseMultiply(state, update);
+            }
+
+            if (biasType != BiasType.None)
+            {
+                switch (biasVectorSource)
+                {
+                    case BiasVectorSource.Genome:
+                        bias = genome.BiasVector;
+                        break;
+                    case BiasVectorSource.Target:
+                        bias = cmt.ForcingVector;
+                        break;
+                    case BiasVectorSource.GenomeAndTarget:
+                        if (vec2 == null || vec2.Count != genome.Size)
+                        {
+                            vec2 = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(genome.Size);
+                        }
+
+                        bias = vec2;
+                        genome.InitialState.CopyTo(bias);
+                        bias.Add(cmt.ForcingVector, bias);
+                        break;
+                    default:
+                        throw new ArgumentOutOfRangeException(nameof(biasVectorSource));
+                }
+            }
+
+            if (bias != null)
+            {
+                switch (biasType)
+                {
+                    case BiasType.None:
+                        break;
+                    case BiasType.Constant:
+                        update.Add(bias, update);
+                        break;
+                    case BiasType.Linear:
+                    case BiasType.Quadratic:
+                        if (update2 == null || update2.Count != genome.Size)
+                        {
+                            update2 = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(genome.Size);
+                        }
+
+                        bias.PointwiseMultiply(state, update2);
+                        if (biasType == BiasType.Quadratic)
+                        {
+                            update2.PointwiseMultiply(state, update2);
+                        }
+
+                        update.Add(bias, update2);
+
+                        break;
+                    default:
+                        throw new ArgumentOutOfRangeException(nameof(biasType));
+                }
+            }
+
+            update.MapInplace(squash);
+            update.Multiply(updateRate, update);
+
+            state.Multiply(1.0 - decayRate, state);
+
+            state.Add(update, state);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/LotkaVolterra/LotkaVolterra.cs b/M4MCode/M4M_MkI/M4M.Model/LotkaVolterra/LotkaVolterra.cs
new file mode 100644
index 0000000..ddf536b
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/LotkaVolterra/LotkaVolterra.cs
@@ -0,0 +1,106 @@
+using M4M.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.LotkaVolterra
+{
+    /// <summary>
+    /// LotkaVolterra Spinner.
+    /// Performs lots of relaxation steps, rather than mutating and developing
+    /// It's a total cludge, but who cares.
+    /// </summary>
+    [State.StateClass]
+    public class LotkaVolterraSpinner : IPopulationSpinner<DenseIndividual>
+    {
+        [Obsolete]
+        protected LotkaVolterraSpinner()
+        { }
+
+        public LotkaVolterraSpinner(double growthRate)
+        {
+            GrowthRate = growthRate;
+        }
+
+        public string Name => "LotkaVolterraSpinner";
+
+        public string Description => $"{Name} (GrowthReate={GrowthRate})";
+
+        [SimpleStateProperty("GrowthReate")]
+        public double GrowthRate { get; private set; }
+
+        public IReadOnlyList<IndividualJudgement<DenseIndividual>> SpinPopulation(Population<DenseIndividual> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<DenseIndividual> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<DenseIndividual>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            MathNet.Numerics.LinearAlgebra.Vector<double> update = null;
+            MathNet.Numerics.LinearAlgebra.Vector<double> update2 = null;
+            int count = population.Count;
+
+            var individuals = population.ExtractAll();
+            var judgements = new IndividualJudgement<DenseIndividual>[individuals.Length];
+
+            for (int generation = 0; generation < generations; generation++)
+            {
+                target.NextGeneration(context.Rand, jrules);
+
+                if (generation == 0 && judgementFeedback != null)
+                {
+                    for (int i = 0; i < individuals.Length; i++)
+                    {
+                        var individual = individuals[i].Clone(context);
+                        individual.DevelopInplace(context, drules);
+                        judgements[i] = new IndividualJudgement<DenseIndividual>(individual, individual.Judge(jrules, target));
+                    }
+
+                    judgementFeedback.Invoke(0, judgements.ToArray());
+                }
+
+                for (int i = 0; i < individuals.Length; i++)
+                {
+                    Step(individuals[i].Genome, ref update, ref update2, GrowthRate);
+
+                    if (judgementFeedback != null || generation == generations - 1)
+                    {
+                        individuals[i].DevelopInplace(context, drules);
+                        var individual = individuals[i].Clone(context);
+                        judgements[i] = new IndividualJudgement<DenseIndividual>(individual, individual.Judge(jrules, target));
+                    }
+                }
+
+                judgementFeedback?.Invoke(generation + 1, judgements.ToArray());
+            }
+
+            population.IntroduceMany(individuals);
+
+            return judgements;
+        }
+
+        /// <summary>
+        /// Steps the initial state of the given genome.
+        /// </summary>
+        public static void Step(DenseGenome genome, ref MathNet.Numerics.LinearAlgebra.Vector<double> updateLeft, ref MathNet.Numerics.LinearAlgebra.Vector<double> updateRight, double growthRate)
+        {
+            if (updateLeft == null || updateLeft.Count != genome.Size)
+            {
+                updateLeft = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(genome.Size);
+            }
+
+            if (updateRight == null || updateRight.Count != genome.Size)
+            {
+                updateRight = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(genome.Size);
+            }
+
+            var state = genome.InitialState;
+            var transMat = genome.TransMat;
+            var bias = genome.BiasVector;
+
+            transMat.Multiply(state, updateRight); // r = Wx
+            updateRight.Add(bias, updateRight); // r = k+Wx
+
+            state.Multiply(growthRate, updateLeft); // l = mx
+            updateLeft.PointwiseDivide(bias, updateLeft); // l = mx/k
+
+            updateLeft.PointwiseMultiply(updateRight, updateRight); // lr = (mx/k)(k+Wx)
+            state.Add(updateRight, state);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/M4M.Model.csproj b/M4MCode/M4M_MkI/M4M.Model/M4M.Model.csproj
new file mode 100644
index 0000000..462b5cd
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/M4M.Model.csproj
@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <Content Remove="Info/version.txt" />
+    <Content Remove="Info/buildinfo.txt" />
+  </ItemGroup>
+  <Target Name="UpdateVersion" BeforeTargets="BeforeBuild" Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <Exec Command="dotnet ../BuildTools/AutoVersion.dll info Info/buildinfo.txt Info/version.txt" ContinueOnError="WarnAndContinue" />
+  </Target>
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="NetState">
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <EmbeddedResource Include="Info/version.txt" />
+    <EmbeddedResource Include="Info/buildinfo.txt" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MathNet.Numerics" Version="4.9.1" />
+  </ItemGroup>
+
+</Project>
diff --git a/M4MCode/M4M_MkI/M4M.Model/MatrixPool.cs b/M4MCode/M4M_MkI/M4M.Model/MatrixPool.cs
new file mode 100644
index 0000000..a08fe05
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/MatrixPool.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public struct MatrixDimensions
+    {
+        public readonly int Rows;
+        public readonly int Columns;
+
+        public MatrixDimensions(int rows, int columns)
+        {
+            Rows = rows;
+            Columns = columns;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is MatrixDimensions))
+            {
+                return false;
+            }
+
+            var dimensions = (MatrixDimensions)obj;
+            return Rows == dimensions.Rows &&
+                   Columns == dimensions.Columns;
+        }
+
+        public override int GetHashCode()
+        {
+            var hashCode = 1026099028;
+            hashCode = hashCode * -1521134295 + Rows.GetHashCode();
+            hashCode = hashCode * -1521134295 + Columns.GetHashCode();
+            return hashCode;
+        }
+
+        public static bool operator ==(MatrixDimensions left, MatrixDimensions right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(MatrixDimensions left, MatrixDimensions right)
+        {
+            return !(left == right);
+        }
+    }
+
+    public class MatrixDimensionsComparer : IEqualityComparer<MatrixDimensions>
+    {
+        public static readonly MatrixDimensionsComparer Instance = new MatrixDimensionsComparer();
+
+        private MatrixDimensionsComparer()
+        {
+        }
+
+        public bool Equals(MatrixDimensions x, MatrixDimensions y)
+        {
+            return x.Rows == y.Rows && x.Columns == y.Columns;
+        }
+
+        public int GetHashCode(MatrixDimensions matDims)
+        {
+            return matDims.GetHashCode();
+        }
+    }
+
+    /// <summary>
+    /// Provides means to recycle matrices of specific dimensions
+    /// NOT Thread Safe
+    /// </summary>
+    public class MatrixPool
+    {
+        /// <summary>
+        /// Global switch for MatrixPooling
+        /// If you are not careful, matrix pooling can ruin your day: verify that your code works with matrix pooling before depending on it for anything
+        /// </summary>
+        public static bool EnableMatrixPools = false;
+        public long CacheHits { get; private set; }
+        public long CacheMisses { get; private set; }
+        public long CacheCaps { get; private set; }
+
+        public MatrixDimensions Dim { get; }
+        public int MaxCount { get; }
+        private readonly Stack<Linear.Matrix<double>> Stack = new Stack<Linear.Matrix<double>>();
+
+        public MatrixPool(MatrixDimensions dim, int maxCount)
+        {
+            Dim = dim;
+            MaxCount = maxCount;
+        }
+
+        public void Release(Linear.Matrix<double> unused)
+        {
+            if (!EnableMatrixPools)
+                return;
+
+            if (Stack.Count < MaxCount)
+            {
+                Stack.Push(unused);
+            }
+            else
+            {
+                CacheCaps++;
+            }
+        }
+
+        public Linear.Matrix<double> Grab()
+        {
+            if (!EnableMatrixPools)
+                return Linear.CreateMatrix.Dense<double>(Dim.Rows, Dim.Columns);
+
+            if (Stack.Count > 0)
+            {
+                CacheHits++;
+                return Stack.Pop();
+            }
+            else
+            {
+                CacheMisses++;
+                return Linear.CreateMatrix.Dense<double>(Dim.Rows, Dim.Columns);
+            }
+        }
+
+        public Linear.Matrix<double> Clone(Linear.Matrix<double> original)
+        {
+            if (!EnableMatrixPools)
+                return original.Clone();
+
+            if (Stack.Count > 0)
+            {
+                CacheHits++;
+                var mat = Stack.Pop();
+                original.CopyTo(mat);
+                return mat;
+            }
+            else
+            {
+                CacheMisses++;
+                return original.Clone();
+            }
+        }
+    }
+
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Misc.cs b/M4MCode/M4M_MkI/M4M.Model/Misc.cs
new file mode 100644
index 0000000..d9deee0
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Misc.cs
@@ -0,0 +1,1945 @@
+using M4M.Modular;
+using M4M.State;
+using MathNet.Numerics.LinearAlgebra.Complex;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enogh)
+
+namespace M4M
+{
+    public interface IEvent<T>
+    {
+        void Register(T t);
+        void Unregister(T t);
+    }
+
+    public class Event<T> : IEvent<T>
+    {
+        HashSet<T> Entries { get; } = new HashSet<T>();
+
+        public Event()
+        {
+        }
+
+        public void Register(T t)
+        {
+            Entries.Add(t);
+        }
+
+        public void Unregister(T t)
+        {
+            Entries.Remove(t);
+        }
+
+        public void Call(Action<T> act)
+        {
+            foreach (T t in Entries)
+            {
+                act(t);
+            }
+        }
+
+        public bool HasAny => Entries.Count > 0;
+    }
+
+    public struct Percentile
+    {
+        public Percentile(double position, double value)
+        {
+            Position = position;
+            Value = value;
+        }
+
+        public double Position { get; }
+        public double Value { get; }
+
+        /// <summary>
+        /// Assumes that reusableBuffer is null, or that it is the right length; same goes for percentiles
+        /// </summary>
+        public static void ComputePercentiles(double[] positions, IEnumerable<double> unorderedSamples, ref double[] reusableBuffer, ref Percentile[] percentiles)
+        {
+            if (reusableBuffer == null)
+            {
+                reusableBuffer = unorderedSamples.ToArray();
+            }
+            else
+            {
+                int i = 0;
+                foreach (double d in unorderedSamples)
+                {
+                    reusableBuffer[i++] = d;
+                }
+
+                if (i != reusableBuffer.Length)
+                    throw new ArgumentException("Reusable buffer was too big");
+            }
+            
+            Array.Sort(reusableBuffer);
+
+            if (percentiles == null)
+                percentiles = new Percentile[positions.Length];
+            
+            for (int i = 0; i < positions.Length; i++)
+            {
+                double p = positions[i];
+
+                int idx = (int)(p * (double)(reusableBuffer.Length));
+                idx = Misc.Clamp(idx, 0, reusableBuffer.Length - 1);
+
+                percentiles[i] = new Percentile(p, reusableBuffer[idx]);
+            }
+        }
+    }
+
+    public class Indexed<T>
+    {
+        public Indexed(int index, T value)
+        {
+            Index = index;
+            Value = value;
+        }
+
+        public int Index { get; }
+        public T Value { get; }
+
+        public static IEnumerable<Indexed<T>> IndexSeq(IEnumerable<T> seq, int startIndex = 0, int step = 1)
+        {
+            int i = int.MaxValue; // sadly we have to give a value here... and it will always be meaningless
+            bool first = true;
+            foreach (T t in seq)
+            {
+                if (first) // we do it this way so that it can't overflow on the last index (totally worth it)
+                {
+                    i = startIndex;
+                    first = false;
+                }
+                else
+                {
+                     i += step;
+                }
+
+                yield return new Indexed<T>(i, t);
+            }
+        }
+    }
+
+    public class IndexedIndexedComparer<T> : IEqualityComparer<Indexed<T>>
+    {
+        public static readonly UnindexedIndexedComparer<T> Default = new UnindexedIndexedComparer<T>();
+
+        public IEqualityComparer<T> TComparer { get; }
+
+        public IndexedIndexedComparer(IEqualityComparer<T> tComparer = null)
+        {
+            TComparer = tComparer ?? EqualityComparer<T>.Default;
+        }
+
+        public bool Equals(Indexed<T> x, Indexed<T> y)
+        {
+            return x.Index == y.Index && TComparer.Equals(x.Value, y.Value);
+        }
+
+        public int GetHashCode(Indexed<T> indexed)
+        {
+            return indexed.Index ^ TComparer.GetHashCode(indexed.Value);
+        }
+    }
+
+    public class UnindexedIndexedComparer<T> : IEqualityComparer<Indexed<T>>
+    {
+        public static readonly UnindexedIndexedComparer<T> Default = new UnindexedIndexedComparer<T>();
+
+        public IEqualityComparer<T> TComparer { get; }
+
+        public UnindexedIndexedComparer(IEqualityComparer<T> tComparer = null)
+        {
+            TComparer = tComparer ?? EqualityComparer<T>.Default;
+        }
+
+        public bool Equals(Indexed<T> x, Indexed<T> y)
+        {
+            return TComparer.Equals(x.Value, y.Value);
+        }
+        
+        public int GetHashCode(Indexed<T> indexed)
+        {
+            return TComparer.GetHashCode(indexed.Value);
+        }
+    }
+
+    public static partial class Misc
+    {
+        public static readonly Dictionary<char, int> Multipliers = new Dictionary<char, int>()
+        {
+            ['k'] = 3,
+            ['M'] = 6,
+            ['G'] = 9,
+            ['m'] = -3,
+            ['u'] = -6,
+            ['n'] = -9,
+        };
+
+        private static int Pow10(int power)
+        {
+            return (int)Math.Pow(10, power);
+        }
+
+        public static int ParseInt(string str)
+        {
+            return (int)ParseLong(str);
+        }
+
+        public static long ParseLong(string str)
+        {
+            long i;
+            if (long.TryParse(str, out i))
+                return i;
+
+            var idx = str.IndexOf('e');
+            if (idx == -1)
+                idx = str.IndexOf('E');
+
+            if (idx > 0)
+            {
+                int didx = str.IndexOf('.');
+                int sub = didx < 0 ? 0 : idx - didx - 1; // the number of digits after the decimal point but before the e/E
+                var front = str.Remove(idx);
+                if (didx >= 0)
+                    front = front.Remove(didx, 1);
+                if (long.TryParse(front, out i) && int.TryParse(str.Substring(idx + 1), out int exp))
+                {
+                    int mul = exp - sub;
+                    i *= Pow10(mul);
+                    return i;
+                }
+            }
+
+            foreach (var vk in Multipliers)
+            {
+                idx = str.IndexOf(vk.Key);
+                if (idx == str.Length - 1)
+                {
+                    int didx = str.IndexOf('.');
+
+                    if (didx >= 0)
+                    {
+                        // replace decimal point with multiplier
+                        str = str.Substring(0, didx) + vk.Key + str.Substring(didx + 1, idx - didx - 1);
+                        idx = didx;
+                    }
+                }
+
+                if (idx > 0) // don't allow e.g. k3, require 0k3
+                {
+                    if (long.TryParse(str.Remove(idx, 1), out i))
+                    {
+                        int sub = str.Length - idx - 1; // the number of digits after the multiplier
+                        int mul = vk.Value - sub;
+                        i *= Pow10(mul);
+                    }
+                    return i;
+                }
+            }
+
+            throw new FormatException($"String does not represent an integer: {str}");
+        }
+
+        /// <summary>
+        /// Creates a Matrix where each entry on each column has the same value
+        /// </summary>
+        /// <param name="rowCount">The number of rows to create</param>
+        /// <param name="columns">The value in each column</param>
+        public static Linear.Matrix<T> Columns<T>(int rowCount, IReadOnlyList<T> columns) where T : struct, IEquatable<T>, IFormattable
+        {
+            return Linear.CreateMatrix.Dense<T>(rowCount, columns.Count, (i, j) => columns[j]);
+        }
+
+        /// <summary>
+        /// Creates a Matrix where each entry on each column has the same value
+        /// </summary>
+        /// <param name="rows">The value in each row</param>
+        /// <param name="columnCount">The number of columns to create</param>
+        public static Linear.Matrix<T> Rows<T>(IReadOnlyList<T> rows, int columnCount) where T : struct, IEquatable<T>, IFormattable
+        {
+            return Linear.CreateMatrix.Dense<T>(rows.Count, columnCount, (i, j) => rows[i]);
+        }
+
+        /// <summary>
+        /// Creates a Matrix where each entry on each column has the same value
+        /// </summary>
+        /// <param name="rows">The value in each row</param>
+        /// <param name="columnCount">The number of columns to create</param>
+        public static Linear.Matrix<T> Dense<T>(Modules modules, T intra, T inter) where T : struct, IEquatable<T>, IFormattable
+        {
+            var mat = Linear.CreateMatrix.Dense<T>(modules.ElementCount, modules.ElementCount, inter);
+            var assignments = modules.ModuleAssignments;
+
+            foreach (var module in assignments)
+            {
+                foreach (var r in module)
+                    foreach (var c in module)
+                        mat[r, c] = intra;
+            }
+
+            return mat;
+        }
+
+        /// <summary>
+        /// Converts lists of samples to lists of trajectories
+        /// </summary>
+        public static T[][] SamplesToTrajectories<T>(IEnumerable<IReadOnlyList<T>> samples)
+        {
+            List<T>[] trajectories = null;
+
+            foreach (var sample in samples)
+            {
+                if (trajectories == null)
+                {
+                    trajectories = new List<T>[sample.Count];
+
+                    for (int i = 0; i < trajectories.Length; i++)
+                        trajectories[i] = new List<T>();
+                }
+
+                for (int i = 0; i < sample.Count; i++)
+                    trajectories[i].Add(sample[i]);
+            }
+
+            return trajectories.Select(traj => traj.ToArray()).ToArray();
+        }
+
+        /// <summary>
+        /// Converts lists of trajectories to lists of samples
+        /// </summary>
+        public static IReadOnlyList<T[]> TrajectoriesToSamples<T>(T[][] trajectories)
+        {
+            T[][] samples = new T[trajectories[0].Length][];
+
+            for (int t = 0; t < trajectories[0].Length; t++)
+            {
+                T[] sample = new T[trajectories.Length];
+
+                for (int i = 0; i < sample.Length; i++)
+                    sample[i] = trajectories[i][t];
+
+                samples[t] = sample;
+            }
+
+            return samples.ToArray();
+        }
+
+        public static IEnumerable<Indexed<T>> IndexedHystericalDownSample<T>(IEnumerable<T> sequence, int hnum, IEqualityComparer<T> comparer, int startIndex = 0, int step = 1)
+        {
+            return HystericalDownSample(Indexed<T>.IndexSeq(sequence, 0, 1), hnum, new UnindexedIndexedComparer<T>(comparer));
+        }
+
+        public static IEnumerable<T> HystericalDownSample<T>(IEnumerable<T> sequence, int hnum, IEqualityComparer<T> comparer)
+        {
+            bool first = true;
+            T current = default(T);
+
+            int hcount = 0;
+
+            foreach (T t in sequence)
+            {
+                if (first)
+                {
+                    current = t;
+                    hcount = 0;
+                    continue;
+                }
+                else if (comparer.Equals(current, t))
+                {
+                    hcount++;
+
+                    if (hcount == hnum)
+                    {
+                        yield return current;
+                    }
+                }
+                else
+                {
+                    current = t;
+                    hcount = 0;
+                }
+            }
+        }
+
+        public static double[][] Threshold(IReadOnlyList<IReadOnlyList<double>> trajectories, double threshold, double min, double max)
+        {
+            return trajectories.Select(t => t == null ? null : t.Select(s => s >= threshold ? max : min).ToArray()).ToArray();
+        }
+
+        public static double[][] MovingAverages(IReadOnlyList<IReadOnlyList<double>> trajectories, int windowRadius)
+        {
+            return trajectories.Select(t => t == null ? null : MovingAverage(t, windowRadius)).ToArray();
+        }
+
+        public static double[] MovingAverage(IReadOnlyList<double> trajectory, int windowRadius)
+        {
+            double[] res = new double[trajectory.Count];
+
+            for (int i = 0; i < res.Length; i++)
+            {
+                double total = 0.0;
+                int c = 0;
+                for (int j = i - windowRadius; j <= i + windowRadius; j++)
+                {
+                    if (j >= 0 && j < trajectory.Count)
+                    {
+                        total += trajectory[j];
+                        c++;
+                    }
+                }
+
+                res[i] = total / c;
+            }
+
+            return res;
+        }
+
+        public static double[][] MeanDownsample(IReadOnlyList<IReadOnlyList<double>> trajectories, int resolution)
+        {
+            return trajectories.Select(t => t == null ? null : MeanDownsample(t, resolution)).ToArray();
+        }
+
+        public static double[] MeanDownsample(IReadOnlyList<double> trajectory, int resolution)
+        {
+            double[] res = new double[trajectory.Count / resolution];
+
+            for (int i = 0; i < res.Length; i++)
+            {
+                double total = 0.0;
+                int c = 0;
+                for (int j = i * resolution; j < i * resolution + resolution; j++)
+                {
+                    if (j >= 0 && j < trajectory.Count)
+                    {
+                        total += trajectory[j];
+                        c++;
+                    }
+                }
+
+                res[i] = total / c;
+            }
+
+            return res;
+        }
+
+        public static double[][] Downsample(IReadOnlyList<IReadOnlyList<double>> trajectories, int resolution)
+        {
+            return trajectories.Select(t => t == null ? null : Downsample(t, resolution)).ToArray();
+        }
+
+        public static double[] Downsample(IReadOnlyList<double> trajectory, int resolution)
+        {
+            double[] res = new double[trajectory.Count / resolution];
+
+            for (int i = 0; i < res.Length; i++)
+            {
+                res[i] = trajectory[i * resolution];
+            }
+
+            return res;
+        }
+
+        public static void MeanCentreInplace(this double[] samples)
+        {
+            double mean = samples.Average();
+            for (int i = 0; i < samples.Length; i++)
+                samples[i] -= mean;
+        }
+
+        /// <summary>
+        /// (L1)
+        /// </summary>
+        public static void NormaliseInplace(this double[] samples)
+        {
+            double l1s = samples.Sum(x => Math.Abs(x));
+            if (l1s > 0)
+            {
+                double il1s = 1.0 / l1s;
+                for (int i = 0; i < samples.Length; i++)
+                    samples[i] *= il1s;
+            }
+        }
+
+        /// <summary>
+        /// Attempts to create a directory of the given name is none already exists
+        /// Throws if anything goes wrong
+        /// </summary>
+        public static void EnsureDirectory(string name)
+        {
+            if (name == "")
+                return;
+
+            if (!System.IO.Directory.Exists(name))
+            {
+                System.IO.Directory.CreateDirectory(name);
+            }
+        }
+
+        /// <summary>
+        /// Approximates the solution to f(x) = target, assuming f is a monotonically non-decreasing function in and on the given bound
+        /// Accurate to the given epsillon
+        /// </summary>
+        /// <param name="f">The function to solve</param>
+        /// <param name="start">The start position in the function</param>
+        /// <param name="target">Target value</param>
+        public static double BinarySearch(Func<double, double> f, double target, Range bound, double eps)
+        {
+            double binSearch(double l, double fl, double r, double fr)
+            {
+                var m = (l + r) * 0.5;
+                double fm = f(m);
+
+                if (Math.Abs(fm - target) < eps || (l == r))
+                    return m;
+                else if (m > target)
+                    return binSearch(l, fl, m, fm);
+                else
+                    return binSearch(m, fm, r, fr);
+            }
+
+            return binSearch(bound.Min, f(bound.Min), bound.Max, f(bound.Max));
+        }
+
+        /// <summary>
+        /// Finds the value x that maximise f(x) in the given bounds, assuming f has a negative second differential everywhere
+        /// Accurate to the given epsillon
+        /// </summary>
+        /// <param name="f">The function to solve</param>
+        /// <param name="bound">The bounds wherein the search</param>
+        /// <param name="eps">The minimum interval to consider</param>
+        public static double BinaryArgMax(Func<double, double> f, Range bound, double eps)
+        {
+            double binSearch(double l, double fl, double r, double fr)
+            {
+                var ml = l * 0.6 + r * 0.4;
+                var mr = l * 0.4 + r * 0.6;
+                double fml = f(ml);
+                double fmr = f(mr);
+
+                if (Math.Abs(l - r) < eps)
+                    return (l + r) * 0.5;
+                else if (fml > fmr)
+                    return binSearch(l, fl, mr, fmr);
+                else
+                    return binSearch(ml, fml, r, fr);
+            }
+
+            return binSearch(bound.Min, f(bound.Min), bound.Max, f(bound.Max));
+        }
+
+        /// <summary>
+        /// Finds the value x that maximise f(x) in the given bounds
+        /// Accurate to the given epsillon
+        /// </summary>
+        /// <param name="f">The function to solve</param>
+        /// <param name="bound">The bounds wherein the search</param>
+        /// <param name="eps">The minimum interval to consider</param>
+        public static double ArgMax(Func<double, double> f, Range bound, double eps)
+        {
+            var bestx = bound.Min;
+            var best = f(bound.Min);
+
+            for (double x = bound.Min; x < bound.Max;)
+            {
+                x = x + eps;
+                if (x > bound.Max)
+                    x = bound.Max;
+
+                var fx = f(x);
+                if (fx > best)
+                {
+                    bestx = x;
+                    best = fx;
+                }
+            }
+
+            return bestx;
+        }
+
+        /// <summary>
+        /// Cuts the sequence into contiguous sub-sequences which all have the same key
+        /// </summary>
+        /// <param name="seq">The sequence to cut</param>
+        /// <param name="key">A function which returns the key</param>
+        /// <param name="reuseBuffer">Whether the method may reuse the same buffer each time (e.g. return the same list, but with a different contence for each sequence); unless you have a very good reason, don't set this to tru.</param>
+        public static IEnumerable<IEnumerable<T>> CutBy<T, K>(this IEnumerable<T> seq, Func<T, K> key, bool reuseBuffer = false) where K : IEquatable<K>
+        {
+            // this doesn't use CutOn in order to avoid calling key may times
+
+            List<T> current = null;
+            K last = default(K);
+
+            foreach (var t in seq)
+            {
+                K next = key(t);
+
+                if (current == null)
+                {
+                    current = new List<T>();
+                }
+                else if (!next.Equals(last))
+                {
+                    yield return current;
+                    current = new List<T>();
+                }
+
+                current.Add(t);
+                last = next; // this means you can use not-strictly equality things, which you definitely should not
+            }
+
+            if (current != null)
+                yield return current;
+        }
+
+        /// <summary>
+        /// Cuts the sequence into contiguous sub-sequences
+        /// </summary>
+        /// <param name="seq">The sequence to cut</param>
+        /// <param name="cutter">A function which return whether to cut between the given elements</param>
+        /// <param name="reuseBuffer">Whether the method may reuse the same buffer each time (e.g. return the same list, but with a different contence for each sequence); unless you have a very good reason, don't set this to tru.</param>
+        public static IEnumerable<IEnumerable<T>> CutOn<T>(this IEnumerable<T> seq, Func<T, T, bool> cutter, bool reuseBuffer = false)
+        {
+            List<T> current = null;
+            T last = default(T);
+
+            foreach (var t in seq)
+            {
+                if (current == null)
+                {
+                    current = new List<T>();
+                }
+                else if (cutter(last, t))
+                {
+                    yield return current;
+                    current = new List<T>();
+                }
+
+                current.Add(t);
+                last = t; // this means you can use not-strictly equality things, which you definitely should not
+            }
+
+            if (current != null)
+                yield return current;
+        }
+
+        /// <summary>
+        /// Finds the index of the element with the smallest value
+        /// Returns the index of the (first) smallest element, throws if the list is empty
+        /// </summary>
+        public static int IndexMin<T, K>(this IReadOnlyList<T> list, Func<T, K> func) where K : IComparable<K>
+        {
+            int bestIndex = -1;
+            K bestK = default(K);
+            bool first = true;
+
+            for (int i = 0; i < list.Count; i++)
+            {
+                K k = func(list[i]);
+
+                if (first || k.CompareTo(bestK) < 0)
+                {
+                    bestIndex = i;
+                    bestK = k;
+
+                    first = false;
+                }
+            }
+
+            if (first)
+            {
+                throw new ArgumentException("Enumeration must not be empty to compute argmin");
+            }
+
+            return bestIndex;
+        }
+
+        /// <summary>
+        /// Finds the index of the first element that matches the predicate
+        /// Returns true if a match was found, otherwise false
+        /// </summary>
+        public static bool FirstIndex<T>(this IEnumerable<T> seq, Predicate<T> predicate, out int index, out T entryAtIndex)
+        {
+            int i = 0;
+            foreach (T t in seq)
+            {
+                if (predicate(t))
+                {
+                    entryAtIndex = t;
+                    index = i;
+                    return true;
+                }
+
+                i++;
+            }
+
+            // nothing
+            entryAtIndex = default(T);
+            index = -1;
+            return false;
+        }
+
+        /// <summary>
+        /// Finds the index of the first element that matches the predicate, which is followed by (hnum - 1) entries which also mathc the predicate
+        /// Returns true if a match was found, otherwise false
+        /// </summary>
+        public static bool HystericalFirstIndex<T>(this IEnumerable<T> seq, Predicate<T> predicate, int hnum, out int index, out T entryAtIndex)
+        {
+            if (hnum < 1)
+                throw new ArgumentException("HNum must be greater than or equal to 1", nameof(hnum));
+
+            int hcount = 0;
+
+            // make flow-analysis happy
+            entryAtIndex = default(T);
+            index = -1;
+
+            int i = 0;
+            foreach (T t in seq)
+            {
+                if (predicate(t))
+                {
+                    if (hcount == 0)
+                    {
+                        entryAtIndex = t;
+                        index = i;
+                        hcount = 1;
+                    }
+                    else
+                    {
+                        hcount++;
+                    }
+
+                    if (hcount == hnum)
+                        return true;
+                }
+                else
+                {
+                    hcount = 0;
+                }
+
+                i++;
+            }
+
+            // nothing
+            entryAtIndex = default(T);
+            index = -1;
+            return false;
+        }
+
+        public static IEnumerable<int> IndexWhere<T>(this IEnumerable<T> seq, Predicate<T> predicate)
+        {
+            int i = 0;
+            foreach (T t in seq)
+            {
+                if (predicate(t))
+                    yield return i;
+
+                i++;
+            }
+        }
+
+        public static int IndexMax<T, K>(this IReadOnlyList<T> list, Func<T, K> func) where K : IComparable<K>
+        {
+            int bestIndex = -1;
+            K bestK = default(K);
+            bool first = true;
+
+            for (int i = 0; i < list.Count; i++)
+            {
+                K k = func(list[i]);
+
+                if (first || k.CompareTo(bestK) > 0)
+                {
+                    bestIndex = i;
+                    bestK = k;
+
+                    first = false;
+                }
+            }
+
+            if (first)
+            {
+                throw new ArgumentException("Enumeration must not be empty to compute argmin");
+            }
+
+            return bestIndex;
+        }
+
+        public static int IndexMin<T, K>(this T[] arr, Func<T, K> func) where K : IComparable<K>
+        {
+            if (arr.Length == 0)
+            {
+                throw new ArgumentException("Array must not be empty to compute indexmin");
+            }
+
+            int best = 0;
+            K bestK = func(arr[0]);
+
+            for (int i = 1; i < arr.Length; i++)
+            {
+                K k = func(arr[i]);
+
+                if (k.CompareTo(bestK) < 0)
+                {
+                    best = i;
+                    bestK = k;
+                }
+            }
+
+            return best;
+        }
+
+        public static int IndexMax<T, K>(this T[] arr, Func<T, K> func) where K : IComparable<K>
+        {
+            if (arr.Length == 0)
+            {
+                throw new ArgumentException("Array must not be empty to compute indexmax");
+            }
+
+            int best = 0;
+            K bestK = func(arr[0]);
+
+            for (int i = 1; i < arr.Length; i++)
+            {
+                K k = func(arr[i]);
+
+                if (k.CompareTo(bestK) > 0)
+                {
+                    best = i;
+                    bestK = k;
+                }
+            }
+
+            return best;
+        }
+
+        public static int IndexMax<T>(this ArraySegment<T> arr) where T : IComparable<T>
+        {
+            if (arr.Count == 0)
+            {
+                throw new ArgumentException("Array must not be empty to compute indexmax");
+            }
+
+            int best = 0;
+            T bestT = arr.Array[arr.Offset];
+
+            for (int i = 1; i < arr.Count; i++)
+            {
+                T t = arr.Array[arr.Offset + i];
+
+                if (t.CompareTo(bestT) > 0)
+                {
+                    best = i;
+                    bestT = t;
+                }
+            }
+
+            return best;
+        }
+
+        public static T ArgMin<T, K>(this IEnumerable<T> enumeration, Func<T, K> func) where K : IComparable<K>
+        {
+            T bestT = default(T);
+            K bestK = default(K);
+            bool first = true;
+
+            foreach (T t in enumeration)
+            {
+                K k = func(t);
+
+                if (first || k.CompareTo(bestK) < 0)
+                {
+                    bestT = t;
+                    bestK = k;
+
+                    first = false;
+                }
+            }
+
+            if (first)
+            {
+                throw new ArgumentException("Enumeration must not be empty to compute argmin");
+            }
+
+            return bestT;
+        }
+
+        public static T ArgMax<T, K>(this IEnumerable<T> enumeration, Func<T, K> func) where K : IComparable<K>
+        {
+            T bestT = default(T);
+            K bestK = default(K);
+            bool first = true;
+
+            foreach (T t in enumeration)
+            {
+                K k = func(t);
+
+                if (first || k.CompareTo(bestK) > 0)
+                {
+                    bestT = t;
+                    bestK = k;
+
+                    first = false;
+                }
+            }
+
+            if (first)
+            {
+                throw new ArgumentException("Enumeration must not be empty to compute argmax");
+            }
+
+            return bestT;
+        }
+
+        public static T ArgMedian<T, K>(this IEnumerable<T> enumeration, Func<T, K> func) where K : IComparable<K>
+        {
+            var arr = enumeration.OrderBy(func).ToArray();
+
+            if (arr.Length == 0)
+                throw new ArgumentException("Enumeration must not be empty to compute argmax");
+
+            return arr[arr.Length / 2];
+        }
+
+        public static double Entropy(IEnumerable<double> frequencies, double bitBase = 2)
+        {
+            // not apparent which base this is in...
+            //return MathNet.Numerics.Statistics.StreamingStatistics.Entropy(frequencies) * Math.Log(2) / Math.Log(bitBase);
+
+            double acc = 0;
+
+            foreach (var f in frequencies)
+            {
+                double logTerm = Math.Log(f, bitBase);
+
+                if (double.IsNaN(logTerm) || double.IsNegativeInfinity(logTerm))
+                    continue; // implies f very small
+
+                acc -= f * logTerm;
+            }
+
+            return acc;
+        }
+
+        /// <summary>
+        /// Computes the Χ² error between observed and expected frequencies
+        /// </summary>
+        /// <param name="observedFrequencies">The frequencies observed</param>
+        /// <param name="expectedFrequencies">The expected frequencies</param>
+        /// <returns>The Χ² computed as the sum of  (o - e)^2 / e  terms</returns>
+        public static double ChiSquaredError<T>(IReadOnlyDictionary<T, double> observedFrequencies, IReadOnlyDictionary<T, double> expectedFrequencies)
+        {
+            List<double> observed = new List<double>();
+            List<double> expected = new List<double>();
+
+            foreach (T t in observedFrequencies.Keys)
+            {
+                observed.Add(observedFrequencies[t]);
+                expected.Add(expectedFrequencies[t]);
+            }
+
+            return ChiSquaredError(observed, expected);
+        }
+
+        /// <summary>
+        /// Computes the Χ² error between observed and expected frequencies
+        /// </summary>
+        /// <param name="observedFrequencies">The frequencies observed</param>
+        /// <param name="expectedFrequencies">The expected frequencies</param>
+        /// <returns>The Χ² computed as the sum of  (o - e)^2 / e  terms</returns>
+        public static double ChiSquaredError(IReadOnlyList<double> observedFrequencies, IReadOnlyList<double> expectedFrequencies)
+        {
+            System.Diagnostics.Debug.Assert(observedFrequencies.Count == expectedFrequencies.Count, $"Misc/ChiSquaredError.ctor(string): observedFrequencies and expectedFrequencies must have same count");
+
+            double acc = 0.0;
+
+            int len = observedFrequencies.Count;
+
+            for (int i = 0; i < len; i++)
+            {
+                double o = observedFrequencies[i];
+                double e = expectedFrequencies[i];
+
+                double component = (o - e) * (o - e) / e;
+
+                acc += component;
+            }
+
+            return acc;
+        }
+
+        public static T[] Create<T>(int length, T v)
+        {
+            T[] arr = new T[length];
+
+            for (int i = 0; i < length; i++)
+            {
+                arr[i] = v;
+            }
+
+            return arr;
+        }
+
+        public static T[] Create<T>(int length, Func<int, T> func)
+        {
+            T[] arr = new T[length];
+
+            for (int i = 0; i < length; i++)
+            {
+                arr[i] = func(i);
+            }
+
+            return arr;
+        }
+
+        public static T[][] CreateEmpty<T>(int length1, int length2)
+        {
+            T[][] arr = new T[length1][];
+
+            for (int i = 0; i < length1; i++)
+            {
+                arr[i] = new T[length2];
+            }
+
+            return arr;
+        }
+
+        public static Linear.Vector<double> Pointwise(Linear.Vector<double> vector, Func<double, double> func)
+        {
+            return PointwiseInplace(vector.Clone(), func);
+        }
+
+        public static Linear.Vector<double> PointwiseInplace(Linear.Vector<double> vector, Func<double, double> func)
+        {
+            for (int i = 0; i < vector.Count; i++)
+            {
+                vector[i] = func(vector[i]);
+            }
+            return vector;
+        }
+
+        /// <summary>
+        /// An immutable range of doubles, defined by inclusive upper (Max) and lower (Min) bounds
+        /// </summary>
+        [StateClass]
+        public class Range
+        {
+            [Obsolete]
+            private Range()
+            { }
+
+            public Range(double min, double max)
+            {
+                if (max < min)
+                    throw new ArgumentException("max must not be less than min");
+
+                Min = min;
+                Max = max;
+            }
+
+            /// <summary>
+            /// The minimum (inclusive) value in the Range
+            /// </summary>
+            [SimpleStateProperty("Min")]
+            public double Min { get; private set; }
+
+            /// <summary>
+            /// The maximum (inclusive) value in the Range
+            /// </summary>
+            [SimpleStateProperty("Max")]
+            public double Max { get; private set; }
+
+            /// <summary>
+            /// Clamps a value to the range
+            /// </summary>
+            /// <param name="v">The value to clamp</param>
+            /// <returns>Min if v is less than Min, Max if v is greater than Max, else v</returns>
+            public double Clamp(double v)
+            {
+                if (v < Min)
+                    return Min;
+                else if (v > Max)
+                    return Max;
+                else
+                    return v;
+            }
+
+            /// <summary>
+            /// Determines whether the given value is within the range (inclusived of Min and Max)
+            /// </summary>
+            /// <param name="v">The query value</param>
+            /// <returns>True if v gte Min and v lte Max, else False</returns>
+            public bool Within(double v)
+            {
+                return v >= Min && v <= Max;
+            }
+
+            public override string ToString()
+            {
+                return $"({Min}, {Max})";
+            }
+
+            public double Sample(Random rnd)
+            {
+                return rnd.NextDouble() * (Max - Min) + Min;
+            }
+        }
+
+        public static double Saturate(double v, double min, double threshold, double max)
+        {
+            if (v < threshold)
+                return min;
+            else
+                return max;
+        }
+
+        public static Linear.Vector<double> Saturate(this Linear.Vector<double> vector, double min, double threshold, double max)
+        {
+            return Linear.CreateVector.Dense<double>(vector.Count, i => Misc.Saturate(vector[i], min, threshold, max));
+        }
+
+        public static int Clamp(int v, int min, int max)
+        {
+            if (v < min)
+                return min;
+            else if (v > max)
+                return max;
+            else
+                return v;
+        }
+
+        public static double Clamp(double v, double min, double max)
+        {
+            if (v < min)
+                return min;
+            else if (v > max)
+                return max;
+            else
+                return v;
+        }
+
+        public static T[][] Unroll<T>(T[] arr, int foldCount)
+        {
+            T[][] narr = new T[arr.Length / foldCount][];
+
+            int o = 0;
+            for (int i = 0; i < narr.Length; i++)
+            {
+                narr[i] = new T[foldCount];
+
+                for (int j = 0; j < foldCount; j++)
+                {
+                    narr[i][j] = arr[o];
+                    o++;
+                }
+            }
+
+            return narr;
+        }
+
+        public static T[] Roll<T>(T[][] arr)
+        {
+            T[] narr = new T[arr.Sum(a => a.Length)];
+
+            int o = 0;
+            for (int i = 0; i < arr.Length; i++)
+            {
+                for (int j = 0; j < arr[i].Length; j++)
+                {
+                    narr[o] = arr[i][j];
+                    o++;
+                }
+            }
+
+            return narr;
+        }
+
+        public static void ShuffleInplace<T>(MathNet.Numerics.Random.RandomSource rand, T[] arr)
+        {
+            for (int i = 0; i < arr.Length; i++)
+            {
+                int j = rand.Next(i, arr.Length);
+
+                if (i != j)
+                {
+                    T t = arr[i];
+                    arr[i] = arr[j];
+                    arr[j] = t;
+                }
+            }
+        }
+
+        public static T[,] Unjagged<T>(T[][] jagged)
+        {
+            T[,] res = new T[jagged.Length, jagged[0].Length];
+
+            for (int i = 0; i < jagged.Length; i++)
+            {
+                for (int j = 0; j < jagged[0].Length; j++)
+                {
+                    res[i, j] = jagged[i][j];
+                }
+            }
+
+            return res;
+        }
+
+        public static T[][] Jagged<T>(T[,] unjagged)
+        {
+            T[][] res = new T[unjagged.GetLength(0)][];
+
+            for (int i = 0; i < res.Length; i++)
+            {
+                res[i] = new T[unjagged.GetLength(1)];
+
+                for (int j = 0; j < res[i].Length; j++)
+                {
+                    res[i][j] = unjagged[i, j];
+                }
+            }
+
+            return res;
+        }
+
+        public static T[,] Rotate<T>(T[,] arr)
+        {
+            T[,] res = new T[arr.GetLength(1), arr.GetLength(0)];
+
+            for (int i = 0; i < arr.GetLength(1); i++)
+            {
+                for (int j = 0; j < arr.GetLength(0); j++)
+                {
+                    res[i, j] = arr[j, arr.GetLength(1) - i - 1];
+                }
+            }
+
+            return res;
+        }
+
+        public static IEnumerable<double> DRange(double s, double e, double d)
+        {
+            double x = s;
+            for (; x < e; x += d)
+                yield return x;
+            if (x < e + d * 0.9)
+                yield return e;
+        }
+
+        public static double NextSignedUniform(this Random rand, double maxAbs)
+        {
+            return (rand.NextDouble() * 2.0 - 1.0) * maxAbs;
+        }
+
+        public static double NextNoise(this Random rand, double magnitude, NoiseType noiseType)
+        {
+            switch (noiseType)
+            {
+                case NoiseType.Uniform:
+                    return rand.NextSignedUniform(magnitude);
+                case NoiseType.Gaussian:
+                    return MathNet.Numerics.Distributions.Normal.Sample(rand, 0.0, magnitude);
+                case NoiseType.Binary:
+                    return rand.NextDouble() < 0.5 ? magnitude : -magnitude;
+                default:
+                    throw new Exception("Unrecognised NoiseType: " + noiseType);
+            }
+        }
+
+        public static string NowTime => DateTime.Now.ToString("yyyyMMdd'T'HHmmss");
+
+        // these three are not efficient
+        public static IEnumerable<MatrixEntryAddress> EntriesWhere<T>(this Linear.Matrix<T> mat, Predicate<T> predicate) where T : struct, IEquatable<T>, IFormattable
+        {
+            return mat.EnumerateIndexed(Linear.Zeros.Include).Where(p => predicate(p.Item3)).Select(p => new MatrixEntryAddress(p.Item1, p.Item2));
+        }
+
+        public static MatrixEntryAddress EntryMax<T, K>(this Linear.Matrix<T> mat, Func<T, K> func) where T : struct, IEquatable<T>, IFormattable where K : IComparable<K>
+        {
+            var argMax = mat.EnumerateIndexed(Linear.Zeros.Include).ArgMax(p => func(p.Item3));
+            return new MatrixEntryAddress(argMax.Item1, argMax.Item2);
+        }
+
+        public static MatrixEntryAddress EntryMin<T, K>(this Linear.Matrix<T> mat, Func<T, K> func) where T : struct, IEquatable<T>, IFormattable where K : IComparable<K>
+        {
+            var argMin = mat.EnumerateIndexed(Linear.Zeros.Include).ArgMin(p => func(p.Item3));
+            return new MatrixEntryAddress(argMin.Item1, argMin.Item2);
+        }
+
+        /// <summary>
+        /// Returns a copy of the given matrix, where each element is replaced by zero with probability pruneProbability.
+        /// </summary>
+        public static Linear.Matrix<double> Prune(RandomSource rand, Linear.Matrix<double> matrix, double pruneProbability)
+        {
+            var res = matrix.Clone();
+
+            for (int r = 0; r < matrix.RowCount; r++)
+            {
+                for (int c = 0; c < matrix.ColumnCount; c++)
+                {
+                    bool prune = rand.NextDouble() < pruneProbability;
+                    res[r, c] = prune ? 0.0 : matrix[r, c];
+                }
+            }
+
+            return res;
+        }
+
+        /// <summary>
+        /// 3*ab -> ababab
+        /// Returns null if invalid
+        /// </summary>
+        public static string ExpandStar(string maybeStar)
+        {
+            if (maybeStar.Contains("*"))
+            {
+                string[] data = maybeStar.Split('*');
+
+                if (data.Length != 2)
+                    return null;
+
+                if (!int.TryParse(data[0], out int l))
+                    return null;
+                string r = data[1];
+
+                return string.Join("", Enumerable.Repeat(r, l));
+            }
+            else
+            {
+                return maybeStar;
+            }
+        }
+
+        public static Linear.Vector<double> ParseExtremesVector(string str, double min, double max, double zero)
+        {
+            if (str.Contains(";"))
+            {
+                string[] data = str.Split(';');
+                return Linear.CreateVector.DenseOfEnumerable(data.Select(double.Parse));
+            }
+            else
+            {
+                str = Misc.ExpandStar(str);
+
+                double extremeTranslator(char c)
+                {
+                    if (c == '-')
+                        return min;
+                    if (c == '+')
+                        return max;
+                    if (c == '0')
+                        return zero;
+                    throw new Exception($"Unrecognised extreme char: {c}; should be one of -, +, or 0");
+                }
+
+                return Linear.CreateVector.DenseOfEnumerable(str.Select(extremeTranslator));
+            }
+        }
+
+        public static Linear.Matrix<double> Correlate(this Linear.Vector<double> vec)
+        {
+            return Linear.CreateMatrix.Dense<double>(vec.Count, vec.Count, (r, c) => vec[r] * vec[c]);
+        }
+
+        public static double[] ParseDoubleList(string str)
+        {
+            return str.Split(';').Select(double.Parse).ToArray();
+        }
+
+        public static int[] ParseIntList(string str)
+        {
+            return str.Split(';').Select(ParseInt).ToArray();
+        }
+
+        public static Linear.Matrix<double> IterativeNormalise(Linear.Matrix<double> mat, Linear.Vector<double> normalisationRowSums, Linear.Vector<double> normalisationColumnSums, double cutoff = 10E-5)
+        {
+            var mat2 = mat;
+
+            double l2diff;
+            do
+            {
+                mat = mat2.Clone();
+
+                // row norm
+
+                for (int r = 0; r < mat.RowCount; r++)
+                {
+                    double rowSum = 0.0;
+
+                    for (int c = 0; c < mat.ColumnCount; c++)
+                        rowSum += c != r ? mat[r, c] : 0.0;
+
+                    var irs = normalisationRowSums[r] / rowSum;
+
+                    for (int c = 0; c < mat.ColumnCount; c++)
+                        mat2[r, c] *= c != r ? irs : 1.0;
+                }
+
+                // col norm
+
+                for (int c = 0; c < mat.ColumnCount; c++)
+                {
+                    double colSum = 0.0;
+
+                    for (int r = 0; r < mat.RowCount; r++)
+                        colSum += c != r ? mat[r, c] : 0.0;
+
+                    var ics = normalisationColumnSums[c] / colSum;
+
+                    for (int r = 0; r < mat.RowCount; r++)
+                        mat2[r, c] *= c != r ? ics : 1.0;
+                }
+
+                l2diff = (mat - mat2).Enumerate().Sum(x => x * x);
+            }
+            while (l2diff >= cutoff);
+
+            return mat2;
+        }
+
+        public static void FillRandom(Linear.Vector<double> v, Random rand, double magnitude, NoiseType noiseType)
+        {
+            for (int i = 0; i < v.Count; i++)
+                v[i] = Misc.NextNoise(rand, magnitude, noiseType);
+        }
+    }
+
+    // State Provider registered in GraphSerialisation.cs
+    public enum NoiseType : int
+    {
+        Uniform,
+        Gaussian,
+        Binary,
+    }
+
+    public class Combinatorics
+    {
+        /// <summary>
+        /// Enumerates parameterisations, such that the total of all entryies in any assignment is total
+        /// i.e. enumerates all vectors s of length paramCount such that sum_i(s_i) = total, and all s_i >= 0
+        /// </summary>
+        public static IEnumerable<double[]> EnumerateTotalUnorderedParameterisations(int paramCount, double total, int resolution)
+        {
+            int volume = resolution * paramCount;
+            double fact = total / volume;
+            double[] s = new double[paramCount];
+
+            foreach (var p in Combinatorics.EnumerateOrderedAssignments(volume, paramCount))
+            {
+                // each p is a unique assignment of one unit of weight to a parameter
+                for (int i = 0; i < s.Length; i++)
+                    s[i] = 0;
+                foreach (int a in p)
+                    s[a]++;
+                for (int i = 0; i < s.Length; i++)
+                    s[i] *= fact;
+                yield return s;
+            }
+        }
+
+        /// <summary>
+        /// Enumerates parameterisations, such that the magnitude of each entry in the assignment is no more than the previous, and the total of all entryies in any assignment is total
+        /// i.e. enumerates all vectors s of length paramCount such that sum_i(s_i) = total, and s_i >= s_j for all i,j were i &lt; j, and all s_i >= 0
+        /// </summary>
+        public static IEnumerable<double[]> EnumerateOrderedParameterisations(int paramCount, double total, int resolution)
+        {
+            int volume = resolution * paramCount;
+            double fact = total / volume;
+            int[] s = new int[paramCount];
+            s[0] = volume;
+            return EnumerateOrderedParameterisations(s, 1).Select(ir => ir.Select(i => i * fact).ToArray());
+        }
+
+        private static IEnumerable<int[]> EnumerateOrderedParameterisations(int paramCount, int total)
+        {
+            int[] s = new int[paramCount];
+            s[0] = total;
+            return EnumerateOrderedParameterisations(s, 1);
+        }
+
+        private static IEnumerable<int[]> EnumerateOrderedParameterisations(int[] current, int p)
+        {
+            if (p >= current.Length)
+            {
+                yield return current;
+                yield break;
+            }
+
+            int[] c = current.ToArray(); // so we don't have to 'undo' anything
+            do
+            {
+                foreach (var s in EnumerateOrderedParameterisations(c, p + 1))
+                    yield return s;
+
+                c[p]++;
+                c[0]--;
+            }
+            while (c[p] <= c[p - 1] && c[1] <= c[0]);
+        }
+
+        // NOTE: this method name is terrible; provides the same functionality as EnumerateAssignments
+        /// <summary>
+        /// Enumerates an array of all posible values as determined by maxes
+        /// i.e. enumerates all vectors s, where s_i in [0, maxes_i - 1]
+        /// Yields the same (modified) array every iteration; does not modify maxes
+        /// </summary>
+        /// <param name="maxes">Upbounds of each array element</param>
+        public static IEnumerable<int[]> EnumerateCombinations(int[] maxes)
+        {
+            int[] s = new int[maxes.Length];
+
+            yield return s;
+
+            while (true)
+            {
+                int p = 0;
+                while (p < s.Length && ++s[p] == maxes[p])
+                    s[p++] = 0;
+
+                if (p == s.Length)
+                    yield break;
+
+                yield return s;
+            }
+        }
+        
+        /// <summary>
+        /// Enumerates all vectors s where s_i in [0, maxes_i - 1]
+        /// Yields the same array every time, and requires that it not be modified by the consumer
+        /// Does not modify maximums
+        /// </summary>
+        /// <param name="maximums">The maximum values for each entry of the assignment</param>
+        public static IEnumerable<int[]> EnumerateAssignments(int[] maximums)
+        {
+            int[] assignment = new int[maximums.Length];
+            yield return assignment;
+
+            while (NextAssignment(assignment, maximums))
+                yield return assignment;
+        }
+        
+        /// <summary>
+        /// Produces the next element in an arbitrarily ordered sequence of all vectors s where s_i in [0, maxes_i - 1]
+        /// Returns true if another element can be provided, in which case assignment is updated to contain the next assignemnt
+        /// Returns false if the end of the sequence has been reached, in which case the assignment contains only zero values
+        /// The initial assignment must be an array of all zeros
+        /// No check is performed to verify that assignment and maximums are of the same length, or that the current assignent is valid
+        /// </summary>
+        /// <param name="assignment">The current assignment</param>
+        /// <param name="maximums">The maximum values for each entry of the assignment</param>
+        public static bool NextAssignment(int[] assignment, int[] maximums)
+        {
+            int p = 0;
+            while (p < assignment.Length && ++assignment[p] == maximums[p])
+                assignment[p++] = 0;
+
+            return p != assignment.Length;
+        }
+        
+        /// <summary>
+        /// Enumerates all vectors s of length count where s_i in [0, maxes_i - 1] and s_i &le;= s_j for all i,j where i &lt; j
+        /// Yields the same array every time, and requires that it not be modified by the consumer
+        /// </summary>
+        /// <param name="count">The number of entries per assignment (must be positive)</param>
+        /// <param name="maximums">The maximum values for each entry of the assignment (must be non-negative)</param>
+        public static IEnumerable<int[]> EnumerateOrderedAssignments(int count, int max)
+        {
+            if (count <= 0)
+                throw new ArgumentException("Count must be positive", nameof(count));
+            if (max < 0)
+                throw new ArgumentException("Maximum must be non-negative", nameof(max));
+
+            int[] assignment = new int[count];
+            yield return assignment;
+
+            while (NextOrderedAssignment(assignment, max))
+                yield return assignment;
+        }
+        
+        /// <summary>
+        /// Produces the next element in an arbitrarily ordered sequence of all vectors s where s_i in [0, max], and s_i &lt;= s_j for all i,j where i &lt; j
+        /// Returns true if another element can be provided, in which case assignment is updated to contain the next assignemnt
+        /// Returns false if the end of the sequence has been reached, in which case the assignment contains only zero values
+        /// The initial assignment must be an array of all zeros
+        /// No check is performed to verify that the current assignent is valid
+        /// </summary>
+        /// <param name="assignment">The current assignment</param>
+        /// <param name="maximum">The maximum value for each entry of the assignment</param>
+        public static bool NextOrderedAssignment(int[] assignment, int maximum)
+        {
+            int p = 0;
+            while (p < assignment.Length - 1 && assignment[p] == assignment[p+1])
+                assignment[p++] = 0;
+
+            assignment[p]++;
+
+            // handle last element specially
+            if (p == assignment.Length - 1)
+            {
+                if (assignment[p] >= maximum) // this sneakily covers the maximum = 0 case
+                {
+                    assignment[p] = 0;
+                    return false;
+                }
+            }
+
+            return true;
+        }
+        
+        /// <summary>
+        /// Enumerates all vectors s of length count such that sum_i(s_i) = total, and all s_i >= 0
+        /// Yields the same array every time, and requires that it not be modified by the consumer
+        /// </summary>
+        public static IEnumerable<int[]> EnumerateTotalUnorderedAssignment(int count, int total)
+        {
+            if (count <= 0)
+                throw new ArgumentException("Count must be positive", nameof(count));
+            if (total < 0)
+                throw new ArgumentException("Total must be non-negative", nameof(total));
+            
+            int[] s = new int[count];
+
+            foreach (var p in Combinatorics.EnumerateOrderedAssignments(total, count))
+            {
+                // each p is a unique assignment of one unit of weight to a parameter
+                for (int i = 0; i < s.Length; i++)
+                    s[i] = 0;
+                foreach (int a in p)
+                    s[a]++;
+                yield return s;
+            }
+
+            // TODO: implement this without indirecting through EnumerateOrderedAssignments
+            //int[] assignment = new int[count];
+            //assignment[0] = total;
+            //yield return assignment;
+
+            //while (NextTotalUnorderedAssignment(assignment))
+            //    yield return assignment;
+        }
+        
+        /// <summary>
+        /// Produces the next element in an arbitrarily ordered sequence of all vectors s such that sum_i(s_i) = total, and all s_i >= 0
+        /// Returns true if another element can be provided, in which case assignment is updated to contain the next assignemnt
+        /// Returns false if the end of the sequence has been reached, in which case the assignment contains only zero values
+        /// The initial assignment must be an array with the total in the zeroth position
+        /// No check is performed to verify that the current assignent is valid
+        /// </summary>
+        public static bool NextTotalUnorderedAssignment(int[] assignment)
+        {
+            throw new NotImplementedException("Not yet implemeneted; use EnumerateTotalUnorderedAssignment instead");
+        }
+
+        /// <summary>
+        /// Enumerates all boolean assignments of a given length.
+        /// Returns the same array every iteration, so clone it if you need to hold onto it.
+        /// Returns a single empty array if the length is zero.
+        /// </summary>
+        /// <param name="length">The number of booleans.</param>
+        public static IEnumerable<bool[]> EnumerateBooleanAssignments(int length)
+        {
+            bool[] assignment = new bool[length];
+            yield return assignment;
+
+            int p = 0;
+            while (p < length)
+            {
+                if (assignment[p])
+                {
+                    assignment[p] = false;
+                    p++;
+                }
+                else
+                {
+                    assignment[p] = true;
+                    yield return assignment;
+                    p = 0;
+                }
+            }
+        }
+    }
+
+    [StateClass]
+    public class FileStuff
+    {
+        [Obsolete]
+        protected FileStuff()
+        { }
+
+        public static FileStuff CreateNow(string outDir, string filePrefix, string titlePrefix, bool createDir = true, bool appendTimestamp = true)
+        {
+            if (appendTimestamp)
+                outDir += "_" + Misc.NowTime;
+
+            var stuff = new FileStuff(outDir, filePrefix, titlePrefix);
+
+            if (createDir)
+                stuff.CreateDir();
+
+            return stuff;
+        }
+
+        public FileStuff(string outDir, string filePrefix, string titlePrefix)
+        {
+            OutDir = outDir;
+            FilePrefix = filePrefix;
+            TitlePrefix = titlePrefix;
+        }
+        
+        public void CreateDir()
+        {
+            if (!System.IO.Directory.Exists(OutDir))
+                System.IO.Directory.CreateDirectory(OutDir);
+        }
+
+        [SimpleStateProperty("OutDir")]
+        public string OutDir { get; private set; }
+        
+        [SimpleStateProperty("FilePrefix")]
+        public string FilePrefix { get; private set; }
+        
+        [SimpleStateProperty("TitlePrefix")]
+        public string TitlePrefix { get; private set; }
+
+        public string Title(string name)
+        {
+            return TitlePrefix + name;
+        }
+
+        public string File(string name)
+        {
+            return System.IO.Path.Combine(OutDir, FilePrefix + name);
+        }
+
+        public System.IO.StreamWriter Open(string name)
+        {
+            return new System.IO.StreamWriter(File(name));
+        }
+
+        public System.IO.FileStream CreateBin(string name)
+        {
+            return System.IO.File.Open(File(name), System.IO.FileMode.Create);
+        }
+
+        public FileStuff CreateSubStuff(string subDir)
+        {
+            return CreateSubStuff(subDir, FilePrefix, TitlePrefix);
+        }
+
+        public FileStuff CreateSubStuff(string subDir, string filePrefix, string titlePrefix)
+        {
+            string sdir = System.IO.Path.Combine(OutDir, subDir);
+            System.IO.Directory.CreateDirectory(sdir);
+            return new FileStuff(sdir, filePrefix, titlePrefix);
+        }
+    }
+
+    /// <summary>
+    /// Provides the same observable behaviour and interface (excepting some constructors) as a MathNet.Numerics.Random.MersenneTwister,
+    /// but has some overrides to improve memory characteristics
+    /// </summary>
+    public class CustomMersenneTwister : MathNet.Numerics.Random.MersenneTwister
+    {
+        public CustomMersenneTwister(int seed) : base(seed, false)
+        {
+        }
+
+        private readonly byte[] bytes4 = new byte[4];
+        private readonly byte[] bytes8 = new byte[8];
+
+        // implementation taken from https://raw.githubusercontent.com/mathnet/mathnet-numerics/master/src/Numerics/Random/RandomSource.cs on 2019-03-10
+        // uses a shared byte buffer isntead of creating a new one upon each call
+        protected override int DoSampleInt32WithNBits(int bitCount)
+        {
+            if (bitCount == 0)
+            {
+                return 0;
+            }
+            
+            DoSampleBytes(bytes4);
+
+            // every bit with independent uniform distribution
+            uint uint32 = BitConverter.ToUInt32(bytes4, 0);
+
+            // the least significant N bits with independent uniform distribution and the remaining bits zero
+            uint uintN = uint32 >> (32 - bitCount);
+            return (int)uintN;
+        }
+        
+        // implementation taken from https://raw.githubusercontent.com/mathnet/mathnet-numerics/master/src/Numerics/Random/RandomSource.cs on 2019-03-10
+        // uses a shared byte buffer isntead of creating a new one upon each call
+        protected override long DoSampleInt64WithNBits(int bitCount)
+        {
+			// Fast case: Only 0 is allowed to be returned
+            // No random call is needed
+            if (bitCount == 0)
+            {
+                return 0;
+            }
+            
+            DoSampleBytes(bytes8);
+
+            // every bit with independent uniform distribution
+            ulong uint64 = BitConverter.ToUInt64(bytes8, 0);
+
+            // the least significant N bits with independent uniform distribution and the remaining bits zero
+            ulong uintN = uint64 >> (64 - bitCount);
+            return (long)uintN;
+        }
+    }
+
+    /// <summary>
+    /// This is a Struct because we want to void allocations in tight loops.
+    /// This is definitely not a good idea, but I did it anyway.
+    /// </summary>
+    public struct RandomBits
+    {
+        public RandomBits(Random rand)
+        {
+            Rand = rand;
+            CurrentBits = 0;
+            CurrentBit = 0;
+        }
+
+        private const int LastBit = 1 << 30;
+        private readonly Random Rand;
+        private int CurrentBits; // non-negative number: only use the first 31 bits
+        private int CurrentBit; // powers of 2, or 0 is we are unseeded
+
+        /// <summary>
+        /// Returns the next random boolean
+        /// </summary>
+        public bool NextBit()
+        {
+            if (CurrentBit <= 0)
+            {
+                CurrentBits = Rand.Next();
+                CurrentBit = 1;
+            }
+
+            bool b = (CurrentBits & CurrentBit) > 0;
+
+            if (CurrentBit < LastBit)
+                CurrentBit <<= 1;
+            else
+                CurrentBit = 0;
+
+            return b;
+        }
+    }
+
+    public struct MatrixIndexHelper
+    {
+        public int Width { get; }
+        public int Height { get; }
+
+        public MatrixIndexHelper(int width, int height)
+        {
+            Width = width;
+            Height = height;
+        }
+
+        public static MatrixIndexHelper FromMatrix<T>(Linear.Matrix<T> mat) where T : struct, IFormattable, IEquatable<T>
+        {
+            return new MatrixIndexHelper(mat.ColumnCount, mat.RowCount);
+        }
+
+        public void ToRowCol(int index, out int row, out int col)
+        {
+            EnsureInBounds(index);
+
+            row = index / Width;
+            col = index % Width;
+        }
+
+        public MatrixEntryAddress ToMatrixEntryAddress(int index)
+        {
+            ToRowCol(index, out int r, out int c);
+            return new MatrixEntryAddress(r, c);
+        }
+
+        public int ToIndex(int row, int col)
+        {
+            EnsureInBounds(row, col);
+
+            return row * Width + col;
+        }
+
+        public int ToIndex(MatrixEntryAddress mae)
+        {
+            return ToIndex(mae.Row, mae.Col);
+        }
+
+        public void EnsureInBounds(int index)
+        {
+            if (index < 0 || index >= Width * Height)
+                throw new ArgumentException(nameof(index), "Index not in bounds");
+        }
+
+        public bool InBounds(int index)
+        {
+            if (index < 0 || index >= Width * Height)
+                return false;
+            return true;
+        }
+
+        public void EnsureInBounds(int row, int col)
+        {
+            if (row < 0 || row >= Height)
+                throw new ArgumentException(nameof(row), "Row not in bounds");
+            if (col < 0 || col >= Width)
+                throw new ArgumentException(nameof(col), "Col not in bounds");
+        }
+
+        public bool InBounds(int row, int col)
+        {
+            if (row < 0 || row >= Height)
+                return false;
+            if (col < 0 || col >= Width)
+                return false;
+            return true;
+        }
+
+        public bool TryOffsetRowCol(int row, int col, int offsetRow, int offsetCol, out int newRow, out int newCol)
+        {
+            row += offsetRow;
+            col += offsetCol;
+
+            if (InBounds(row, col))
+            {
+                newRow = row;
+                newCol = col;
+                return true;
+            }
+            else
+            {
+                newRow = -1;
+                newCol = -1;
+                return false;
+            }
+        }
+
+        public bool TryOffsetIndex(int index, int offsetRow, int offsetCol, out int newIndex)
+        {
+            ToRowCol(index, out int r, out int c);
+            r += offsetRow;
+            c += offsetCol;
+
+            if (InBounds(r, c))
+            {
+                newIndex = ToIndex(r, c);
+                return true;
+            }
+            else
+            {
+                newIndex = -1;
+                return false;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.Model/Model.cs b/M4MCode/M4M_MkI/M4M.Model/Model.cs
new file mode 100644
index 0000000..2c1ef88
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Model.cs
@@ -0,0 +1,3447 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enough)
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using M4M.State;
+
+namespace M4M
+{
+    /// <summary>
+    /// The model execution context, which does little more than provide random numbers really, because nobody wants to use matrix pooling.
+    /// </summary>
+    public class ModelExecutionContext
+    {
+        public ModelExecutionContext(RandomSource rand)
+        {
+            TrueRandom = rand;
+            Rand = rand;
+        }
+
+        /// <summary>
+        /// Attempts to disable the Randomness
+        /// Returns true if it was not already disabled, passing out the Random object (to be passed to EnableRandom to reenable Random)
+        /// </summary>
+        public bool TryDisableRandom(out RandomSource rand)
+        {
+            if (Rand == null)
+            {
+                rand = null;
+                return false;
+            }
+
+            Rand = null;
+            rand = TrueRandom;
+            return true;
+        }
+        
+        /// <summary>
+        /// Attempts to disable the Randomness
+        /// Throws if Randomness is already disabled
+        /// </summary>
+        public RandomSource DisableRandom()
+        {
+            if (Rand == null)
+                throw new Exception("Random already disabled; consider using TryDisableRandom");
+
+            Rand = null;
+            return TrueRandom;
+        }
+        
+        /// <summary>
+        /// Attempts to enable randomness
+        /// Throws if Randomness is already enabled, or an inccorect RandomSource is provided
+        /// </summary>
+        public void EnableRandom(RandomSource rand)
+        {
+            if (Rand != null)
+                throw new Exception("Invalid call to EnableRandom: was not disabled");
+            if (rand != TrueRandom)
+                throw new Exception("Invalid call to EnableRandom: randomsource was different from that supplied by DisableRandom");
+            Rand = TrueRandom;
+        }
+        
+        /// <summary>
+        /// Our random source; constant
+        /// </summary>
+        private RandomSource TrueRandom { get; }
+
+        /// <summary>
+        /// The ModelExecutionContext's random source; may be null if the RandomSource is disabled
+        /// (sorry about the awful API)
+        /// </summary>
+        public RandomSource Rand { get; private set; }
+
+        private Dictionary<MatrixDimensions, MatrixPool> MatrixPools = new Dictionary<MatrixDimensions, MatrixPool>(MatrixDimensionsComparer.Instance);
+        public MatrixPool GetMatrixPool(int rows, int cols)
+        {
+            var dim = new MatrixDimensions(rows, cols);
+
+            if (!MatrixPools.TryGetValue(dim, out var pool))
+            {
+                MatrixPools.Add(dim, pool = new MatrixPool(dim, 100));
+            }
+
+            return pool;
+        }
+
+        public Linear.Matrix<double> Clone(Linear.Matrix<double> original)
+        {
+            if (!MatrixPool.EnableMatrixPools)
+                return original.Clone();
+
+            return GetMatrixPool(original.RowCount, original.ColumnCount).Clone(original);
+        }
+
+        public void Release(Linear.Matrix<double> mat)
+        {
+            if (!MatrixPool.EnableMatrixPools)
+                return;
+
+            GetMatrixPool(mat.RowCount, mat.ColumnCount).Release(mat);
+        }
+    }
+
+    public class RegularisationHelpers
+    {
+        /// <summary>
+        /// Simple row-major L1 Sum.
+        /// </summary>
+        /// <param name="mat"></param>
+        /// <returns></returns>
+        public static double L1Sum(Linear.Matrix<double> mat)
+        {
+            double acc = 0.0;
+
+            for (int i = 0; i < mat.RowCount; i++)
+            {
+                for (int j = 0; j < mat.ColumnCount; j++)
+                {
+                    acc += Math.Abs(mat[i, j]);
+                }
+            }
+
+            return acc;
+        }
+
+        /// <summary>
+        /// Row-major L1 Sum.
+        /// </summary>
+        /// <param name="mat"></param>
+        /// <returns></returns>
+        public static double RowL1Sum(Linear.Matrix<double> mat)
+        {
+            if (mat.Storage is Linear.Storage.DenseColumnMajorMatrixStorage<double> dcmms)
+            {
+                int w = mat.RowCount;
+                int h = mat.RowCount;
+
+                double d = 0.0;
+                var data = dcmms.Data;
+                for (int r = 0; r < w; r++)
+                {
+                    for (int c = 0; c < h; c++)
+                    {
+                        d += Math.Abs(data[c * w + r]);
+                    }
+                }
+                return d;
+            }
+            else if (mat.Storage is Linear.Storage.DiagonalMatrixStorage<double> dms)
+            {
+                double d = 0.0;
+                var data = dms.Data;
+                for (int i = 0; i < data.Length; i++)
+                {
+                    d += Math.Abs(data[i]);
+                }
+                return d;
+            }
+            else
+            {
+                int w = mat.RowCount;
+                int h = mat.RowCount;
+
+                double d = 0.0;
+                for (int i = 0; i < w; i++)
+                {
+                    for (int j = 0; j < h; j++)
+                    {
+                        d += Math.Abs(mat[i, j]);
+                    }
+                }
+                return d;
+            }
+        }
+
+        /// <summary>
+        /// Column-major L1 Sum: faster than <see cref="L1Sum(Linear.Matrix{double})"/>, but not compatible.
+        /// </summary>
+        /// <param name="mat"></param>
+        /// <returns></returns>
+        public static double ColL1Sum(Linear.Matrix<double> mat)
+        {
+            if (mat.Storage is Linear.Storage.DenseColumnMajorMatrixStorage<double> dcmms)
+            {
+                double d = 0.0;
+                var data = dcmms.Data;
+                for (int i = 0; i < data.Length; i++)
+                {
+                    d += Math.Abs(data[i]);
+                }
+                return d;
+            }
+            else if (mat.Storage is Linear.Storage.DiagonalMatrixStorage<double> dms)
+            {
+                double d = 0.0;
+                var data = dms.Data;
+                for (int i = 0; i < data.Length; i++)
+                {
+                    d += Math.Abs(data[i]);
+                }
+                return d;
+            }
+            else
+            {
+                int w = mat.RowCount;
+                int h = mat.RowCount;
+
+                double d = 0.0;
+                for (int j = 0; j < h; j++)
+                {
+                    for (int i = 0; i < w; i++)
+                    {
+                        d += Math.Abs(mat[i, j]);
+                    }
+                }
+                return d;
+            }
+        }
+
+        public static double L2Sum(Linear.Matrix<double> mat)
+        {
+            double acc = 0.0;
+
+            for (int i = 0; i < mat.RowCount; i++)
+            {
+                for (int j = 0; j < mat.ColumnCount; j++)
+                {
+                    double x = mat[i, j];
+                    acc += x * x;
+                }
+            }
+
+            return acc;
+        }
+
+        /// <summary>
+        /// Row-major L2 Sum.
+        /// </summary>
+        /// <param name="mat"></param>
+        /// <returns></returns>
+        public static double RowL2Sum(Linear.Matrix<double> mat)
+        {
+            if (mat.Storage is Linear.Storage.DenseColumnMajorMatrixStorage<double> dcmms)
+            {
+                int w = mat.RowCount;
+                int h = mat.RowCount;
+
+                double d = 0.0;
+                var data = dcmms.Data;
+                for (int r = 0; r < w; r++)
+                {
+                    for (int c = 0; c < h; c++)
+                    {
+                        var x = data[c * w + r];
+                        d += x * x;
+                    }
+                }
+                return d;
+            }
+            else if (mat.Storage is Linear.Storage.DiagonalMatrixStorage<double> dms)
+            {
+                double d = 0.0;
+                var data = dms.Data;
+                for (int i = 0; i < data.Length; i++)
+                {
+                    var x = data[i];
+                    d += x * x;
+                }
+                return d;
+            }
+            else
+            {
+                int w = mat.RowCount;
+                int h = mat.RowCount;
+
+                double d = 0.0;
+                for (int i = 0; i < w; i++)
+                {
+                    for (int j = 0; j < h; j++)
+                    {
+                        var x = mat[i, j];
+                        d += x * x;
+                    }
+                }
+                return d;
+            }
+        }
+    }
+    
+    /// <summary>
+    /// Represents a regularisation function, that can compute the cost component of fitness for a given genome.
+    /// </summary>
+    /// <typeparam name="TG">The type of genome.</typeparam>
+    public interface IRegularisationFunction<in TG> where TG : IGenome
+    {
+        string Name { get; }
+        double ComputeCost(TG genome);
+    }
+
+    [StateClass]
+    public class ConstantEquivalentRegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "ConstEq";
+        public double ComputeCost(IGenome g) => 1.0;
+    }
+
+    [StateClass]
+    public class L1EquivalentRegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "L1Eq";
+        public double ComputeCost(IGenome g) => RegularisationHelpers.L1Sum(g.TransMat) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class RowL1EquivalentRegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "RowL1Eq";
+        public double ComputeCost(IGenome g) => RegularisationHelpers.RowL1Sum(g.TransMat) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class ColL1EquivalentRegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "ColL1Eq";
+        public double ComputeCost(IGenome g) => RegularisationHelpers.ColL1Sum(g.TransMat) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class L2EquivalentRegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "L2Eq";
+        public double ComputeCost(IGenome g) => RegularisationHelpers.L2Sum(g.TransMat) / ((double)g.Size * g.Size);
+    }
+    
+    [StateClass]
+    public class LHalfEquivalentRegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "LHalfEq";
+        public double ComputeCost(IGenome g) => g.TransMat.Enumerate().Sum(d => Math.Sqrt(Math.Abs(d))) / ((double)g.Size * g.Size);
+    }
+    
+    [StateClass]
+    public class MMSO1RegularisationFunction : IRegularisationFunction<IGenome>
+    {
+        public string Name => "MMSO1";
+        public double ComputeCost(IGenome g) => g.TransMat.Enumerate().Sum(d => 1.0 * (d < 0 ? -d / (-d + 1) : d / (d + 1))) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class L1And2 : IRegularisationFunction<IGenome>
+    {
+        [Obsolete]
+        private L1And2()
+        { }
+
+        public L1And2(double a, double b)
+        {
+            this.a = a;
+            this.b = b;
+        }
+
+        [SimpleStateProperty("a")]
+        public double a { get; private set; }
+
+        [SimpleStateProperty("b")]
+        public double b { get; private set; }
+
+        public string Name => $"L1And2a{a}b{b}";
+        public double ComputeCost(IGenome g) => (a * RegularisationHelpers.RowL1Sum(g.TransMat) + b * RegularisationHelpers.RowL2Sum(g.TransMat)) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class MMSO : IRegularisationFunction<IGenome>
+    {
+        [Obsolete]
+        private MMSO()
+        { }
+
+        public MMSO(double a, double b)
+        {
+            this.a = a;
+            this.b = b;
+        }
+
+        [SimpleStateProperty("a")]
+        public double a { get; private set; }
+        
+        [SimpleStateProperty("b")]
+        public double b { get; private set; }
+
+        // Michaelis-Menten Symetric... ????
+        public string Name => $"MMSOa{a}b{b}";
+        public double ComputeCost(IGenome g) => g.TransMat.Enumerate().Sum(d => b * (d < 0 ? -d / (-d + a) : d / (d + a))) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class MMSOLin : IRegularisationFunction<IGenome>
+    {
+        [Obsolete]
+        private MMSOLin()
+        { }
+
+        public MMSOLin(double a, double b, double c)
+        {
+            this.a = a;
+            this.b = b;
+            this.c = c;
+        }
+
+        [SimpleStateProperty("a")]
+        public double a { get; private set; }
+
+        [SimpleStateProperty("b")]
+        public double b { get; private set; }
+
+        [SimpleStateProperty("c")]
+        public double c { get; private set; }
+        
+        public string Name => $"MMSOLin(a{a}b{b}c{c})";
+        public double ComputeCost(IGenome g) => g.TransMat.Enumerate().Sum(d => c * Math.Abs(d) + b * (d < 0 ? -d / (-d + a) : d / (d + a))) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class L1and2 : IRegularisationFunction<IGenome>
+    {
+        [Obsolete]
+        private L1and2()
+        { }
+
+        /// <summary>
+        /// Prepares an L1and2 regularisation function
+        /// </summary>
+        /// <param name="z">The representaton of the L2 component</param>
+        public L1and2(double z)
+        {
+            this.z = z;
+        }
+        
+        /// <summary>
+        /// The representaton of the L2 component
+        /// </summary>
+        [SimpleStateProperty("z")]
+        public double z { get; private set; }
+        
+        public string Name => $"L1and2(z={z})";
+        public double ComputeCost(IGenome g) => ((1-z) * RegularisationHelpers.L1Sum(g.TransMat) + z * RegularisationHelpers.L2Sum(g.TransMat)) / ((double)g.Size * g.Size);
+    }
+
+    [StateClass]
+    public class JudgementRules
+    {
+        [Obsolete]
+        protected JudgementRules()
+        { }
+
+        public static IRegularisationFunction<IGenome> ConstantEquivalent = new ConstantEquivalentRegularisationFunction();
+        public static IRegularisationFunction<IGenome> L1Equivalent = new L1EquivalentRegularisationFunction();
+        public static IRegularisationFunction<IGenome> RowL1Equivalent = new RowL1EquivalentRegularisationFunction();
+        public static IRegularisationFunction<IGenome> ColL1Equivalent = new ColL1EquivalentRegularisationFunction();
+        public static IRegularisationFunction<IGenome> L2Equivalent = new L2EquivalentRegularisationFunction();
+        public static IRegularisationFunction<IGenome> LHalfEquivalent = new LHalfEquivalentRegularisationFunction();
+        public static IRegularisationFunction<IGenome> MMSO1 = new MMSO1RegularisationFunction();
+
+        /// <summary>
+        /// a|x| + bx^2
+        /// Gradient at x=0 is 
+        /// </summary>
+        public static IRegularisationFunction<IGenome> L1And2(double a, double b) => new L1And2(a, b);
+
+        /// <summary>
+        /// bx/(x+a)
+        /// Gradient at x=0 is b/a
+        /// </summary>
+        public static IRegularisationFunction<IGenome> MMSO(double a, double b) => new MMSO(a, b);
+
+        /// <summary>
+        /// bx/(x+a) + cx
+        /// Gradient at x=0 is b/a + c
+        /// </summary>
+        public static IRegularisationFunction<IGenome> MMSOLin(double a, double b, double c) => new MMSOLin(a, b, c);
+
+        /// <summary>
+        /// a=a, b=a*(1-q), c=q
+        /// bx/(x+a) + cx
+        /// Gradient at x=0 is 1
+        /// </summary>
+        public static IRegularisationFunction<IGenome> MMSOLin(double a, double q) => MMSOLin(a, a * (1 - q), q);
+        
+        /// <summary>
+        /// (1-z)*x + z*x*x
+        /// Gradient is (1-z) + z*2*x
+        /// </summary>
+        public static IRegularisationFunction<IGenome> L1and2(double z) => new L1and2(z);
+
+        /// <summary>
+        /// The factor to multiply the regularisation term (c) by when judging a phenotype
+        /// λ in the paper
+        /// </summary>
+        [SimpleStateProperty("RegularisationFactor")]
+        public double RegularisationFactor { get; private set; }
+
+        /// <summary>
+        /// The function for the regulariser
+        /// ϕ
+        /// </summary>
+        [SimpleStateProperty("RegularisationFunction")]
+        public IRegularisationFunction<IGenome> RegularisationFunction { get; private set; }
+        
+        /// <summary>
+        /// The factor to multiply the N(0, 1) term by when adding noise to a target
+        /// κ in the paper
+        /// </summary>
+        [SimpleStateProperty("NoiseFactor")]
+        public double NoiseFactor { get; private set; }
+
+        /// <summary>
+        /// Assembles the JudgementRules
+        /// </summary>
+        /// <param name="regularisationFactor">The factor to multiply the regularisation term by (λ)</param>
+        /// <param name="regularisationFunction">The function for the regulariser (ϕ)</param>
+        /// <param name="noiseFactor">The factor to multiply the N(0, 1) term by when adding noise to a target (κ)</param>
+        public JudgementRules(double regularisationFactor, IRegularisationFunction<IGenome> regularisationFunction, double noiseFactor)
+        {
+            RegularisationFactor = regularisationFactor;
+            RegularisationFunction = regularisationFunction;
+            NoiseFactor = noiseFactor;
+        }
+    }
+
+    public interface ISquash
+    {
+        string Name { get; }
+        double Squash(double expression);
+
+        /// <summary>
+        /// A one-time delegate (you can pass me around, instead of having to manage your own copy)
+        /// </summary>
+        Func<double, double> Delegate { get; }
+    }
+
+    [StateClass]
+    public class NoneSquash : ISquash
+    {
+        public static readonly NoneSquash Instance = new NoneSquash();
+
+        private NoneSquash()
+        {
+            Delegate = Squash;
+        }
+
+        public string Name => "None";
+
+        public Func<double, double> Delegate { get; }
+
+        public double Squash(double x)
+        {
+            return x;
+        }
+    }
+
+    [StateClass]
+    public class TanhSquash : ISquash
+    {
+        private TanhSquash()
+        {
+            Init();
+        }
+
+        public TanhSquash(double innerCoef, string name = null)
+            : this()
+        {
+            InnerCoef = innerCoef;
+            Name = name ?? $"Tanh({InnerCoef}*x)";
+        }
+
+        private void Init()
+        {
+            Delegate = Squash;
+        }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        [SimpleStateProperty("InnerCoef")]
+        public double InnerCoef { get; private set; }
+
+        public Func<double, double> Delegate { get; private set; }
+
+        public double Squash(double x)
+        {
+            return Math.Tanh(x * InnerCoef);
+        }
+    }
+
+    [StateClass]
+    public class PositiveTanhSquash : ISquash
+    {
+        private PositiveTanhSquash()
+        {
+            Init();
+        }
+
+        public PositiveTanhSquash(double innerCoef, string name = null)
+            : this()
+        {
+            InnerCoef = innerCoef;
+            Name = name ?? $"(Tanh({InnerCoef}*x)+1)/2";
+        }
+
+        private void Init()
+        {
+            Delegate = Squash;
+        }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        [SimpleStateProperty("InnerCoef")]
+        public double InnerCoef { get; private set; }
+
+        public Func<double, double> Delegate { get; private set; }
+
+        public double Squash(double x)
+        {
+            return (Math.Tanh(x * InnerCoef) + 1) / 2;
+        }
+    }
+
+    [StateClass]
+    public class MichaelisMenton : ISquash
+    {
+        public MichaelisMenton()
+        {
+            Delegate = Squash;
+        }
+
+        public string Name => "Michaelis-Menten";
+        
+        public Func<double, double> Delegate { get; }
+
+        public double Squash(double x)
+        {
+            return x / (x + 1);
+        }
+    }
+
+    [StateClass]
+    public class MichaelisMentenPaush : ISquash
+    {
+        public MichaelisMentenPaush()
+        {
+            Delegate = Squash;
+        }
+
+        public string Name => "Michaelis-Menten (Paush)";
+        
+        public Func<double, double> Delegate { get; }
+
+        public double Squash(double x)
+        {
+            return x < 0 ? 0 : x/(x+1);
+        }
+    }
+
+    [StateClass]
+    public class MichaelisMentenSymOdd : ISquash
+    {
+        public MichaelisMentenSymOdd()
+        {
+            Delegate = Squash;
+        }
+
+        public string Name => "Michaelis-Menten (SymOdd)";
+        
+        public Func<double, double> Delegate { get; }
+
+        public double Squash(double x)
+        {
+            return x < 0 ? x/(-x+1) : x/(x+1);
+        }
+    }
+
+    [StateClass]
+    public class DevelopmentRules
+    {
+        [Obsolete]
+        private DevelopmentRules()
+        { }
+
+        public static ISquash None = NoneSquash.Instance;
+        public static ISquash Tanh = new TanhSquash(1.0, "Tanh(x)");
+        public static ISquash TanhHalf = new TanhSquash(0.5, "Tanh(x/2)");
+        public static ISquash TanhTen = new TanhSquash(10.0, "Tanh(10*x)");
+
+        public static ISquash PositiveTanhHalf = new PositiveTanhSquash(0.5, "(Tanh(x/2)+1)/2");
+
+        public static ISquash MichaelisMenten = new MichaelisMenton();
+        public static ISquash MichaelisMentenPaush = new MichaelisMentenPaush();
+        public static ISquash MichaelisMentenSymOdd = new MichaelisMentenSymOdd();
+
+        public static ISquash[] CommonSquashes = new ISquash[]
+        {
+            None,
+            Tanh,
+            TanhHalf,
+            TanhTen,
+            PositiveTanhHalf,
+            MichaelisMenten,
+            MichaelisMentenPaush,
+            MichaelisMentenSymOdd,
+        };
+
+        /// <summary>
+        /// Number of developmental time steps
+        /// T in the paper
+        /// </summary>
+        [SimpleStateProperty("TimeSteps")]
+        public int TimeSteps { get; private set; }
+
+        /// <summary>
+        /// Trait update rate
+        /// τ1 in the paper
+        /// </summary>
+        [SimpleStateProperty("UpdateRate")]
+        public double UpdateRate { get; private set; }
+
+        /// <summary>
+        /// Trait decay rate
+        /// τ2 in the paper
+        /// </summary>
+        [SimpleStateProperty("DecayRate")]
+        public double DecayRate { get; private set; }
+
+        /// <summary>
+        /// Train update squash function
+        /// σ in the paper
+        /// </summary>
+        [SimpleStateProperty("Squash")]
+        public ISquash Squash { get; private set; }
+
+        /// <summary>
+        /// The scale by which to scale the developed pehnotype.
+        /// </summary>
+        [SimpleStateProperty("RescaleScale")]
+        public double RescaleScale { get; private set; } = 1.0;
+
+        /// <summary>
+        /// The amount by which to offset the developed phenotype after applying the <see cref="RescaleScale"/>.
+        /// </summary>
+        [SimpleStateProperty("RescaleOffset")]
+        public double RescaleOffset { get; private set; } = 0.0;
+
+        /// <summary>
+        /// Assembles the DevelopmentRules
+        /// </summary>
+        /// <param name="timeSteps">Number of developmental time steps (T)</param>
+        /// <param name="updateRate">Trait update rate (τ1)</param>
+        /// <param name="decayRate">Trait decay rate (τ2)</param>
+        /// <param name="squash">Train update squash function (σ)</param>
+        public DevelopmentRules(int timeSteps, double updateRate, double decayRate, ISquash squash)
+        {
+            TimeSteps = timeSteps;
+            UpdateRate = updateRate;
+            DecayRate = decayRate;
+            Squash = squash;
+        }
+
+        /// <summary>
+        /// Assembles the DevelopmentRules
+        /// </summary>
+        /// <param name="timeSteps">Number of developmental time steps (T)</param>
+        /// <param name="updateRate">Trait update rate (τ1)</param>
+        /// <param name="decayRate">Trait decay rate (τ2)</param>
+        /// <param name="squash">Train update squash function (σ)</param>
+        /// <param name="rescaleScale">Trait rescale scale</param>
+        /// <param name="rescaleOffset">Trait rescale offset</param>
+        public DevelopmentRules(int timeSteps, double updateRate, double decayRate, ISquash squash, double rescaleScale, double rescaleOffset)
+        {
+            TimeSteps = timeSteps;
+            UpdateRate = updateRate;
+            DecayRate = decayRate;
+            Squash = squash;
+            RescaleScale = rescaleScale;
+            RescaleOffset = rescaleOffset;
+        }
+    }
+
+    [StateClass]
+    public class ReproductionRules
+    {
+        [Obsolete]
+        private ReproductionRules()
+        { }
+
+        public static readonly Range ClampAny = new Range(double.NegativeInfinity, double.PositiveInfinity);
+        public static readonly Range ClampAbs1 = new Range(-1, 1);
+        public static readonly Range ClampZeroOne = new Range(0, 1);
+
+        /// <summary>
+        /// The maximum magnitude of an applied mutation to the initial State (G)
+        /// M_G
+        /// </summary>
+        [SimpleStateProperty("InitialStateMutationSize")]
+        public double InitialStateMutationSize { get; private set; }
+
+        /// <summary>
+        /// The type of mutation applied to the initial state
+        /// </summary>
+        [SimpleStateProperty("InitialStateMutationType")]
+        //[NetState.SoftState.SoftPropertyAttribute("InitialStateMutationType", null, typeof(NetState.AutoState.AutoEnumStateProvider<NoiseType, int, NetState.Serialisation.IntStateProvider>))]
+        public NoiseType InitialStateMutationType { get; private set; }
+
+        /// <summary>
+        /// The maximum magnitude of an applied mutation to the developmental transfomration matrix
+        /// M_B
+        /// </summary>
+        [SimpleStateProperty("DevelopmentalTransformationMatrixMutationSize")]
+        public double DevelopmentalTransformationMatrixMutationSize { get; private set; }
+
+        /// <summary>
+        /// The type of mutation applied to the developmental transfomration matrix
+        /// </summary>
+        [SimpleStateProperty("DevelopmentalTransformationMatrixMutationType")]
+        public NoiseType DevelopmentalTransformationMatrixMutationType { get; private set; }
+
+        /// <summary>
+        /// The probability of mutating B every time we mutate consider mutating G
+        /// R_B
+        /// </summary>
+        [SimpleStateProperty("DevelopmentalTransformationMatrixRate")]
+        public double DevelopmentalTransformationMatrixRate { get; private set; }
+        
+        /// <summary>
+        /// Whether to not mutate G when mutating B
+        /// </summary>
+        [SimpleStateProperty("ExclusiveBMutation")]
+        public bool ExclusiveBMutation { get; private set; }
+
+        /// <summary>
+        /// The number of times to update an element of the initial state vector (e.g. will pick one at random and update it more than once (may pick different traits, or the same one, each draw is uniform)
+        /// </summary>
+        [SimpleStateProperty("InitialTraitUpdates")]
+        public int InitialTraitUpdates { get; private set; }
+
+        /// <summary>
+        /// The Range to clamp the initial state elements to
+        /// Default is [-1, 1]
+        /// </summary>
+        [SimpleStateProperty("InitialStateClamping")]
+        public Range InitialStateClamping { get; private set; }
+
+        /// <summary>
+        /// The Range to clamp the initial state elements to
+        /// Default is [NegativeInfinity, PositiveInfinity]
+        /// </summary>
+        [SimpleStateProperty("DevelopmentalTransformationMatrixClamping")]
+        public Range DevelopmentalTransformationMatrixClamping { get; private set; } = ClampAny;
+
+        /// <summary>
+        /// Assembles the ReproductionRules
+        /// </summary>
+        /// <param name="initialStateMutationSize">The maximum magnitude of an applied mutation to the initial State (completely independant of B)</param>
+        /// <param name="developmentalTransformationMatrixMutationSize">The maximum magnitude of an applied mutation to the initial  B (completely independant of G)</param>
+        /// <param name="developmentalTransformationMatrixMutationRate">The probability of mutating B every time we mutate consider mutating G</param>
+        /// <param name="exclusiveBMutation">Whether to not mutate G when mutating B</param>
+        /// <param name="initialTraitUpdates">The number of times to update an element of the initial state vector</param>
+        /// <param name="initialStateClamping">The Range to clamp the initial state elements to</param>
+        public ReproductionRules(double initialStateMutationSize, double developmentalTransformationMatrixMutationSize, double developmentalTransformationMatrixMutationRate, bool exclusiveBMutation = false, int initialTraitUpdates = 1, Range initialStateClamping = null, NoiseType initialStateMutationType = NoiseType.Uniform, NoiseType developmentalTransformationMatrixMutationType = NoiseType.Uniform, Range developmentalTransformationMatrixClamping = null)
+        {
+            InitialStateMutationSize = initialStateMutationSize;
+            InitialStateMutationType = initialStateMutationType;
+            DevelopmentalTransformationMatrixMutationSize = developmentalTransformationMatrixMutationSize;
+            DevelopmentalTransformationMatrixMutationType = developmentalTransformationMatrixMutationType;
+            DevelopmentalTransformationMatrixRate = developmentalTransformationMatrixMutationRate;
+            ExclusiveBMutation = exclusiveBMutation;
+            InitialTraitUpdates = initialTraitUpdates;
+            InitialStateClamping = initialStateClamping ?? ClampAbs1; // use default if null
+            DevelopmentalTransformationMatrixClamping = developmentalTransformationMatrixClamping ?? ClampAny; // use default if null
+        }
+    }
+
+    public interface ITargetPackage
+    {
+        string Name { get; }
+        int TargetSize { get; }
+        IReadOnlyList<ITarget> Targets { get; }
+    }
+
+    public class TargetPackage : ITargetPackage
+    {
+        public TargetPackage(string name, IReadOnlyList<ITarget> targets)
+        {
+            Name = name;
+            Targets = targets;
+        }
+
+        public string Name { get; }
+        public int TargetSize => Targets[0].Size;
+        public IReadOnlyList<ITarget> Targets { get; }
+    }
+
+    public struct ExposureInformation
+    {
+        public ExposureInformation(int exposureDuration)
+        {
+            ExposureDuration = exposureDuration;
+        }
+
+        /// <summary>
+        /// Indicates the duration (in generations) of the next exposure
+        /// </summary>
+        public int ExposureDuration { get; set; }
+    }
+
+    public static class TargetHelpers
+    {
+        public static ITarget Unwrap(this ITarget target)
+        {
+            while (target is ITargetDecorator decorator)
+                target = decorator.Target;
+
+            return target;
+        }
+    }
+
+    public interface ITarget
+    {
+        /// <summary>
+        /// The size of the Target, and so the size of the Phenotypes expected
+        /// </summary>
+        int Size { get; }
+
+        /// <summary>
+        /// A friendly name for the target
+        /// </summary>
+        string FriendlyName { get; }
+
+        /// <summary>
+        /// A full name for the target
+        /// </summary>
+        string FullName { get; }    
+
+        /// <summary>
+        /// A detailed description of the target
+        /// </summary>
+        string Description { get; }
+
+        /// <summary>
+        /// Computes the fitness of the Phenotype given this Target
+        /// </summary>
+        /// <param name="p">The phenotype</param>
+        /// <returns>The fitness of the Phenotype</returns>
+        double Judge(Phenotype p);
+
+        /// <summary>
+        /// Prepares the Target for the next generation (e.g. by adding noise, etc.)
+        /// </summary>
+        /// <param name="rand">Random source for the noise</param>
+        /// <param name="jrules">Judgement Rules</param>
+        /// <returns>True if the objective has changed, thereby invalidating previous evaluations</returns>
+        bool NextGeneration(MathNet.Numerics.Random.RandomSource rand, JudgementRules jrules);
+
+        /// <summary>
+        /// Prepares the target for the next exposure (e.g. by changing something? I don't know)
+        /// </summary>
+        /// <param name="rand">Random source for the noise</param>
+        /// <param name="jrules">Judgement Rules</param>
+        /// <param name="epoch">The current epoch</param>
+        /// <param name="exposureInformation">Information about the next exposure, which can be modified by the target</param>
+        /// <returns>True if the objective has changed, thereby invalidating previous evaluations</returns>
+        bool NextExposure(MathNet.Numerics.Random.RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation);
+    }
+
+    /// <summary>
+    /// A target where we can find a perfect phenotype vector.
+    /// </summary>
+    public interface IPerfectPhenotypeTarget : ITarget
+    {
+        Linear.Vector<double> PreparePerfectP();
+    }
+
+
+    [StateClass]
+    public class VectorTarget : IPerfectPhenotypeTarget
+    {
+        /// <summary>
+        /// The vector we are targetting
+        /// S in the paper
+        /// </summary>
+        [VectorStateProperty("Vector")]
+        public Linear.Vector<double> Vector { get; private set; }
+
+        /// <summary>
+        /// Per-Generation Noisy Vector
+        /// Modify inplace
+        /// </summary>
+        [VectorStateProperty("NoisyVector")]
+        private Linear.Vector<double> NoisyVector { get; set; }
+
+        /// <summary>
+        /// The size of the Target
+        /// </summary>
+        public int Size => Vector.Count;
+        
+        /// <summary>
+        /// A friendly name for the target
+        /// </summary>
+        [SimpleStateProperty("FriendlyName")]
+        public string FriendlyName { get; private set; }
+
+        /// <summary>
+        /// A full name for the target
+        /// </summary>
+        [SimpleStateProperty("FullName")]
+        public string FullName { get; private set; }
+        
+        /// <summary>
+        /// A detailed description of the target
+        /// </summary>
+        public virtual string Description => $"Basic Vector Target (Size={Size}, NormaliseCustomJudger={NormaliseCustomJudger}) (CJ: {CustomJudger?.Description})";
+
+        /// <summary>
+        /// The Custom Judger to use (default of dot product is used if null)
+        /// </summary>
+        [SimpleStateProperty("CustomJudger")]
+        public IVectorTargetJudger CustomJudger { get; private set; } = null;
+
+        /// <summary>
+        /// Whether the output of a CustomJudger should be normalised from [-Size, Size] to [0, 1]
+        /// </summary>
+        [SimpleStateProperty("NormaliseCustomJudger")]
+        public bool NormaliseCustomJudger { get; private set; }
+
+        [Obsolete]
+        protected VectorTarget()
+        { }
+
+        /// <summary>
+        /// Creates a target using the given Vector
+        /// </summary>
+        /// <param name="targetVector">The vector we are targetting (S)</param>
+        /// <param name="friendlyName">A friendly name for the target</param>
+        /// <param name="customJudger">A custom judgement function to use (leave null for default)</param>
+        public VectorTarget(Linear.Vector<double> targetVector, string friendlyName, IVectorTargetJudger customJudger = null, bool normaliseCustomJudger = true)
+        {
+            Vector = targetVector;
+            FriendlyName = friendlyName;
+            FullName = friendlyName + " (CJ: " + (customJudger?.Name ?? "default") +")";
+            CustomJudger = customJudger;
+            NormaliseCustomJudger = normaliseCustomJudger;
+        }
+
+        /// <summary>
+        /// Creates a target using a copy of the given array
+        /// </summary>
+        /// <param name="targetVector">The vector we are targetting (S)</param>
+        /// <param name="friendlyName">A friendly name for the target</param>
+        /// <param name="customJudger">A custom judgement function to use (leave null for default)</param>
+        public VectorTarget(double[] targetVectorArray, string friendlyName, IVectorTargetJudger customJudger = null, bool normaliseCustomJudger = true)
+        {
+            Vector = Linear.CreateVector.DenseOfArray(targetVectorArray);
+            FriendlyName = friendlyName;
+            FullName = friendlyName + " (CJ: " + (customJudger?.Name ?? "default") + ")";
+            CustomJudger = customJudger;
+            NormaliseCustomJudger = normaliseCustomJudger;
+        }
+
+        /// <summary>
+        /// Creates a target using a copy of the given array
+        /// </summary>
+        /// <param name="targetVector">The vector we are targetting (S)</param>
+        /// <param name="friendlyName">A friendly name for the target</param>
+        /// <param name="customJudger">A custom judgement function to use (leave null for default)</param>
+        public VectorTarget(int[] targetVectorArray, string friendlyName, IVectorTargetJudger customJudger = null, bool normaliseCustomJudger = true)
+        {
+            Vector = Linear.CreateVector.DenseOfArray(targetVectorArray.Select(i => (double)i).ToArray());
+            FriendlyName = friendlyName;
+            FullName = friendlyName + " (CJ: " + (customJudger?.Name ?? "default") + ")";
+            CustomJudger = customJudger;
+            NormaliseCustomJudger = normaliseCustomJudger;
+        }
+        
+        /// <summary>
+        /// Creates a target from the chracters in the string (+ => 1, - => -1, space => 0)
+        /// </summary>
+        /// <param name="targetVector">The vector we are targetting (S)</param>
+        /// <param name="friendlyName">A friendly name for the target</param>
+        /// <param name="customJudger">A custom judgement function to use (leave null for default)</param>
+        public VectorTarget(string targetVectorString, string friendlyName, IVectorTargetJudger customJudger = null, bool normaliseCustomJudger = true)
+        {
+            Vector = Misc.ParseExtremesVector(targetVectorString, -1.0, 1.0, 0.0);
+
+            FriendlyName = friendlyName;
+            FullName = friendlyName + " (CJ: " + (customJudger?.Name ?? "default") +")";
+            CustomJudger = customJudger;
+            NormaliseCustomJudger = normaliseCustomJudger;
+        }
+
+        /// <summary>
+        /// Indicates whether the NoisyVector may be different from the (normal) Vector
+        /// (must be true by default, because NoisyVector is serialised)
+        /// </summary>
+        private bool Noisy = true;
+
+        /// <summary>
+        /// Prepares the Target for the next generation by creating new Noise or whatever
+        /// </summary>
+        /// <param name="rand">Random source for the noise</param>
+        /// <param name="jrules">Judgement Rules</param>
+        /// <returns>True if the objective has changed, thereby invalidating previous evaluations</returns>
+        public virtual bool NextGeneration(MathNet.Numerics.Random.RandomSource rand, JudgementRules jrules)
+        {
+            bool invalidated = false;
+
+            // reset NoisyVector if necessary
+            if (NoisyVector == null)
+            {
+                NoisyVector = Vector.Clone();
+                invalidated = true;
+            }
+            else if (Noisy)
+            {
+                Vector.CopyTo(NoisyVector);
+                invalidated = true;
+            }
+            
+            // if κ != 0, then add gaussian noise to NoisyVector
+            if (jrules.NoiseFactor != 0)
+            {
+                // cached to avoid unnecessary allocations
+                var κ = jrules.NoiseFactor;
+                var rnd = rand;
+
+                NoisyVector.MapInplace(d => d + MathNet.Numerics.Distributions.Normal.Sample(rnd, 0, 1) * κ, Linear.Zeros.Include);
+                Noisy = true;
+                invalidated = true;
+            }
+            else
+            {
+                Noisy = false;
+            }
+
+            return invalidated;
+        }
+
+        /// <summary>
+        /// Prepares the target for the next exposure (e.g. by changing something? I don't know)
+        /// </summary>
+        /// <param name="rand">Random source for the noise</param>
+        /// <param name="jrules">Judgement Rules</param>
+        /// <param name="epoch">The current epoch</param>
+        /// <param name="exposureInformation">Information about the next exposure, which the target can modify</param>
+        /// <returns>True if the objective has changed, thereby invalidating previous evaluations</returns>
+        public virtual bool NextExposure(MathNet.Numerics.Random.RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return false;
+        }
+
+        /// <summary>
+        /// Computes the fitness of the Phenotype given this Target
+        /// b in the paper
+        /// </summary>
+        /// <param name="p">The phenotype (P*)</param>
+        /// <returns>The fitness of the Phenotype (b)</returns>
+        public double Judge(Phenotype p)
+        {
+            Debug.Assert(NoisyVector != null, $"Model/VectorTarget.Judge NoisyVector must be set!!! call NextGeneration(rand, jrules) before each generation");
+            Debug.Assert(p.Size == Size, $"Model/VectorTarget.Judge (size = {p.Size}) must be the same size as the Target (size = {Size})");
+
+            if (CustomJudger != null)
+            {
+                if (NormaliseCustomJudger)
+                    return 0.5 * (1.0 + CustomJudger.Judge(NoisyVector, p) / Size);
+                else
+                    return CustomJudger.Judge(NoisyVector, p);
+            }
+            else
+            {
+                return 0.5 * (1.0 + NoisyVector.DotProduct(p.Vector) / Size);
+            }
+        }
+
+        public virtual Linear.Vector<double> PreparePerfectP()
+        {
+            return Vector.Clone();
+        }
+    }
+
+    [StateClass]
+    public class EuclideanTargetJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected EuclideanTargetJudger()
+        {
+        }
+
+        public EuclideanTargetJudger(double factor, double bias)
+        {
+            Factor = factor;
+            Bias = bias;
+        }
+
+        public string Name => "EuclideanJudger";
+
+        public string Description => $"EuclideanJudger (Factor={Factor}, Bias={Bias})";
+
+        [SimpleStateProperty("Factor")]
+        public double Factor { get; private set; }
+
+        [SimpleStateProperty("Bias")]
+        public double Bias { get; private set; }
+
+        public double Judge(Linear.Vector<double> vector, Phenotype phenotype)
+        {
+            return Bias - (vector - phenotype.Vector).L2Norm();
+        }
+    }
+
+    public interface ITargetDecorator
+    {
+        ITarget Target { get; }
+    }
+
+    [StateClass]
+    public class DutyTarget : ITarget, ITargetDecorator
+    {
+        [Obsolete]
+        protected DutyTarget()
+        { }
+
+        public DutyTarget(ITarget target, double start, double end)
+        {
+            if (end < start)
+                throw new ArgumentException("Start must be before the end");
+
+            Target = target ?? throw new ArgumentNullException(nameof(target));
+            Start = start;
+            End = end;
+        }
+
+        public int Size => Target.Size;
+
+        public string FriendlyName => $"Duty({Target.FriendlyName})";
+
+        public string FullName => $"Duty({Target.FullName})";
+
+        public string Description => $"Duty({Target.Description}, {Start}, {End})";
+
+        /// <summary>
+        /// The underlying target wrapped by this DutyTarget
+        /// </summary>
+        [SimpleStateProperty("Target")]
+        public ITarget Target { get; private set; }
+
+        [SimpleStateProperty("Start")]
+        public double Start { get; private set; }
+
+        [SimpleStateProperty("End")]
+        public double End { get; private set; }
+
+        public double Judge(Phenotype p)
+        {
+            return Target.Judge(p);
+        }
+
+        public int DetermineDuration(int totalDuration, double start, double end)
+        {
+            int s = (int)Math.Floor(totalDuration * start);
+            int e = (int)Math.Floor(totalDuration * end);
+            return e - s;
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            exposureInformation.ExposureDuration = DetermineDuration(exposureInformation.ExposureDuration, Start, End);
+            return Target.NextExposure(rand, jrules, epoch, ref exposureInformation);
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            return Target.NextGeneration(rand, jrules);
+        }
+    }
+
+    [StateClass]
+    public class SaturationTarget : ITarget, ITargetDecorator
+    {
+        [Obsolete]
+        protected SaturationTarget()
+        { }
+
+        public SaturationTarget(ITarget target, double min, double threshold, double max)
+        {
+            Target = target ?? throw new ArgumentNullException(nameof(target));
+            Min = min;
+            Threshold = threshold;
+            Max = max;
+        }
+
+        public int Size => Target.Size;
+
+        public string FriendlyName => $"Sat({Target.FriendlyName})";
+
+        public string FullName => $"Sat({Target.FullName})";
+
+        public string Description => $"Sat({Target.Description})";
+
+        /// <summary>
+        /// The underlying target wrapped by this SaturationTarget
+        /// </summary>
+        [SimpleStateProperty("Target")]
+        public ITarget Target { get; private set; }
+
+        [SimpleStateProperty("Min")]
+        public double Min { get; private set; }
+
+        [SimpleStateProperty("Threshold")]
+        public double Threshold { get; private set; }
+
+        [SimpleStateProperty("Max")]
+        public double Max { get; private set; }
+
+        public double Judge(Phenotype p)
+        {
+            return Target.Judge(p.Saturate(Min, Threshold, Max));
+        }
+
+        public bool NextExposure(RandomSource rand, JudgementRules jrules, int epoch, ref ExposureInformation exposureInformation)
+        {
+            return Target.NextExposure(rand, jrules, epoch, ref exposureInformation);
+        }
+
+        public bool NextGeneration(RandomSource rand, JudgementRules jrules)
+        {
+            return Target.NextGeneration(rand, jrules);
+        }
+    }
+
+    public interface IVectorTargetJudger
+    {
+        /// <summary>
+        /// The name of the IVectorTargetJudger
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// A detailed description of the IVectorTargetJudger
+        /// </summary>
+        string Description { get; }
+
+        /// <summary>
+        /// Judges the given phenotype considering the given target vector
+        /// </summary>
+        /// <param name="vector">The current target vector</param>
+        /// <param name="phenotype">The phenotype to judge</param>
+        double Judge(Linear.Vector<double> vector, Phenotype phenotype);
+    }
+
+    // NOTE: a custom StateProvider is in StateProviders.cs
+    public struct MultiMeasureJudgement
+    {
+        /// <summary>
+        /// The benefit afforded the phenotype by the environment
+        /// b in the paper
+        /// </summary>
+        public double Benefit { get; }
+
+        /// <summary>
+        /// The cost of the genome
+        /// c in the paper
+        /// </summary>
+        public double Cost { get; }
+
+        /// <summary>
+        /// The combined fitness
+        /// (b - c * λ)
+        /// </summary>
+        public double CombinedFitness { get; }
+
+        public MultiMeasureJudgement(double b, double c, double combinedFitness)
+        {
+            Benefit = b;
+            Cost = c;
+            CombinedFitness = combinedFitness;
+        }
+
+        /// <summary>
+        /// Computes the fitness of the Genome/Phenotype combination given this Target and JudgementRules
+        /// f_s(G) in the paper
+        /// </summary>
+        /// <param name="g">The genome (G)</param>
+        /// <param name="p">The phenotype (P*)</param>
+        /// <param name="jrules">Judgement Rules (regularistion etc.)</param>
+        /// <param name="jrules">The current environment (Target)</param>
+        /// <returns>The fitness of the Phenotype (w(P*))</returns>
+        public static MultiMeasureJudgement Judge(IGenome g, Phenotype p, JudgementRules jrules, ITarget target)
+        {
+            double b = target.Judge(p);
+            
+            double λ = jrules.RegularisationFactor;
+            double c = jrules.RegularisationFunction.ComputeCost(g);
+
+            return new MultiMeasureJudgement(b, c, b - c * λ);
+        }
+    }
+
+    [StateClass]
+    public class Phenotype
+    {
+        /// <summary>
+        /// The vector representing the phenotype
+        /// P* in the paper
+        /// </summary>
+        [VectorStateProperty("Vector")]
+        public Linear.Vector<double> Vector { get; private set; }
+        
+        /// <summary>
+        /// The size of the Phenotype
+        /// </summary>
+        public int Size => Vector.Count;
+
+        [Obsolete]
+        private Phenotype()
+        { }
+
+        /// <summary>
+        /// Creates a Phenotype
+        /// </summary>
+        /// <param name="developedPhenotypeVector">The vector representing the phenotype (P*)</param>
+        public Phenotype(Linear.Vector<double> developedPhenotypeVector)
+        {
+            Vector = developedPhenotypeVector;
+        }
+
+        /// <summary>
+        /// Gets the expression of trait idx
+        /// </summary>
+        /// <param name="idx">The index of the trait</param>
+        public double this[int idx] => Vector[idx];
+
+        /// <summary>
+        /// Saturates the phenotype to the given min/max values based on the given threshold
+        /// </summary>
+        /// <returns></returns>
+        public Phenotype Saturate(double min, double threshold, double max)
+        {
+            return new Phenotype(Linear.CreateVector.Dense<double>(Vector.Count, i => Misc.Saturate(Vector[i], min, threshold, max)));
+        }
+    }
+    
+    /// <summary>
+    /// IGenome
+    /// Something that can develop into a Phenotype
+    /// </summary>
+    public interface IGenome
+    {
+        /// <summary>
+        /// Initial state before development
+        /// G in the paper
+        /// </summary>
+        Linear.Vector<double> InitialState { get; }
+
+        /// <summary>
+        /// Recursively applied developmental transformation matrix
+        /// B in the paper
+        /// </summary>
+        Linear.Matrix<double> TransMat { get; }
+
+        /// <summary>
+        /// The size of the Genome
+        /// N in the paper
+        /// </summary>
+        int Size { get; }
+
+        /// <summary>
+        /// Develops the Genotype into a Phenotype of the same Size given the DevelopmentalRules
+        /// </summary>
+        /// <param name="rules">The Rules to apply for development</param>
+        /// <returns>The developed phenotype (P*)</returns>
+        Phenotype Develop(ModelExecutionContext context, DevelopmentRules rules);
+
+        /// <summary>
+        /// Develops the Genotype into a Phenotype of the same Size given the DevelopmentalRules
+        /// </summary>
+        /// <param name="phenotype">The target Phenotype</param>
+        /// <param name="rules">The Rules to apply for development</param>
+        void DevelopInto(Phenotype phenotype, ModelExecutionContext context, DevelopmentRules rules);
+    }
+
+    /// <summary>
+    /// IGenome<typeparamref name="T"/>
+    /// An IGenome that can produce a mutant
+    /// </summary>
+    /// <typeparam name="T">Mutant type</typeparam>
+    public interface IGenome<T> : IGenome where T : IGenome<T>
+    {
+        /// <summary>
+        /// Creates a new offspring Genome (of type) with a single mutation
+        /// </summary>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        /// <returns>The offspring</returns>
+        T Mutate(ModelExecutionContext context, ReproductionRules rules);
+
+        /// <summary>
+        /// Mutates this Genome, turning the target Genome into an offspring
+        /// </summary>
+        /// <param name="target">The target Genome</param>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        void MutateInto(T target, ModelExecutionContext context, ReproductionRules rules);
+
+        /// <summary>
+        /// Mutates this Genome inplace
+        /// </summary>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        void MutateInplace(ModelExecutionContext context, ReproductionRules rules);
+
+        /// <summary>
+        /// Combines this Genome with another, turning the target Genome into an unmutated offspring
+        /// </summary>
+        /// <param name="other">The other Genome involves in the combination</param>
+        /// <param name="target">The target Genome</param>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        void CombineInto(T other, T target, ModelExecutionContext context, ReproductionRules rules);
+
+        /// <summary>
+        /// Create an exact copy, duplicating any state
+        /// </summary>
+        T Clone(ModelExecutionContext context);
+    }
+
+    // NOTE: a custom StateProvider is in StateProviders.cs
+    public class MatrixEntryAddress
+    {
+        public int Row { get; private set; }
+        public int Col { get; private set; }
+        
+        public MatrixEntryAddress(int r, int c)
+        {
+            Row = r;
+            Col = c;
+        }
+
+        public static MatrixEntryAddress[] Complete(int rows, int cols)
+        {
+            MatrixEntryAddress[] res = new MatrixEntryAddress[rows * cols];
+
+            int o = 0;
+            for (int r = 0; r < rows; r++)
+            {
+                for (int c = 0; c < cols; c++)
+                {
+                    res[o++] = new MatrixEntryAddress(r, c);
+                }
+            }
+
+            return res;
+        }
+
+        public static IEnumerable<MatrixEntryAddress> CompleteLazy(int rows, int cols)
+        {
+            for (int r = 0; r < rows; r++)
+            {
+                for (int c = 0; c < cols; c++)
+                {
+                    yield return new MatrixEntryAddress(r, c);
+                }
+            }
+        }
+
+        public override string ToString()
+        {
+            return $"({Row},{Col})";
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is MatrixEntryAddress address &&
+                   Row == address.Row &&
+                   Col == address.Col;
+        }
+
+        public override int GetHashCode()
+        {
+            int hashCode = 1084646500;
+            hashCode = hashCode * -1521134295 + Row.GetHashCode();
+            hashCode = hashCode * -1521134295 + Col.GetHashCode();
+            return hashCode;
+        }
+    }
+
+    [StateClass]
+    public class ColumnsTransMatMutator : ITransMatMutator
+    {
+        public string Name => "Columns" + Size;
+        
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        [SimpleStateProperty("Modules")]
+        public int Modules { get; private set; }
+
+        [SimpleStateProperty("ModuleSize")]
+        public int ModuleSize { get; private set; }
+
+        [Obsolete]
+        private ColumnsTransMatMutator()
+        {}
+
+        [Obsolete]
+        [StateMethod]
+        private void PostRead(object ctx)
+        {
+            PrepareRegions();
+        }
+
+        private MatrixEntryAddress[][] Regions { get; set; }
+
+        public ColumnsTransMatMutator(int size, int modules)
+        {
+            Size = size;
+            Modules = modules;
+            ModuleSize = size / modules;
+
+            PrepareRegions();
+        }
+
+        private void PrepareRegions()
+        {
+            Regions = Misc.CreateEmpty<MatrixEntryAddress>(Size * Modules, ModuleSize);
+            for (int c = 0; c < Size; c++)
+            {
+                for (int r = 0; r < Size; r++)
+                {
+                    Regions[c * Modules + r / ModuleSize][r % ModuleSize] = new MatrixEntryAddress(r, c);
+                }
+            }
+        }
+        
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            // no clone
+            var newTransMat = Linear.CreateMatrix.Dense<double>(Size, Size);
+            Mutators.RegionalTransMatMutateInto(Regions, current, newTransMat, transMatIndexOpenEntries, rand, rules);
+            
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            // no copy
+            Mutators.RegionalTransMatMutateInto(Regions, current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            throw new NotSupportedException("MutateInPlace is not supported by ColumnsTransMatMutator"); 
+        }
+    }
+    
+    [StateClass]
+    public class Columns2TransMatMutator : ITransMatMutator
+    {
+        public string Name => "Columns" + Size + "/" + ModuleSize;
+
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        [SimpleStateProperty("ModuleCount")]
+        public int ModuleCount { get; private set; }
+
+        [SimpleStateProperty("ModuleSize")]
+        public int ModuleSize { get; private set; }
+        
+        [Obsolete]
+        private Columns2TransMatMutator()
+        { }
+
+        public Columns2TransMatMutator(int size, int modules)
+        {
+            Size = size;
+            ModuleCount = modules;
+
+            ModuleSize = size / modules;
+        }
+        
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            // no clone
+            var newTransMat = Linear.CreateMatrix.Dense<double>(Size, Size);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+            
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same vector");
+
+            // no copy
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null)
+            {
+                // mark all open entries
+                foreach (var mea in transMatIndexOpenEntries)
+                {
+                    int r, c; // row, col
+
+                    r = mea.Row;
+                    c = mea.Col;
+
+                    newTransMat[r, c] = 1;
+                }
+            }
+
+            for (int i = 0; i < newTransMat.RowCount; i += ModuleSize)
+            {
+                for (int c = 0; c < newTransMat.ColumnCount; c++)
+                {
+                    // draw delta
+                    double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                    for (int r = i; r < i + ModuleSize; r++)
+                    {
+                        if (transMatIndexOpenEntries != null && newTransMat[r, c] < 1)
+                        {
+                            // skip unmarked entries if transMatIndexOpenEntries is non-null
+                            newTransMat[r, c] = current[r, c];
+                        }
+                        else
+                        {
+                            // apply
+                            newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]); // clamping
+                        }
+                    }
+                }
+            }
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null)
+            {
+                throw new NotSupportedException("MutateInPlace is not supported by Columns2TransMatMutator with non-null transMatIndexOpenEntries");
+            }
+            else
+            {
+                MutateIntoInternal(transMat, transMat, null, rand, rules);
+            }
+        }
+    }
+    
+    [StateClass]
+    public class SingleColumnTransMatMutator : ITransMatMutator
+    {
+        public string Name => "SingleColumn" + Size + "/" + ModuleSize;
+        
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        [SimpleStateProperty("Modules")]
+        public int Modules { get; private set; }
+
+        [SimpleStateProperty("ModuleSize")]
+        public int ModuleSize { get; private set; }
+        
+        [Obsolete]
+        private SingleColumnTransMatMutator()
+        { }
+        
+        public SingleColumnTransMatMutator(int size, int modules)
+        {
+            Size = size;
+            Modules = modules;
+
+            ModuleSize = size / modules;
+        }
+        
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            var newTransMat = current.Clone();
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+            
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+            
+            current.CopyTo(newTransMat);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            // pick a random region
+            int region = rand.Next(Size * Modules);
+            
+            // draw delta
+            double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+            // update single region
+            if (transMatIndexOpenEntries != null)
+            {
+                foreach (var mea in transMatIndexOpenEntries)
+                {
+                    int r, c; // row, col
+
+                    r = mea.Row;
+                    c = mea.Col;
+
+                    if (c + (r / ModuleSize) * Size == region)
+                        newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+                }
+            }
+            else
+            {
+                int r = (region / Size) * ModuleSize;
+                int c = region % Size;
+
+                for (int ro = 0; ro < ModuleSize; ro++)
+                {
+                    newTransMat[r + ro, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r + ro, c]);
+                }
+            }
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(transMat, transMat, transMatIndexOpenEntries, rand, rules);
+        }
+    }
+
+    /// <summary>
+    /// Updates only a single cell (or does nothing, if there are no open entries)
+    /// </summary>
+    [StateClass]
+    public class SingleCellTransMatMutator : ITransMatMutator
+    {
+        public string Name => "SingleCell";
+
+        public SingleCellTransMatMutator()
+        {
+        }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null && transMatIndexOpenEntries.Count == 0)
+                return current; // don't copy
+
+            var newTransMat = current.Clone();
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            current.CopyTo(newTransMat);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null)
+            {
+                if (transMatIndexOpenEntries.Count == 0)
+                    return; // nothing to do
+
+                // pick a random cell
+                int cidx = rand.Next(transMatIndexOpenEntries.Count);
+
+                var mea = transMatIndexOpenEntries[cidx];
+
+                int r = mea.Row;
+                int c = mea.Col;
+
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+            }
+            else
+            {
+                int size = current.ColumnCount;
+
+                // pick a random cell
+                int r = rand.Next(size);
+                int c = rand.Next(size);
+
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+            }
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(transMat, transMat, transMatIndexOpenEntries, rand, rules);
+        }
+    }
+
+    /// <summary>
+    /// Updates only a single cell and it's symmetric partner (or does nothing, if there are no open entries)
+    /// </summary>
+    [StateClass]
+    public class SingleCellSymmetricTransMatMutator : ITransMatMutator
+    {
+        public string Name => "SingleCellSymmetric";
+
+        public SingleCellSymmetricTransMatMutator()
+        {
+        }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null && transMatIndexOpenEntries.Count == 0)
+                return current; // don't copy
+
+            var newTransMat = current.Clone();
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            current.CopyTo(newTransMat);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null)
+            {
+                if (transMatIndexOpenEntries.Count == 0)
+                    return; // nothing to do
+
+                // pick a random cell
+                int cidx = rand.Next(transMatIndexOpenEntries.Count);
+
+                var mea = transMatIndexOpenEntries[cidx];
+
+                int r = mea.Row;
+                int c = mea.Col;
+
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+                newTransMat[c, r] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[c, r]);
+            }
+            else
+            {
+                int size = current.ColumnCount;
+
+                // pick a random cell
+                int r = rand.Next(size);
+                int c = rand.Next(size);
+
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+                newTransMat[c, r] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[c, r]);
+            }
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(transMat, transMat, transMatIndexOpenEntries, rand, rules);
+        }
+    }
+
+    /// <summary>
+    /// Moves weight between two cells.
+    /// </summary>
+    [StateClass]
+    public class CellTransferTransMatMutator : ITransMatMutator
+    {
+        public string Name => "CellTransfer";
+
+        public CellTransferTransMatMutator()
+        {
+        }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null && transMatIndexOpenEntries.Count == 0)
+                return current; // don't copy
+
+            var newTransMat = current.Clone();
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            current.CopyTo(newTransMat);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            int r1, c1, r2, c2;
+
+            if (transMatIndexOpenEntries != null)
+            {
+                if (transMatIndexOpenEntries.Count <= 1)
+                    return; // nothing to do
+
+                // pick two random (distinct) cells
+                int c1idx = rand.Next(transMatIndexOpenEntries.Count);
+                int c2idx = rand.Next(transMatIndexOpenEntries.Count - 1);
+                if (c2idx >= c1idx)
+                    c2idx++;
+
+                var mea1 = transMatIndexOpenEntries[c1idx];
+                var mea2 = transMatIndexOpenEntries[c2idx];
+
+                r1 = mea1.Row;
+                c1 = mea1.Col;
+                r2 = mea2.Row;
+                c2 = mea2.Col;
+            }
+            else
+            {
+                int size = current.ColumnCount;
+
+                // pick two random (distinct) cells
+                int c1idx = rand.Next(size * size);
+                int c2idx = rand.Next(size * size - 1);
+                if (c2idx >= c1idx)
+                    c2idx++;
+
+                r1 = c1idx / size;
+                c1 = c1idx % size;
+                r2 = c2idx / size;
+                c2 = c2idx % size;
+            }
+
+            // draw delta
+            double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+            // find out how much we can transfer without clamping
+            var v1 = rules.DevelopmentalTransformationMatrixClamping.Clamp(current[r1, c1] + μ2) - current[r1, c1];
+            var v2 = rules.DevelopmentalTransformationMatrixClamping.Clamp(current[r2, c2] - μ2) - current[r2, c2];
+
+            // only draw as much as we can draw
+            var v = Math.Min(Math.Abs(v1), Math.Abs(v2));
+            μ2 = Math.Sign(μ2) * v;
+
+            // transfer it
+            newTransMat[r1, c1] = rules.DevelopmentalTransformationMatrixClamping.Clamp(current[r1, c1] + μ2);
+            newTransMat[r2, c2] = rules.DevelopmentalTransformationMatrixClamping.Clamp(current[r2, c2] - μ2);
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(transMat, transMat, transMatIndexOpenEntries, rand, rules);
+        }
+    }
+
+    /// <summary>
+    /// Updates only a single module or inter-module block
+    /// </summary>
+    [StateClass]
+    public class SingleByModuleTransMatMutator : ITransMatMutator
+    {
+        public string Name { get; private set; }
+
+        /// <summary>
+        /// The modules by which to mutate
+        /// </summary>
+        [SimpleStateProperty("Modules")]
+        public Modular.Modules Modules { get; private set; }
+
+        /// <summary>
+        /// Whether to divive weights by the number of cells in the block
+        /// </summary>
+        [SimpleStateProperty("DivideWeights")]
+        public bool DivideWeights { get; private set; }
+
+        [Obsolete]
+        protected SingleByModuleTransMatMutator()
+        {
+        }
+
+        public SingleByModuleTransMatMutator(Modular.Modules modules, bool divideWeights)
+        {
+            Modules = modules;
+            DivideWeights = divideWeights;
+
+            Name = "SingleByModule" + (divideWeights ? "DivideWeights" : "") + modules.ToString();
+        }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null && transMatIndexOpenEntries.Count == 0)
+                return current; // don't copy
+
+            var newTransMat = current.Clone();
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            current.CopyTo(newTransMat);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        // not a very efficient implementation
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null)
+            {
+                if (transMatIndexOpenEntries.Count == 0)
+                    return; // nothing to do
+
+                // mark open
+                foreach (var open in transMatIndexOpenEntries)
+                    newTransMat[open.Row, open.Col] = double.NaN;
+
+                // pick two random modules
+                int midxr = rand.Next(Modules.ModuleCount);
+                int midxc = rand.Next(Modules.ModuleCount);
+
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                // divide weights if necessary
+                if (DivideWeights)
+                    μ2 /= (Modules[midxr].Count * Modules[midxc].Count);
+
+                // apply to all
+                foreach (var r in Modules[midxr])
+                    foreach (var c in Modules[midxc])
+                        if (double.IsNaN(newTransMat[r, c]))
+                            newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+
+                // copy those still open
+                foreach (var open in transMatIndexOpenEntries)
+                    if (double.IsNaN(newTransMat[open.Row, open.Col]))
+                        newTransMat[open.Row, open.Col] = current[open.Row, open.Col];
+            }
+            else
+            {
+                // pick two random modules
+                int midxr = rand.Next(Modules.ModuleCount);
+                int midxc = rand.Next(Modules.ModuleCount);
+
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                // divide weights if necessary
+                if (DivideWeights)
+                    μ2 /= (Modules[midxr].Count * Modules[midxc].Count);
+
+                // apply to all
+                foreach (var r in Modules[midxr])
+                    foreach (var c in Modules[midxc])
+                        newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+            }
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(transMat, transMat, transMatIndexOpenEntries, rand, rules);
+        }
+    }
+
+    /// <summary>
+    /// Doesn't do any mutating at all
+    /// </summary>
+    [StateClass]
+    public class NullTransMatMutator : ITransMatMutator
+    {
+        public static readonly NullTransMatMutator Instance = new NullTransMatMutator();
+
+        public string Name => "NullTransMatMutator";
+
+        protected NullTransMatMutator()
+        {
+        }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null && transMatIndexOpenEntries.Count == 0)
+                return current; // don't copy
+
+            var newTransMat = current.Clone();
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            current.CopyTo(newTransMat);
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            // nix
+        }
+    }
+
+    /// <summary>
+    /// Throws if you try to use it
+    /// </summary>
+    [StateClass]
+    public class ThrowTransMatMutator : ITransMatMutator
+    {
+        public static readonly ThrowTransMatMutator Instance = new ThrowTransMatMutator();
+
+        public string Name => "ThrowTransMatMutator";
+
+        protected ThrowTransMatMutator()
+        {
+        }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            throw new InvalidOperationException("Invoked a ThrowTransMatMutator");
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            throw new InvalidOperationException("Invoked a ThrowTransMatMutator");
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            throw new InvalidOperationException("Invoked a ThrowTransMatMutator");
+        }
+    }
+
+    /// <summary>
+    /// Performs multiple single-cell updates (or does nothing, if there are no open entries)
+    /// </summary>
+    [StateClass]
+    public class MutliCellTransMatMutator : ITransMatMutator
+    {
+        [Obsolete]
+        protected MutliCellTransMatMutator()
+        {
+        }
+        
+        public string Name => "MultiCell" + UpdateCount;
+        
+        public MutliCellTransMatMutator(int updateCount)
+        {
+            UpdateCount = updateCount;
+        }
+
+        [SimpleStateProperty("UpdateCount")]
+        public int UpdateCount { get; private set; }
+
+        public Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null && transMatIndexOpenEntries.Count == 0)
+                return current; // don't copy
+            
+            var newTransMat = current.Clone();
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+            
+            return newTransMat;
+        }
+
+        public void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            current.CopyTo(newTransMat);
+            MutateIntoInternal(current, newTransMat, transMatIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (transMatIndexOpenEntries != null)
+            {
+                if (transMatIndexOpenEntries.Count == 0)
+                    return; // nothing to do
+                
+                for (int i = 0; i < UpdateCount; i++)
+                {
+                    // pick a random cell
+                    int cidx = rand.Next(transMatIndexOpenEntries.Count);
+
+                    var mea = transMatIndexOpenEntries[cidx];
+
+                    int r = mea.Row;
+                    int c = mea.Col;
+                    
+                    // draw delta
+                    double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+                    newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+                }
+            }
+            else
+            {
+                int size = current.ColumnCount;
+                
+                for (int i = 0; i < UpdateCount; i++)
+                {
+                    // pick a random cell
+                    int r = rand.Next(size);
+                    int c = rand.Next(size);
+                
+                    // draw delta
+                    double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                    newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+                }
+            }
+        }
+
+        public void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(transMat, transMat, transMatIndexOpenEntries, rand, rules);
+        }
+    }
+
+    public static class Mutators
+    {
+        /// <summary>
+        /// General purpose method that probably isn't what you want if you want performance
+        /// Applies the same delta to each entry in each region, ignoring entries that arn't in transMatIndexOpenEntries
+        /// </summary>
+        public static void RegionalTransMatMutateInto(MatrixEntryAddress[][] regions, Linear.Matrix<double> current, Linear.Matrix<double> newTransMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newTransMat)
+                throw new Exception("Cannot mutate into the same matrix");
+
+            if (transMatIndexOpenEntries != null)
+            {
+                // mark all open entries
+                foreach (var mea in transMatIndexOpenEntries)
+                {
+                    int r, c; // row, col
+
+                    r = mea.Row;
+                    c = mea.Col;
+
+                    newTransMat[r, c] = 1;
+                }
+            }
+
+            foreach (var region in regions)
+            {
+                // draw delta
+                double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                foreach (var mea in region)
+                {
+                    int r, c; // row, col
+
+                    r = mea.Row;
+                    c = mea.Col;
+
+                    if (transMatIndexOpenEntries != null && newTransMat[r, c] < 1)
+                    {
+                        // skip unmarked entries if transMatIndexOpenEntries is non-null
+                        newTransMat[r, c] = current[r, c];
+                    }
+                    else
+                    {
+                        // apply (unclamped)
+                        newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + current[r, c]);
+                    }
+                }
+            }
+        }
+
+        public static ITransMatMutator Columns(int size, int modules)
+        {
+            return new ColumnsTransMatMutator(size, modules);
+        }
+
+        public static ITransMatMutator Columns2(int size, int modules)
+        {
+            return new Columns2TransMatMutator(size, modules);
+        }
+
+        public static ITransMatMutator SingleColumn(int size, int modules)
+        {
+            return new SingleColumnTransMatMutator(size, modules);
+        }
+    }
+
+    /// <summary>
+    /// Not thread safe
+    /// </summary>
+    [StateClass]
+    public class DefaultDevelopmentStep : IDevelopmentStep
+    {
+        public string Name => $"Default(Size:{Size},Noise:{StateNoise})";
+
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+        
+        [SimpleStateProperty("StateNoise")]
+        public double StateNoise { get; private set; }
+
+        [Obsolete]
+        private DefaultDevelopmentStep()
+        { }
+
+        [StateMethod]
+        private void PostRead(object ctx)
+        {
+            Init();
+        }
+        
+        private Linear.Vector<double> Update;
+
+        public DefaultDevelopmentStep(int size, double stateNoise)
+        {
+            Size = size;
+            StateNoise = stateNoise;
+
+            Init();
+        }
+
+        private void Init()
+        {
+            Update = Linear.CreateVector.Dense<double>(Size);
+        }
+
+        public void Step(Linear.Vector<double> state, Linear.Matrix<double> transMat, RandomSource rand, DevelopmentRules drules, Linear.Vector<double> bias)
+        {
+            transMat.Multiply(state, Update); // this is where the CPU spends most of its time (N^2)
+            if (bias != null)
+                Update.Add(bias);
+
+            // no managed provider can help us here
+            if (Size < 4096) // if MathNet is just going to do it on one thread... (MathNET uses this constant internally)
+            {
+                // ... then do it ourselves (probably adds some overhead accessing storage; saves overhead of late fast-pathing in MathNET (which includes allocations))
+
+                double rt = 1 - drules.DecayRate;
+                double ut = drules.UpdateRate;
+
+                // fast path dense storage
+                if (state.Storage is Linear.Storage.DenseVectorStorage<double> sdvs && Update.Storage is Linear.Storage.DenseVectorStorage<double> udvs)
+                {
+                    var sdata = sdvs.Data;
+                    var udata = udvs.Data;
+
+                    for (int i = 0; i < sdata.Length; i++)
+                    {
+                        sdata[i] = sdata[i] * rt + drules.Squash.Squash(udata[i]) * ut; // or this, if the matrix is small (N)
+                    }
+                }
+                else
+                {
+                    for (int i = 0; i < Size; i++)
+                    {
+                        state[i] = state[i] * rt + drules.Squash.Squash(Update[i]) * ut; // or this, if the matrix is small (N)
+                    }
+                }
+            }
+            else
+            {
+                // let MathNET do it
+                Update.MapInplace(drules.Squash.Delegate); // or this, if the matrix is small (N)
+
+                Update.Multiply(drules.UpdateRate, Update);
+
+                state.Multiply(1 - drules.DecayRate, state);
+                state.Add(Update, state);
+            }
+
+            if (StateNoise > 0.0)
+            {
+                state.Add(Linear.Double.DenseVector.Build.DenseOfArray(MathNet.Numerics.Distributions.Normal.Samples(rand, 0.0, StateNoise).Take(state.Count).ToArray()), state);
+            }
+        }
+
+        public void StepOld(Linear.Vector<double> state, Linear.Matrix<double> transMat, RandomSource rand, DevelopmentRules drules)
+        {
+            transMat.Multiply(state, Update); // this is where the CPU spends most of its time (N^2)
+
+            // no managed provider can help us here
+            if (Size < 4096) // if MathNet is just going to do it on one thread... (MathNET uses this constant internally)
+            {
+                // ... then do it ourselves (probably adds some overhead accessing storage; saves overhead of late fast-pathing in MathNET (which includes allocations))
+                for (int i = 0; i < Size; i++)
+                    Update[i] = drules.Squash.Squash(Update[i]);
+            }
+            else
+            {
+                // let MathNET do it
+                Update.MapInplace(drules.Squash.Delegate); // or this, if the matrix is small (N)
+            }
+
+            Update.Multiply(drules.UpdateRate, Update);
+
+            state.Multiply(1 - drules.DecayRate, state);
+            state.Add(Update, state);
+
+            if (StateNoise > 0.0)
+            {
+                state.Add(Linear.Double.DenseVector.Build.DenseOfArray(MathNet.Numerics.Distributions.Normal.Samples(rand, 0.0, StateNoise).Take(state.Count).ToArray()), state);
+            }
+        }
+    }
+
+    [StateClass]
+    public class SharingDevelopmentStep : IDevelopmentStep
+    {
+        public string Name => $"Sharing({Size},Noise:{StateNoise})";
+
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+        
+        [SimpleStateProperty("StateNoise")]
+        public double StateNoise { get; private set; }
+
+        [Obsolete]
+        private SharingDevelopmentStep()
+        { }
+
+        [StateMethod]
+        private void PostRead(object ctx)
+        {
+            Init();
+        }
+        
+        private Linear.Vector<double> Update;
+
+        public SharingDevelopmentStep(int size, double stateNoise)
+        {
+            Size = size;
+            StateNoise = stateNoise;
+
+            Init();
+        }
+
+        private void Init()
+        {
+            Update = Linear.CreateVector.Dense<double>(Size);
+        }
+
+        public void Step(Linear.Vector<double> state, Linear.Matrix<double> transMat, RandomSource rand, DevelopmentRules drules, Linear.Vector<double> bias)
+        {
+            var normed = transMat.PointwiseAbs().NormalizeColumns(1.0);
+            var sharingMatrix = transMat.PointwiseMultiply(normed);
+
+            sharingMatrix.Multiply(state, Update);
+            if (bias != null)
+                Update.Add(bias);
+            Update.MapInplace(drules.Squash.Delegate);
+            Update.Multiply(drules.UpdateRate, Update);
+
+            state.Multiply(1 - drules.DecayRate, state);
+            state.Add(Update, state);
+
+            if (StateNoise > 0.0)
+            {
+                state.Add(Linear.Double.DenseVector.Build.DenseOfArray(MathNet.Numerics.Distributions.Normal.Samples(rand, 0.0, StateNoise).ToArray()));
+            }
+        }
+    }
+
+    public static class Development
+    {
+        public static IDevelopmentStep DefaultDevelopmentStep(int size, double stateNoise = 0.0)
+        {
+            return new DefaultDevelopmentStep(size, stateNoise);
+        }
+        
+        public static IDevelopmentStep SharingDevelopmentStep(int size, double stateNoise = 0.0)
+        {
+            return new SharingDevelopmentStep(size, stateNoise);
+        }
+    }
+
+    [StateClass]
+    public class UniformCrossOver : IInitialStateCombiner, ITransMatCombiner
+    {
+        public static readonly UniformCrossOver Instance = new UniformCrossOver();
+
+        public UniformCrossOver()
+        {
+        }
+
+        public string Name => "UniformCrossover";
+        
+        public void CombineInto(Linear.Vector<double> a, Linear.Vector<double> b, Linear.Vector<double> target, RandomSource rand, ReproductionRules rules)
+        {
+            var rb = new RandomBits(rand);
+
+            for (int i = 0; i < target.Count; i++)
+            {
+                target[i] = rb.NextBit() ? a[i] : b[i];
+            }
+        }
+
+        public void CombineInto(Linear.Matrix<double> a, Linear.Matrix<double> b, Linear.Matrix<double> target, RandomSource rand, ReproductionRules rules)
+        {
+            var rb = new RandomBits(rand);
+
+            for (int i = 0; i < target.ColumnCount; i++)
+            {
+                for (int j = 0; j < target.RowCount; j++)
+                {
+                    target[i, j] = rb.NextBit() ? a[i, j] : b[i, j];
+                }
+            }
+        }
+    }
+
+    public interface IInitialStateCombiner
+    {
+        string Name { get; }
+
+        /// <summary>
+        /// Will not modify any of the parameters
+        /// Implies a copy from to target (assumes a, b, and target have the same dimension)
+        /// </summary>
+        void CombineInto(Linear.Vector<double> a, Linear.Vector<double> b, Linear.Vector<double> target, RandomSource rand, ReproductionRules rules);
+    }
+
+    public interface ITransMatCombiner
+    {
+        string Name { get; }
+
+        /// <summary>
+        /// Will not modify any of the parameters
+        /// Implies a copy from to target (assumes a, b, and target have the same dimensions)
+        /// </summary>
+        void CombineInto(Linear.Matrix<double> a, Linear.Matrix<double> b, Linear.Matrix<double> target, RandomSource rand, ReproductionRules rules);
+    }
+
+    public interface ITransMatMutator
+    {
+        string Name { get; }
+
+        /// <summary>
+        /// Will not modify any of the parameters
+        /// May return current
+        /// </summary>
+        Linear.Matrix<double> Mutate(Linear.Matrix<double> current, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules);
+
+        /// <summary>
+        /// Modifies only target
+        /// Implies a copy from current to target (assumes they are the same dimensions)
+        /// </summary>
+        void MutateInto(Linear.Matrix<double> current, Linear.Matrix<double> target, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules);
+
+        /// <summary>
+        /// Modifies only target
+        /// </summary>
+        void MutateInplace(Linear.Matrix<double> transMat, IReadOnlyList<MatrixEntryAddress> transMatIndexOpenEntries, RandomSource rand, ReproductionRules rules);
+    }
+
+    public interface IInitialStateMutator
+    {
+        string Name { get; }
+
+        /// <summary>
+        /// Will not modify any of the parameters
+        /// May return current
+        /// </summary>
+        Linear.Vector<double> Mutate(Linear.Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules);
+
+        /// <summary>
+        /// Modifies only target
+        /// Implies a copy from current to target (assumes they are the same dimension)
+        /// </summary>
+        void MutateInto(Linear.Vector<double> current, Linear.Vector<double> target, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules);
+
+        /// <summary>
+        /// Modifies only current
+        /// </summary>
+        void MutateInplace(Linear.Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules);
+    }
+
+    [StateClass]
+    public class ModuleInitialStateMutator : IInitialStateMutator
+    {
+        public string Name => "Modules" + Size + "/" + ModuleSize;
+        
+        [SimpleStateProperty("Size")]
+        public int Size { get; private set; }
+
+        [SimpleStateProperty("Modules")]
+        public int Modules { get; private set; }
+
+        [SimpleStateProperty("ModuleSize")]
+        public int ModuleSize { get; private set; }
+        
+        [Obsolete]
+        private ModuleInitialStateMutator()
+        { }
+        
+        public ModuleInitialStateMutator(int size, int modules)
+        {
+            Size = size;
+            Modules = modules;
+
+            ModuleSize = size / modules;
+        }
+        
+        public Linear.Vector<double> Mutate(Linear.Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            var newInitialState = current.Clone();
+            MutateIntoInternal(current, newInitialState, initialStateIndexOpenEntries, rand, rules);
+            
+            return newInitialState;
+        }
+
+        public void MutateInto(Linear.Vector<double> current, Linear.Vector<double> newInitialState, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newInitialState)
+                throw new Exception("Cannot mutate into the same vector");
+            
+            current.CopyTo(newInitialState);
+            MutateIntoInternal(current, newInitialState, initialStateIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Vector<double> current, Linear.Vector<double> newInitialState, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            for (int u = 0; u < rules.InitialTraitUpdates; u++)
+            {
+                // pick a module
+                int r = rand.Next(Modules);
+                
+                // draw delta
+                double μ1 = rand.NextNoise(rules.InitialStateMutationSize, rules.InitialStateMutationType);
+
+                // apply clamped
+                if (initialStateIndexOpenEntries != null)
+                {
+                    foreach (var i in initialStateIndexOpenEntries)
+                        if (i >= r * ModuleSize && i < (r + 1) * ModuleSize)
+                            newInitialState[i] = rules.InitialStateClamping.Clamp(μ1 + current[i]);
+                }
+                else
+                {
+                    for (int i = r * ModuleSize; i < (r + 1) * ModuleSize; i++)
+                        newInitialState[i] = rules.InitialStateClamping.Clamp(μ1 + current[i]);
+                }
+            }
+        }
+
+        public void MutateInplace(Linear.Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(current, current, initialStateIndexOpenEntries, rand, rules);
+        }
+    }
+
+    [StateClass]
+    public class InitialStateOneHotModulesMutator : IInitialStateMutator
+    {
+        public string Name => "OneHotModules" + Modules.ToString();
+
+        [SimpleStateProperty("Size")]
+        public Modular.Modules Modules { get; private set; }
+
+        [SimpleStateProperty("Cold")]
+        public double Cold { get; private set; }
+
+        [SimpleStateProperty("Hot")]
+        public double Hot { get; private set; }
+
+        [Obsolete]
+        private InitialStateOneHotModulesMutator()
+        { }
+
+        public InitialStateOneHotModulesMutator(Modular.Modules modules, double hot, double cold)
+        {
+            Modules = modules;
+            Hot = hot;
+            Cold = cold;
+        }
+
+        public Linear.Vector<double> Mutate(Linear.Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            var newInitialState = current.Clone();
+            MutateIntoInternal(current, newInitialState, initialStateIndexOpenEntries, rand, rules);
+
+            return newInitialState;
+        }
+
+        public void MutateInto(Linear.Vector<double> current, Linear.Vector<double> newInitialState, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            if (current == newInitialState)
+                throw new Exception("Cannot mutate into the same vector");
+
+            current.CopyTo(newInitialState);
+            MutateIntoInternal(current, newInitialState, initialStateIndexOpenEntries, rand, rules);
+        }
+
+        private void MutateIntoInternal(Linear.Vector<double> current, Linear.Vector<double> newInitialState, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            for (int u = 0; u < rules.InitialTraitUpdates; u++)
+            {
+                // pick a module
+                int mi = rand.Next(Modules.ModuleCount);
+                var m = Modules.ModuleAssignments[mi];
+
+                // pick an element
+                int ei = rand.Next(m.Count);
+
+                // no delta
+                //double μ1 = rand.NextNoise(rules.InitialStateMutationSize, rules.InitialStateMutationType);
+
+                // apply clamped
+                if (initialStateIndexOpenEntries != null)
+                {
+                    throw new InvalidOperationException($"{nameof(InitialStateOneHotModulesMutator)} does not support explicit open entires");
+                }
+                else
+                {
+                    for (int i = 0; i < m.Count; i++)
+                        newInitialState[m[i]] = Cold;
+                    newInitialState[m[ei]] = Hot;
+                }
+            }
+        }
+
+        public void MutateInplace(Linear.Vector<double> current, IReadOnlyList<int> initialStateIndexOpenEntries, RandomSource rand, ReproductionRules rules)
+        {
+            MutateIntoInternal(current, current, initialStateIndexOpenEntries, rand, rules);
+        }
+    }
+
+    public interface IDevelopmentStep
+    {
+        string Name { get; }
+
+        /// <summary>
+        /// Modifies state
+        /// Will not modify anything else
+        /// </summary>
+        void Step(Linear.Vector<double> state, Linear.Matrix<double> transMat, MathNet.Numerics.Random.RandomSource rand, DevelopmentRules drules, Linear.Vector<double> bias);
+    }
+
+    /// <summary>
+    /// Dense Genome (developmental transformation matrix taken to be dense)
+    /// </summary>
+    [StateClass]
+    public class DenseGenome : IGenome<DenseGenome>
+    {
+        /// <summary>
+        /// Initial state before development
+        /// G in the paper
+        /// </summary>
+        [VectorStateProperty("InitialState")]
+        public Linear.Vector<double> InitialState { get; private set; }
+
+        /// <summary>
+        /// The Developmental Bias Vector
+        /// </summary>
+        [VectorStateProperty("BiasVector")]
+        public Linear.Vector<double> BiasVector { get; private set; }
+
+        /// <summary>
+        /// Recursively applied developmental transformation matrix
+        /// B in the paper
+        /// </summary>
+        [MatrixStateProperty("TransMat")]
+        public Linear.Matrix<double> TransMat { get; private set; }
+
+        /// <summary>
+        /// The size of the Genome
+        /// N in the paper
+        /// </summary>
+        public int Size => InitialState.Count;
+
+        /// <summary>
+        /// The list of vector entries that we are allowed to modify
+        /// null implies we can modify them all
+        /// </summary>
+        [SimpleStateProperty("InitialStateIndexOpenEntries")]
+        public IReadOnlyList<int> InitialStateIndexOpenEntries { get; private set; }
+
+        /// <summary>
+        /// The list of matric entries that we are allowed to modify
+        /// null implies we can modify them all
+        /// </summary>
+        [SimpleStateProperty("TransMatIndexOpenEntries")]
+        public IReadOnlyList<MatrixEntryAddress> TransMatIndexOpenEntries { get; private set; }
+
+        /// <summary>
+        /// A custom TransMatMutator
+        /// If null, the default is used
+        /// </summary>
+        [SimpleStateProperty("CustomTransMatMutator")]
+        public ITransMatMutator CustomTransMatMutator { get; private set; }
+
+        /// <summary>
+        /// A custom InitialStateMutator
+        /// If null, the default is used
+        /// </summary>
+        [SimpleStateProperty("CustomInitialStateMutator")]
+        public IInitialStateMutator CustomInitialStateMutator { get; private set; }
+
+        /// <summary>
+        /// A custom TransMatCombiner
+        /// If null, the default is used
+        /// </summary>
+        [SimpleStateProperty("CustomTransMatCombiner")]
+        public ITransMatCombiner CustomTransMatCombiner { get; private set; }
+
+        /// <summary>
+        /// A custom InitialStateCombiner
+        /// If null, the default is used
+        /// </summary>
+        [SimpleStateProperty("CustomInitialStateCombiner")]
+        public IInitialStateCombiner CustomInitialStateCombiner { get; private set; }
+
+        /// <summary>
+        /// A custom DevelopmentStep
+        /// If null, the default is used
+        /// </summary>
+        [SimpleStateProperty("CustomDevelopmentStep")]
+        public IDevelopmentStep CustomDevelopmentStep { get; private set; }
+        
+        [Obsolete]
+        private DenseGenome()
+        { }
+
+        /// <summary>
+        /// Creates a Genome from the initalState (G) vector and developmental transformation matrix (B)
+        /// </summary>
+        /// <param name="initialState">Initial state before development (G)</param>
+        /// <param name="transMat">Recursively applied developmental transformation matrix (B)</param>
+        /// <param name="initialStateOpenEntries">Open entries in the InitialState (null means all entries are open)</param>
+        /// <param name="transMatOpenEntries">Open entries in the TransMat (null means all entries are open)</param>
+        /// <param name="customInitialStateMutator">The custom InitialStateMutator to use</param>
+        /// <param name="customTransMatMutator">The custom TransMatMutator to use</param>
+        /// <param name="customDevelopmentStep">The custom DevelopmentStep to use</param>
+        /// <param name="skipChecks">Where to skip sanity checks: unless you are certain your code works, and performance is a serious concern, just leave this off</param>
+        public DenseGenome(Linear.Vector<double> initialState, Linear.Matrix<double> transMat, IReadOnlyList<int> initialStateOpenEntries = null, IReadOnlyList<MatrixEntryAddress> transMatOpenEntries = null, ITransMatMutator customTransMatMutator = null, IInitialStateMutator customInitialStateMutator = null, ITransMatCombiner customTransMatCombiner = null, IInitialStateCombiner customInitialStateCombiner = null, IDevelopmentStep customDevelopmentStep = null, Linear.Vector<double> biasVector = null, bool skipChecks = false)
+        {
+            if (!skipChecks)
+            {
+                Debug.Assert(initialState.Count > 0, $"Model/Genome.Ctor1: initialState vector must not be empty (length = {initialState.Count})");
+                Debug.Assert(transMat.RowCount == transMat.ColumnCount, $"Model/Genome.Ctor1: transmat must be a square matrix (rows = {transMat.RowCount} cols = {transMat.ColumnCount})");
+                Debug.Assert(initialState.Count == transMat.ColumnCount, $"Model/Genome.Ctor1: initialState vector and transMat must have the same dimensions");
+                Debug.Assert(biasVector == null || initialState.Count == biasVector.Count, $"Model/Genome.Ctor1: initialState vector and biasVector must have the same dimensions");
+
+                if (initialStateOpenEntries != null)
+                {
+                    Debug.Assert(initialStateOpenEntries.All(i => i >= 0 && i < initialState.Count), $"Model/Genome.Ctor1: all initialStateOpenEntries must address into the transMat");
+                }
+
+                if (transMatOpenEntries != null)
+                {
+                    Debug.Assert(transMatOpenEntries.All(mea => mea.Row >= 0 && mea.Row < transMat.RowCount && mea.Col >= 0 && mea.Col < transMat.ColumnCount), $"Model/Genome.Ctor1: all transMatOpenEntries must address into the transMat");
+                }
+            }
+            
+            InitialState = initialState;
+            TransMat = transMat;
+            InitialStateIndexOpenEntries = initialStateOpenEntries;
+            TransMatIndexOpenEntries = transMatOpenEntries;
+            CustomInitialStateMutator = customInitialStateMutator;
+            CustomTransMatMutator = customTransMatMutator;
+            CustomDevelopmentStep = customDevelopmentStep;
+            CustomTransMatCombiner = customTransMatCombiner;
+            CustomInitialStateCombiner = customInitialStateCombiner;
+            BiasVector = biasVector;
+        }
+
+        /// <summary>
+        /// Creates a 'zero' genome of the given size
+        /// </summary>
+        /// <param name="size">The size of the Genome to creates (N)</param>
+        /// <param name="customInitialStateMutator">The custom InitialStateMutator to use</param>
+        /// <param name="customTransMatMutator">The custom TransMatMutator to use</param>
+        /// <param name="customDevelopmentStep">The custom DevelopmentStep to use</param>
+        /// <returns>A 'zero' genome of the given size</returns>
+        public static DenseGenome CreateDefaultGenome(int size, ITransMatMutator customTransMatMutator = null, IInitialStateMutator customInitialStateMutator = null, IDevelopmentStep customDevelopmentStep = null)
+        {
+            return new DenseGenome(Linear.CreateVector.Dense<double>(size), Linear.CreateMatrix.Sparse<double>(size, size), customTransMatMutator : customTransMatMutator, customInitialStateMutator : customInitialStateMutator, customDevelopmentStep: customDevelopmentStep);
+        }
+
+        /// <summary>
+        /// Creates a 'zero' genome of the given size with a genuinely Dense TransMat
+        /// </summary>
+        /// <param name="size">The size of the Genome to creates (N)</param>
+        /// <param name="customInitialStateMutator">The custom InitialStateMutator to use</param>
+        /// <param name="customTransMatMutator">The custom TransMatMutator to use</param>
+        /// <returns>A 'zero' genome of the given size</returns>
+        public static DenseGenome CreateDefaultGenomeDense(int size, ITransMatMutator customTransMatMutator = null, IInitialStateMutator customInitialStateMutator = null, IDevelopmentStep customDevelopmentStep = null)
+        {
+            return new DenseGenome(Linear.CreateVector.Dense<double>(size), Linear.CreateMatrix.Dense<double>(size, size), customTransMatMutator: customTransMatMutator, customInitialStateMutator : customInitialStateMutator, customDevelopmentStep: customDevelopmentStep);
+        }
+
+        /// <summary>
+        /// Creates a 'zero' genome of the given size
+        /// </summary>
+        /// <param name="size">The size of the Genome to create (N)</param>
+        /// <param name="coeficientsPerTrait">Number of regulation coeficients that will be use to update each trait (per-row in the transMat)</param>
+        /// <param name="rand">The RandomSource to use when creating the DenseGenome (not stored)</param>
+        /// <param name="customInitialStateMutator">The custom InitialStateMutator to use</param>
+        /// <param name="customTransMatMutator">The custom TransMatMutator to use</param>
+        /// <returns>A 'zero' genome of the given size</returns>
+        public static DenseGenome CreateDefaultGenomeSparse(int size, int coeficientsPerTrait, RandomSource rand, ITransMatMutator customTransMatMutator = null, IInitialStateMutator customInitialStateMutator = null)
+        {
+            return new DenseGenome(Linear.CreateVector.Dense<double>(size), Linear.CreateMatrix.Sparse<double>(size, size), null, RandomOpenEntries(size, coeficientsPerTrait, rand), customTransMatMutator, customInitialStateMutator);
+        }
+
+        private static MatrixEntryAddress[] RandomOpenEntries(int size, int coeficientsPerTrait, RandomSource rand)
+        {
+            MatrixEntryAddress[] res = new MatrixEntryAddress[size * coeficientsPerTrait];
+
+            int[] sampler = Create<int>(size, i => i);
+
+            int o = 0;
+            for (int i = 0; i < size; i++)
+            {
+                ShuffleInplace(rand, sampler);
+                for (int j = 0; j < coeficientsPerTrait; j++)
+                {
+                    res[o++] = new MatrixEntryAddress(i, sampler[j]);
+                }
+            }
+
+            return res;
+        }
+
+        /// <summary>
+        /// Develops the Genotype into a Phenotype of the same Size given the DevelopmentalRules
+        /// </summary>
+        /// <param name="phenotypeVector">The target Phenotype</param>
+        /// <param name="context">The ModelExecutionContext</param>
+        /// <param name="rules">The Rules to apply for development</param>
+        /// <returns>The developed phenotype (P*)</returns>
+        public void DevelopInto(Phenotype target, ModelExecutionContext context, DevelopmentRules rules)
+        {
+            var phenotypeVector = target.Vector;
+            InitialState.CopyTo(phenotypeVector);
+            DevelopPhenotypeInternal(phenotypeVector, context, rules);
+        }
+
+
+        /// <summary>
+        /// Develops the given state (assumes it is the initial state, and develops it to a the phenotypic state)
+        /// </summary>
+        private void DevelopPhenotypeInternal(Linear.Vector<double> state, ModelExecutionContext context, DevelopmentRules rules)
+        {
+            IDevelopmentStep dstep = CustomDevelopmentStep ?? new DefaultDevelopmentStep(Size, 0.0);
+
+            for (int i = 0; i < rules.TimeSteps; i++)
+            {
+                dstep.Step(state, TransMat, context.Rand, rules, BiasVector);
+            }
+
+            // normalise phenotype via τ1 and τ2
+            double normalisationFactor = 1.0 / (rules.UpdateRate / rules.DecayRate);
+            state.Multiply(normalisationFactor, state);
+
+            // rescale
+            state.Multiply(rules.RescaleScale, state);
+            state.Add(rules.RescaleOffset, state);
+        }
+
+        /// <summary>
+        /// Develops the Genotype into a Phenotype of the same Size given the DevelopmentalRules
+        /// </summary>
+        /// <param name="rules">The Rules to apply for development</param>
+        /// <returns>The developed phenotype (P*)</returns>
+        public Phenotype Develop(ModelExecutionContext context, DevelopmentRules rules)
+        {
+            Linear.Vector<double> state = InitialState.Clone();
+            DevelopPhenotypeInternal(state, context, rules);
+            return new Phenotype(state);
+        }
+
+        /// <summary>
+        /// Develops the Genotype into a Phenotype of the same Size given the DevelopmentalRules
+        /// </summary>
+        /// <param name="rules">The Rules to apply for development</param>
+        /// <param name="trajectories">The arrays to place the output; leave null for it to be built for you</param>
+        /// <returns>The developed phenotype (P*)</returns>
+        public Phenotype DevelopWithTrajectories(MathNet.Numerics.Random.RandomSource rand, DevelopmentRules rules, ref double[][] trajectories)
+        {
+            if (trajectories == null)
+            {
+                trajectories = new double[Size][];
+                for (int trait = 0; trait < Size; trait++)
+                    trajectories[trait] = new double[rules.TimeSteps + 1];
+            }
+
+            Linear.Vector<double> state = InitialState.Clone();
+            
+            IDevelopmentStep dstep = CustomDevelopmentStep ?? new DefaultDevelopmentStep(Size, 0.0);
+            
+            for (int i = 0; i < rules.TimeSteps; i++)
+            {
+                // record trajectories
+                for (int trait = 0; trait < Size; trait++)
+                    trajectories[trait][i] = state[trait];
+
+                dstep.Step(state, TransMat, rand, rules, BiasVector);
+            }
+            
+            // record trajectories (terminals)
+            for (int trait = 0; trait < Size; trait++)
+                trajectories[trait][rules.TimeSteps] = state[trait];
+
+            // normalise phenotype via τ1 and τ2
+            double normalisationFactor = 1.0 / (rules.UpdateRate / rules.DecayRate);
+            state.Multiply(normalisationFactor, state);
+
+            // rescale
+            state.Multiply(rules.RescaleScale, state);
+            state.Add(rules.RescaleOffset, state);
+
+            return new Phenotype(state);
+        }
+
+        /// <summary>
+        /// Creates an exact replica of the DenseGenome with copies of the state (to mitigate any unintended modification)
+        /// </summary>
+        /// <returns>The exact replica</returns>
+        public DenseGenome Clone(ModelExecutionContext context, Linear.Vector<double> newInitialState = null, Linear.Matrix<double> newTransMat = null, Linear.Vector<double> newBiasVector = null)
+        {
+            return new DenseGenome(newInitialState ?? InitialState.Clone(), newTransMat ?? context.Clone(TransMat), InitialStateIndexOpenEntries, TransMatIndexOpenEntries, CustomTransMatMutator, CustomInitialStateMutator, CustomTransMatCombiner, CustomInitialStateCombiner, CustomDevelopmentStep, newBiasVector ?? BiasVector?.Clone(), true);
+        }
+
+        /// <summary>
+        /// Sets the CustomTransmatMutator inplace
+        /// </summary>
+        public void SetCustomTransmatMutator(ITransMatMutator transMatMutator)
+        {
+            this.CustomTransMatMutator = transMatMutator;
+        }
+
+        /// <summary>
+        /// Sets the CustomInitialStateMutator inplace
+        /// </summary>
+        public void SetCustomInitialStateMutator(IInitialStateMutator initialStateMutator)
+        {
+            this.CustomInitialStateMutator = initialStateMutator;
+        }
+
+        /// <summary>
+        /// Creates an exact replice of the DenseGenome with copies of the state (to mitigate any unintended modification)
+        /// </summary>
+        /// <returns>The exact replica</returns>
+        DenseGenome IGenome<DenseGenome>.Clone(ModelExecutionContext context)
+        {
+            return Clone(context);
+        }
+
+        /// <summary>
+        /// Mutates the current Genome, stuffing the new Genome into the given DenseGenome
+        /// This assumes that the target genome has no shared state: it will reuse existing storage
+        /// It also assumes that the dense genomes are the same dimensions; no gaurentees are made if they are not
+        /// </summary>
+        /// <param name="target">The target DenseGenome, which will become the offspring</param>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        public void MutateInto(DenseGenome target, ModelExecutionContext context, ReproductionRules rules)
+        {
+            var rand = context.Rand;
+            
+            bool mutateB = rand.NextDouble() < rules.DevelopmentalTransformationMatrixRate;
+            if (mutateB)
+            { // mutate trans mat (B)
+                if (CustomTransMatMutator != null)
+                {
+                    CustomTransMatMutator.MutateInto(TransMat, target.TransMat, TransMatIndexOpenEntries, rand, rules);
+                }
+                else
+                {
+                    TransMat.CopyTo(target.TransMat);
+                    MutateTransMatDefaultInplace(target.TransMat, rand, rules);
+                }
+            }
+            else
+            {
+                TransMat.CopyTo(target.TransMat);
+            }
+            
+            if (!(mutateB && rules.ExclusiveBMutation))
+            { // mutate initial state (G)
+                if (CustomInitialStateMutator != null)
+                {
+                    CustomInitialStateMutator.MutateInto(InitialState, target.InitialState, InitialStateIndexOpenEntries, rand, rules);
+                }
+                else
+                {
+                    InitialState.CopyTo(target.InitialState);
+                    MutateInitialStateDefaultInplace(target.InitialState, rand, rules);
+                }
+            }
+            else
+            {
+                InitialState.CopyTo(target.InitialState);
+            }
+        }
+        
+        /// <summary>
+        /// Mutates the current Genome inplace.
+        /// Assumes this genome has no shared state (will not duplicate anything)
+        /// </summary>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        public void MutateInplace(ModelExecutionContext context, ReproductionRules rules)
+        {
+            var rand = context.Rand;
+
+            bool mutateB = rand.NextDouble() < rules.DevelopmentalTransformationMatrixRate;
+            if (mutateB)
+            { // mutate trans mat (B)
+                if (CustomTransMatMutator != null)
+                {
+                    CustomTransMatMutator.MutateInplace(TransMat, TransMatIndexOpenEntries, rand, rules);
+                }
+                else
+                {
+                    MutateTransMatDefaultInplace(TransMat, rand, rules);
+                }
+            }
+
+            if (!(mutateB && rules.ExclusiveBMutation))
+            { // mutate initial state (G)
+                if (CustomInitialStateMutator != null)
+                {
+                    CustomInitialStateMutator.MutateInplace(InitialState, InitialStateIndexOpenEntries, rand, rules);
+                }
+                else
+                {
+                    MutateInitialStateDefaultInplace(InitialState, rand, rules);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Creates a new offspring DenseGenome with a single mutation
+        /// </summary>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        /// <returns>The offspring</returns>
+        public DenseGenome Mutate(ModelExecutionContext context, ReproductionRules rules)
+        {
+            var rand = context.Rand;
+
+            // assume unchanged initially
+            var newInitialState = InitialState;
+            var newTransMat = TransMat;
+            
+            bool mutateB = rand.NextDouble() < rules.DevelopmentalTransformationMatrixRate;
+            if (mutateB)
+            { // mutate trans mat (B)
+                if (CustomTransMatMutator != null)
+                {
+                    newTransMat = CustomTransMatMutator.Mutate(TransMat, TransMatIndexOpenEntries, rand, rules);
+                }
+                else
+                {
+                    // new 'pooled' clone
+                    newTransMat = context.Clone(TransMat);
+                    MutateTransMatDefaultInplace(newTransMat, rand, rules);
+                }
+            }
+            
+            if (!(mutateB && rules.ExclusiveBMutation))
+            { // mutate initial state (G)
+                if (CustomInitialStateMutator != null)
+                {
+                    newInitialState = CustomInitialStateMutator.Mutate(InitialState, InitialStateIndexOpenEntries, rand, rules);
+                }
+                else
+                {
+                    newInitialState = InitialState.Clone();
+                    MutateInitialStateDefaultInplace(newInitialState, rand, rules);
+                }
+            }
+            
+            return new DenseGenome(newInitialState, newTransMat, InitialStateIndexOpenEntries, TransMatIndexOpenEntries, CustomTransMatMutator, CustomInitialStateMutator, CustomTransMatCombiner, CustomInitialStateCombiner, CustomDevelopmentStep, BiasVector, true);
+        }
+
+        /// <summary>
+        /// This is the 'traditional' (and deault) initial-state mutation scheme... it's stuffed in this class for not-very-good legacy-related reasons
+        /// </summary>
+        private void MutateTransMatDefaultInplace(Linear.Matrix<double> newTransMat, RandomSource rand, ReproductionRules rules)
+        {
+            // mutate all traits a little
+
+            // select trait
+            if (TransMatIndexOpenEntries == null)
+            { // any
+                for (int r = 0; r < Size; r++)
+                {
+                    for (int c = 0; c < Size; c++)
+                    {
+                        // draw delta
+                        double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                        // apply (unclamped)
+                        newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + newTransMat[r, c]);
+                    }
+                }
+            }
+            else
+            {
+                foreach (var mea in TransMatIndexOpenEntries)
+                {
+                    int r, c; // row, col
+
+                    r = mea.Row;
+                    c = mea.Col;
+                    
+                    // draw delta
+                    double μ2 = rand.NextNoise(rules.DevelopmentalTransformationMatrixMutationSize, rules.DevelopmentalTransformationMatrixMutationType);
+
+                    // apply (unclamped)
+                    newTransMat[r, c] = rules.DevelopmentalTransformationMatrixClamping.Clamp(μ2 + newTransMat[r, c]);
+                }
+            }
+        }
+
+        /// <summary>
+        /// This is the 'traditional' (and deault) initial-state mutation scheme... it's stuffed in this class for not-very-good legacy-related reasons
+        /// </summary>
+        private void MutateInitialStateDefaultInplace(Linear.Vector<double> newInitialState, RandomSource rand, ReproductionRules rules)
+        {
+            if (InitialStateIndexOpenEntries == null)
+            { // any
+                for (int i = 0; i < rules.InitialTraitUpdates; i++)
+                {
+                    // select trait
+                    int r = rand.Next(Size);
+
+                    // draw delta
+                    double μ1 = rand.NextNoise(rules.InitialStateMutationSize, rules.InitialStateMutationType);
+
+                    // apply clamped
+                    newInitialState[r] = rules.InitialStateClamping.Clamp(μ1 + newInitialState[r]);
+                }
+            }
+            else
+            {
+                for (int i = 0; i < rules.InitialTraitUpdates; i++)
+                {
+                    // select trait
+                    int r = InitialStateIndexOpenEntries[rand.Next(InitialStateIndexOpenEntries.Count)];
+
+                    // draw delta
+                    double μ1 = rand.NextNoise(rules.InitialStateMutationSize, rules.InitialStateMutationType);
+
+                    // apply clamped
+                    newInitialState[r] = rules.InitialStateClamping.Clamp(μ1 + newInitialState[r]);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Copies the given initial state vector (G') over the internal initial state vector (G)
+        /// G = G'
+        /// </summary>
+        /// <param name="newInitialState">The new initial state vector G'</param>
+        public void CopyOverInitialState(Linear.Vector<double> newInitialState)
+        {
+            newInitialState.CopyTo(InitialState);
+        }
+
+        /// <summary>
+        /// Copies the given developmental matrix (B') over the internal developmental matrix (B)
+        /// B = B'
+        /// </summary>
+        /// <param name="newInitialState">The new developemntal matrix B'</param>
+        public void CopyOverTransMat(Linear.Matrix<double> newTransMat)
+        {
+            newTransMat.CopyTo(TransMat);
+        }
+
+        /// <summary>
+        /// Only call this is you are CERTAIN that the TransMat can be recycled
+        /// DO NOT PRESUME YOU CAN STUFF THIS BEHIND AN INTERFACE
+        /// You can EASILY break your program by using this, because Genome's will not duplicate the TransMat unless they have to
+        /// </summary>
+        public void Recycle(ModelExecutionContext context)
+        {
+            context.GetMatrixPool(TransMat.RowCount, TransMat.ColumnCount).Release(TransMat);
+        }
+
+        /// <summary>
+        /// Combines this genome with another into the given target genome
+        /// </summary>
+        /// <param name="other">The other Genome involves in the combination</param>
+        /// <param name="target">The target Genome</param>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        public void CombineInto(DenseGenome other, DenseGenome target, ModelExecutionContext context, ReproductionRules rules)
+        {
+            if (CustomInitialStateCombiner != null)
+            {
+                CustomInitialStateCombiner.CombineInto(InitialState, other.InitialState, target.InitialState, context.Rand, rules);
+            }
+            else
+            {
+                UniformCrossOver.Instance.CombineInto(InitialState, other.InitialState, target.InitialState, context.Rand, rules);
+            }
+
+            if (CustomTransMatCombiner != null)
+            {
+                CustomTransMatCombiner.CombineInto(TransMat, other.TransMat, target.TransMat, context.Rand, rules);
+            }
+            else
+            {
+                UniformCrossOver.Instance.CombineInto(TransMat, other.TransMat, target.TransMat, context.Rand, rules);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Modular/ModularTargets.cs b/M4MCode/M4M_MkI/M4M.Model/Modular/ModularTargets.cs
new file mode 100644
index 0000000..65ecbf6
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Modular/ModularTargets.cs
@@ -0,0 +1,216 @@
+using M4M.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M.Modular
+{   
+    [StateClass]
+    public class CoefJudger : IVectorTargetJudger
+    {
+        [Obsolete]
+        protected CoefJudger()
+        {
+        }
+
+        public CoefJudger(string name, double coef)
+        {
+            Name = name;
+            Coef = coef;
+        }
+
+        [SimpleStateProperty("Name")]
+        public string Name { get; private set; }
+
+        [SimpleStateProperty("Coef")]
+        public double Coef { get; private set; }
+
+        public string Description => $"Coef Judger (Coef={Coef})";
+
+        public double Judge(Linear.Vector<double> vector, Phenotype phenotype)
+        {
+            return vector.DotProduct(phenotype.Vector) * Coef;
+        }
+    }
+
+    public class ModularTargetPackage : ITargetPackage
+    {
+        public static CoefJudger CoefJudgment(string name, double coef)
+        {
+            return new CoefJudger(name, coef);
+        }
+
+        public static readonly VectorTarget[] Targets4x1Standard = new[]
+        {
+            new VectorTarget("++++", "H0"),
+            new VectorTarget("----", "L0"),
+        };
+
+        public static readonly VectorTarget[] Targets2x2Standard = new[]
+        {
+            new VectorTarget("----", "L0R0"),
+            new VectorTarget("--++", "L0R1"),
+            new VectorTarget("++++", "L1R1"),
+            new VectorTarget("++--", "L1R0"),
+        };
+
+        public static readonly VectorTarget[] Targets4x4Standard = new[]
+        {
+            new VectorTarget("----++++++++----", "lhhl"),
+            new VectorTarget("++++++++--------", "hhll"),
+            new VectorTarget("++++----++++----", "hlhl"),
+        };
+
+        public static readonly VectorTarget[] Targets4x4Original = new[]
+        {
+            new VectorTarget("-+-+++--+--+----", "lhhl"),
+            new VectorTarget("+-+-++---++-----", "hhll"),
+            new VectorTarget("+-+---+++--+----", "hlhl"),
+        };
+
+        public static readonly VectorTarget[] Targets4x4Total = All(new[] { "-+-+", "++--", "-++-", "----" }, s => new VectorTarget(s, "bleh")).ToArray();
+
+        public static readonly VectorTarget[] Targets4x1HighJudge = new[]
+        {
+            new VectorTarget("++++", "H1.0", CoefJudgment("J1.0", 1.0)),
+            new VectorTarget("++++", "H1.1", CoefJudgment("J1.1", 1.1)),
+        };
+
+        public static readonly VectorTarget[] Targets3x3Standard = new[]
+        {
+            new VectorTarget("---++++++", "lhh"),
+            new VectorTarget("++++++---", "hhl"),
+            new VectorTarget("+++---+++", "hlh"),
+        };
+
+        public static readonly VectorTarget[] TargetsSingle4 = new[]
+        {
+            new VectorTarget("--++", "s4a"),
+            new VectorTarget("--++", "s4b"),
+        };
+
+        public static readonly VectorTarget[] TargetsSingle8 = new[]
+        {
+            new VectorTarget("----++++", "s8a"),
+            new VectorTarget("----++++", "s8b"),
+        };
+
+        public static readonly VectorTarget[] TargetsAnd9 = new[]
+        {
+            new VectorTarget("---------", "And9_1"),
+            new VectorTarget("+++------", "And9_2"),
+            new VectorTarget("---+++---", "And9_3"),
+            new VectorTarget("+++++++++", "And9_4"),
+        };
+
+        public static readonly VectorTarget[] TargetsBool12 = new[]
+        {
+            new VectorTarget("------------", "Bool12_1"),
+            new VectorTarget("+++------+++", "Bool12_2"),
+            new VectorTarget("---+++---+++", "Bool12_3"),
+            new VectorTarget("+++++++++---", "Bool12_4"),
+        };
+
+        public static readonly ModularTargetPackage Standard4x1 = new ModularTargetPackage("Std4x1", Targets4x1Standard, 1);
+        public static readonly ModularTargetPackage Standard2x2 = new ModularTargetPackage("Std2x2", Targets2x2Standard, 2);
+        public static readonly ModularTargetPackage Standard4x4 = new ModularTargetPackage("Std4x4", Targets4x4Standard, 4);
+        public static readonly ModularTargetPackage Original4x4 = new ModularTargetPackage("Org4x4", Targets4x4Original, 4);
+        public static readonly ModularTargetPackage Total4x4 = new ModularTargetPackage("Tot4x4", Targets4x4Total, 4);
+        public static readonly ModularTargetPackage Standard3x3 = new ModularTargetPackage("Std3x3", Targets3x3Standard, 3);
+        public static readonly ModularTargetPackage HighJudge4x1 = new ModularTargetPackage("HiJ4x1", Targets4x1HighJudge, 1);
+        public static readonly ModularTargetPackage Single4 = new ModularTargetPackage("Single4", TargetsSingle4, 2);
+        public static readonly ModularTargetPackage Single8 = new ModularTargetPackage("Single8", TargetsSingle8, 2);
+        public static readonly ModularTargetPackage And9 = new ModularTargetPackage("And9", TargetsAnd9, 3);
+        public static readonly ModularTargetPackage Bool12 = new ModularTargetPackage("Bool12", TargetsBool12, 4);
+
+        public static Dictionary<string, ModularTargetPackage> TargetPackages { get; private set; }
+
+        public ModularTargetPackage(string name, VectorTarget[] targets, int moduleCount, bool register = true)
+        {
+            Name = name;
+            Targets = targets;
+            ModuleCount = moduleCount;
+
+            if (register)
+            { // such terrible programming
+                if (TargetPackages == null)
+                    TargetPackages = new Dictionary<string, ModularTargetPackage>(StringComparer.InvariantCultureIgnoreCase);
+
+                TargetPackages.Add(name, this);
+            }
+        }
+
+        public string Name { get; }
+        public VectorTarget[] Targets { get; }
+        public int ModuleCount { get; }
+
+        public int TargetSize => Targets[0].Size;
+        public int ModuleSize => TargetSize / ModuleCount;
+        
+        IReadOnlyList<ITarget> ITargetPackage.Targets => Targets;
+
+        public static IEnumerable<VectorTarget> All(string[] modules, Func<string, VectorTarget> targetFunc)
+        {
+            return AllStrings(modules.Select(m => new[] { m, m.Replace('+', '#').Replace('-', '+').Replace('#', '-') })).Select(s => targetFunc(s));
+        }
+
+        // why not
+        private static IEnumerable<string> AllStrings(IEnumerable<IEnumerable<string>> strs)
+        {
+            if (strs.Any())
+            {
+                var head = strs.First();
+                var tail = strs.Skip(1);
+
+                foreach (var r in AllStrings(tail))
+                    foreach (var s in head)
+                        yield return s + r;
+            }
+            else
+            {
+                yield return "";
+            }
+        }
+
+        public Linear.Matrix<double> PrepareFox(double totalWeight)
+        {
+            int N = TargetSize;
+            int k = ModuleSize;
+
+            double elementWeight = totalWeight / (k * N);
+
+            double correl(int i, int j) => Targets[0].Vector[i] * Targets[0].Vector[j];
+            var mat = Linear.CreateMatrix.Dense(N, N, (i, j) => i / k == j / k ? elementWeight * correl(i, j) : 0);
+
+            return mat;
+        }
+
+        public Linear.Matrix<double> PrepareBlizzard(double totalWeight)
+        {
+            int N = TargetSize;
+            int k = ModuleSize;
+
+            double elementWeight = totalWeight / N;
+            
+            double correl(int i, int j) => Targets[0].Vector[i] * Targets[0].Vector[j];
+            var mat = Linear.CreateMatrix.Dense(N, N, (i, j) => i == j ? elementWeight * correl(i, j) : 0);
+
+            return mat;
+        }
+
+        public Linear.Matrix<double> PrepareHusky(double totalWeight)
+        {
+            int N = TargetSize;
+            int k = ModuleSize;
+
+            double elementWeight = totalWeight / N;
+            
+            double correl(int i, int j) => Targets[0].Vector[i] * Targets[0].Vector[j];
+            var mat = Linear.CreateMatrix.Dense(N, N, (i, j) => i / k == j / k && (j % k == 0) ? elementWeight * correl(i, j) : 0);
+
+            return mat;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Modular/Modules.cs b/M4MCode/M4M_MkI/M4M.Model/Modular/Modules.cs
new file mode 100644
index 0000000..fb3c648
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Modular/Modules.cs
@@ -0,0 +1,386 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using M4M.State;
+
+namespace M4M.Modular
+{
+    public interface IModulesStringParser
+    {
+        /// <summary>
+        /// The name of Module String Parser
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// User-friendly description of the module string format
+        /// </summary>
+        string Description { get; }
+
+        /// <summary>
+        /// Attempts to parse a string
+        /// </summary>
+        /// <param name="str"></param>
+        /// <param name="modules"></param>
+        /// <returns></returns>
+        bool TryParse(string str, out Modules modules);
+
+        /// <summary>
+        /// Throws is the string format is invalid
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        Modules Parse(string str);
+    }
+
+    public class ModulesStringParser : IModulesStringParser
+    {
+        public static readonly ModulesStringParser Instance = new ModulesStringParser();
+
+        public string Name => "ModulesStringParser";
+
+        public string Description => "ModulesStringParser: aaabbca => [0, 1, 2, 6], [3, 4], [5]. Only support unicode single-char letters and digits (behaviour with surrogate pairs is broken and undefined)";
+
+        public Modules Parse(string str)
+        {
+            return Modules.CreateFromString(str);
+        }
+
+        public bool TryParse(string str, out Modules modules)
+        {
+            if (string.IsNullOrEmpty(str))
+            {
+                modules = null;
+                return false;
+            }
+            else
+            {
+                modules = Parse(str);
+                return true;
+            }
+        }
+    }
+
+    public class BlockModulesStringParser : IModulesStringParser
+    {
+        public static readonly BlockModulesStringParser Instance = new BlockModulesStringParser();
+
+        public string Name => "BlockModulesStringParser";
+
+        public string Description => "Creates block-modules 'along the diagonal': 1,2,3 -> [0], [1, 2], [3, 4, 5] or 2*3 -> [0, 1, 2], [3, 4, 5]";
+
+        public Modules Parse(string str)
+        {
+            return Modules.CreateBlockModules(str.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries).SelectMany(ExpandStar).ToArray());
+        }
+
+        /// <summary>
+        /// 3*2 -> 2,2,2
+        /// Returns null if invalid
+        /// </summary>
+        private static int[] ExpandStar(string maybeStar)
+        {
+            if (maybeStar.Contains("*"))
+            {
+                string[] data = maybeStar.Split('*');
+
+                if (data.Length != 2)
+                    return null;
+                
+                if (!int.TryParse(data[0], out int l))
+                    return null;
+                if (!int.TryParse(data[1], out int r))
+                    return null;
+                
+                return Enumerable.Repeat(r, l).ToArray();
+            }
+            else
+            {
+                if (!int.TryParse(maybeStar, out var n))
+                    return null;
+                return new[] { n };
+            }
+        }
+
+        public bool TryParse(string str, out Modules modules)
+        {
+            if (string.IsNullOrEmpty(str))
+            {
+                modules = null;
+                return false;
+            }
+            else
+            {
+                var data = str.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries).Select(ExpandStar).ToArray();
+
+                if (data.Any(x => x == null))
+                {
+                    modules = null;
+                    return false;
+                }
+
+                modules = Modules.CreateBlockModules(data.SelectMany(x => x).ToArray());
+                return true;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Guesses at the format, but atleast it tells you what it guessed
+    /// </summary>
+    public class MultiModulesStringParser : IModulesStringParser
+    {
+        public static readonly MultiModulesStringParser Instance = new MultiModulesStringParser();
+
+        public string Name => throw new NotImplementedException();
+
+        public string Description => throw new NotImplementedException();
+
+        public Modules Parse(string str)
+        {
+            if (TryParse(str, out var modules))
+                return modules;
+            else
+                throw new Exception($"Unable to parse string '{str}'");
+        }
+
+        public bool TryParse(string str, out Modules modules) => TryParse(str, out modules, out _);
+
+        public bool TryParse(string str, out Modules modules, out IModulesStringParser parser)
+        {
+            parser = InferParser(str);
+            return parser.TryParse(str, out modules);
+        }
+
+        public IModulesStringParser InferParser(string str)
+        {
+            if (str.Contains(',') || str.Contains(';') || str.Contains('*'))
+            {
+                return BlockModulesStringParser.Instance;
+            }
+            else
+            {
+                return ModulesStringParser.Instance;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Represents a collection of disjoint modules
+    /// </summary>
+    [StateClass]
+    public class Modules
+    {
+        [Obsolete]
+        protected Modules()
+        {
+        }
+
+        private Modules(int[][] moduleAssignments, bool copy, bool verify)
+        {
+            if (copy)
+                _moduleAssignments = moduleAssignments.Select(inner => inner.ToArray()).ToArray();
+            else
+                _moduleAssignments = moduleAssignments;
+            
+            int count = _moduleAssignments.Sum(m => m.Length);
+            _elementCount = count;
+
+            if (verify)
+            {
+                // check that every index is used exactly once
+                bool[] table = new bool[count];
+
+                foreach (var m in _moduleAssignments)
+                {
+                    foreach (var i in m)
+                    {
+                        if (i < 0 || i > count)
+                            throw new Exception("Module assignemnt contains invalid index: " + i);
+                        
+                        if (table[i])
+                            throw new Exception("Module assignemnt contains duplicate index: " + i + ". Modules must be disjoint");
+                        
+                        table[i] = true;
+                    }
+                }
+            }
+        }
+
+        public Modules(IEnumerable<IEnumerable<int>> moduleAssignments)
+            : this(moduleAssignments.Select(inner => inner.ToArray()).ToArray(), false, true)
+        {
+        }
+
+        /// <summary>
+        /// The total number of elements
+        /// </summary>
+        [SimpleStateProperty("ElementCount")]
+        private int _elementCount { get; set; }
+
+        /// <summary>
+        /// Returns the total number of elements
+        /// </summary>
+        public int ElementCount
+        {
+            get
+            {
+                if (_elementCount == 0)
+                {
+                    // I messed up, and some of the older instances of this class are serialised with ElementCount=0, so we have to detect that and deal with it
+                    // we can't use PostRead, because ModuleAssignments might not have finished reading, so it could crash
+                    _elementCount = _moduleAssignments.Sum(m => m.Length);
+                }
+
+                return _elementCount;
+            }
+        }
+
+        [SimpleStateProperty("ModuleAssignments")]
+        private int[][] _moduleAssignments { get; set; }
+
+        /// <summary>
+        /// Returns the ModuleAssignments directly
+        /// </summary>
+        public IReadOnlyList<IReadOnlyList<int>> ModuleAssignments => _moduleAssignments;
+
+        /// <summary>
+        /// Creates a copy of the modular assignments
+        /// </summary>
+        /// <returns></returns>
+        public int[][] ToArray()
+        {
+            return _moduleAssignments.Select(inner => inner.ToArray()).ToArray();
+        }
+        
+        /// <summary>
+        /// The number of modules
+        /// </summary>
+        public int ModuleCount => _moduleAssignments.Length;
+
+        /// <summary>
+        /// Enumerates the elements associated with the module of the given index
+        /// </summary>
+        /// <param name="moduleIndex">Module indices are in the range [0, moduleCount-1]</param>
+        public IReadOnlyList<int> this[int moduleIndex] => _moduleAssignments[moduleIndex];
+
+        /// <summary>
+        /// Creates Block Modules of the same size
+        /// </summary>
+        /// <param name="moduleCount">The number of modules</param>
+        /// <param name="moduleSize">The size of each modules</param>
+        public static Modules CreateBlockModules(int moduleCount, int moduleSize)
+        {
+            return new Modules(Enumerable.Range(0, moduleCount).Select(mi => Enumerable.Range(mi * moduleSize, moduleSize).ToArray()).ToArray(), false, true);
+        }
+
+        /// <summary>
+        /// Creates Block Modules of the given sizes
+        /// </summary>
+        /// <param name="moduleSizes">The sizes of each modules</param>
+        public static Modules CreateBlockModules(IReadOnlyList<int> modulesSizes)
+        {
+            //var ass = modulesSizes.Aggregate((acc: 0, l: Enumerable.Empty<int>()), (a, s) => (a.acc + s, a.l.Concat(Enumerable.Range(a.acc, s))), a => a.l.ToArray());
+
+            int[][] assignments = new int[modulesSizes.Count][];
+
+            int t = 0;
+            for (int i = 0; i < modulesSizes.Count; i++)
+            {
+                assignments[i] = Enumerable.Range(t, modulesSizes[i]).ToArray();
+                t += modulesSizes[i];
+            }
+            
+            return new Modules(assignments, false, true);
+        }
+        
+        /// <summary>
+        /// Creates Modules from a string of symbols indicating the module assignments
+        /// </summary>
+        /// <param name="str">The string containing the per-element module assignments</param>
+        public static Modules CreateFromString(string str)
+        {
+            // aaabbca -> [0, 1, 2, 6], [3, 4], [5]
+            char[] symbols = str.Distinct().OrderBy(c => c).ToArray();
+            var assignments = symbols.Select(s => str.IndexWhere(c => c == s));
+            return new Modules(assignments);
+        }
+
+        /// <summary>
+        /// Renders a module string using the default symbol set
+        /// </summary>
+        public new string ToString()
+        {
+            return ToString(Enumerable.Range(0, ModuleCount).Select(i => (char)('a' + (i % 26))));
+        }
+        
+        /// <summary>
+        /// Renders a module string using the given symbol set
+        /// (Does not check that your symbols are distinct)
+        /// </summary>
+        /// <param name="symbols">A sequence of symbols to use for each module</param>
+        public string ToString(IEnumerable<char> symbols)
+        {
+            char[] c = new char[ElementCount];
+
+            int i = 0;
+            foreach (var s in symbols)
+            {
+                if (i >= ModuleCount)
+                    break;
+
+                foreach (var j in this[i])
+                {
+                    c[j] = s;
+                }
+
+                i++;
+            }
+
+            return new string(c);
+        }
+
+        /// <summary>
+        /// Expands the given values from per-module values to per-element values
+        /// </summary>
+        public T[] Expand<T>(IReadOnlyList<T> values)
+        {
+            if (values.Count != ModuleAssignments.Count)
+                throw new ArgumentException("Incorrect number of values provided; must be exactly one value per module", nameof(values));
+
+            var res = new T[ElementCount];
+
+            for (int mi = 0; mi < ModuleAssignments.Count; mi++)
+            {
+                foreach (var j in ModuleAssignments[mi])
+                {
+                    res[j] = values[mi];
+                }
+            }
+
+            return res;
+        }
+
+        /// <summary>
+        /// Gets an array where each entry points to a leader. The parent is the 'first' member of the module to which the element is assigned.
+        /// For example, aabbbc would produce [0, 0, 2, 2, 2, 5].
+        /// </summary>
+        public int[] GetDefaultLeaderMap()
+        {
+            int[] res = new int[ElementCount];
+
+            foreach (var module in ModuleAssignments)
+            {
+                var defaultParent = module[0];
+
+                foreach (var e in module)
+                {
+                    res[e] = defaultParent;
+                }
+            }
+
+            return res;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Population.cs b/M4MCode/M4M_MkI/M4M.Model/Population.cs
new file mode 100644
index 0000000..e89f047
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Population.cs
@@ -0,0 +1,1947 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using MathNet.Numerics.Random;
+using M4M.State;
+using System.IO;
+using M4M.Epistatics;
+using M4M.Modular;
+using MathNet.Numerics.Providers.LinearAlgebra;
+
+namespace M4M
+{
+    /// <summary>
+    /// Represents an individual with a phenotype that can be judged by a target.
+    /// </summary>
+    public interface IIndividual
+    {
+        Phenotype Phenotype { get; }
+        MultiMeasureJudgement Judge(JudgementRules jrules, ITarget target);
+        void WriteOut(System.IO.StreamWriter cw);
+    }
+
+    /// <summary>
+    /// Represents an individual within a population that can be mutated and combined to produce new individuals.
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public interface IIndividual<T> : IIndividual where T : IIndividual<T>
+    {
+        /// <summary>
+        /// Creates a new individual with mutations
+        /// </summary>
+        /// <param name="context">The Model Execution Context</param>
+        /// <param name="drules">Development Rules</param>
+        /// <param name="rrules">Reproduction Rules</param>
+        T Mutate(ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules);
+
+        /// <summary>
+        /// Mutates this individual into the given target individual
+        /// </summary>
+        /// <param name="target">The target Individual</param>
+        /// <param name="context">The Model Execution Context</param>
+        /// <param name="drules">Development Rules</param>
+        /// <param name="rrules">Reproduction Rules</param>
+        void MutateInto(T target, ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules);
+
+        /// <summary>
+        /// Combines this Individual with another
+        /// </summary>
+        /// <param name="other">The other Genome involves in the combination</param>
+        /// <param name="target">The target Genome</param>
+        /// <param name="context">Model Execution Context</param>
+        /// <param name="rules">Reproduction Rules</param>
+        void CombineAndMutateInto(T other, T target, ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules);
+
+        /// <summary>
+        /// Creates an exact copy of the individual, cloning any internal state
+        /// (Must not modify the state of the Random number generator)
+        /// </summary>
+        T Clone(ModelExecutionContext context);
+
+        /// <summary>
+        /// Inidicates that the individual should be recycled if possible.
+        /// </summary>
+        /// <param name="context">Model Execution Context</param>
+        void Recycle(ModelExecutionContext context);
+
+        /// <summary>
+        /// Indicates that the individual has escaped the population, and can no longer be recycled under any circumstances
+        /// </summary>
+        void Escaped();
+    }
+
+    [StateClass]
+    public class SimpleIndividual<G> : IIndividual<SimpleIndividual<G>> where G : IGenome<G>
+    {
+        [Obsolete]
+        protected SimpleIndividual()
+        { }
+
+        [SimpleStateProperty("Genome")]
+        public G Genome { get; private set; }
+
+        [SimpleStateProperty("Phenotype")]
+        public Phenotype Phenotype { get; private set; }
+
+        protected SimpleIndividual(G genome, Phenotype phenotype)
+        {
+            Genome = genome;
+            Phenotype = phenotype;
+        }
+
+        public SimpleIndividual<G> Clone(ModelExecutionContext context)
+        {
+            return new SimpleIndividual<G>(Genome.Clone(context), new Phenotype(Phenotype.Vector));
+        }
+
+        public static SimpleIndividual<G> Develop(G genome, ModelExecutionContext context, DevelopmentRules drules)
+        {
+            return new SimpleIndividual<G>(genome, genome.Develop(context, drules));
+        }
+
+        public SimpleIndividual<G> Mutate(ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            return Develop(Genome.Mutate(context, rrules), context, drules);
+        }
+
+        public void DevelopInplace(ModelExecutionContext context, DevelopmentRules drules)
+        {
+            Genome.DevelopInto(Phenotype, context, drules);
+        }
+
+        public void MutateInto(SimpleIndividual<G> target, ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            Genome.MutateInto(target.Genome, context, rrules);
+            target.DevelopInplace(context, drules);
+        }
+
+        public void MutateInplace(ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            Genome.MutateInplace(context, rrules);
+            DevelopInplace(context, drules);
+        }
+
+        public void CombineAndMutateInto(SimpleIndividual<G> other, SimpleIndividual<G> target, ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            Genome.CombineInto(other.Genome, target.Genome, context, rrules);
+            target.Genome.MutateInplace(context, rrules);
+            target.DevelopInplace(context, drules);
+        }
+
+        public MultiMeasureJudgement Judge(JudgementRules jrules, ITarget target)
+        {
+            return MultiMeasureJudgement.Judge(Genome, Phenotype, jrules, target);
+        }
+
+        public void Recycle(ModelExecutionContext context)
+        {
+            // nix
+        }
+
+        public void Escaped()
+        {
+            // nix
+        }
+
+        public void WriteOut(System.IO.StreamWriter cw)
+        {
+            cw.WriteLine($"Individual is SimpleIndividual of generic IGenome");
+
+            cw.WriteLine($"Size = {Genome.Size}");
+        }
+    }
+
+    [StateClass]
+    public class DenseIndividual : IIndividual<DenseIndividual>
+    {
+        [SimpleStateProperty("Genome")]
+        public DenseGenome Genome { get; private set; }
+
+        [SimpleStateProperty("Phenotype")]
+        public Phenotype Phenotype { get; private set; }
+
+        [SimpleStateProperty("Epigenetic")]
+        public bool Epigenetic { get; private set; }
+
+        /// <summary>
+        /// Indicates that this DenseIndividual knows that the DenseGenome can be recycled at this time
+        /// This should also be false on load, because we don't know who is loading it (don't persist)
+        /// Never set this to true, except in the constructor
+        /// </summary>
+        private bool CanRecycle = false;
+
+        [Obsolete]
+        protected DenseIndividual()
+        { }
+
+        private DenseIndividual(DenseGenome genome, Phenotype phenotype, bool epigenetic, bool canRecycle = false)
+        {
+            Genome = genome;
+            Phenotype = phenotype;
+            Epigenetic = epigenetic;
+
+            CanRecycle = canRecycle;
+        }
+
+        public DenseIndividual Clone(ModelExecutionContext context)
+        {
+            return new DenseIndividual(Genome.Clone(context), new Phenotype(Phenotype.Vector.Clone()), Epigenetic, true);
+        }
+
+        public static DenseIndividual Develop(DenseGenome genome, ModelExecutionContext context, DevelopmentRules drules, bool epigenetic)
+        {
+            // we have no idea where the Genome came from, so we can't recycle
+            return DevelopInternal(genome, context, drules, epigenetic, false);
+        }
+
+        private static DenseIndividual DevelopInternal(DenseGenome genome, ModelExecutionContext context, DevelopmentRules drules, bool epigenetic, bool canRecycle)
+        {
+            return new DenseIndividual(genome, genome.Develop(context, drules), epigenetic, canRecycle);
+        }
+
+        public void DevelopInplace(ModelExecutionContext context, DevelopmentRules drules)
+        {
+            Genome.DevelopInto(Phenotype, context, drules);
+        }
+
+        public DenseIndividual Mutate(ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            if (Epigenetic)
+            {
+                var temp = Genome.Clone(context); // clone me
+                temp.CopyOverInitialState(Phenotype.Vector); // copy phenotype into state
+
+                // because our Genome is cloned
+                //  - we know it doesn't violate the ability for us to be recycled (don't clear CanRecycle)
+                //  - for the moment the new guy can also be recycled (so tell DevelopInternal)
+                return DevelopInternal(temp.Mutate(context, rrules), context, drules, Epigenetic, true);
+            }
+            else
+            {
+                // simple
+                var newGenome = Genome.Mutate(context, rrules);
+                bool canRecycle = newGenome.TransMat != Genome.TransMat
+                    && newGenome.InitialState != Genome.InitialState; // detect whether an interdependency exists
+
+                CanRecycle &= canRecycle; // clear CanRecycle if we can no longer be recycled
+
+                return DevelopInternal(newGenome, context, drules, Epigenetic, canRecycle);
+            }
+        }
+
+        public void MutateInto(DenseIndividual target, ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            if (!target.CanRecycle)
+                throw new Exception("Cannot mutate into a potencially escaped target individual");
+
+            if (Epigenetic)
+            {
+                // create a new genome, with our phenotype as it's initial state
+                var temp = Genome.Clone(context); // clone me
+                temp.CopyOverInitialState(Phenotype.Vector); // copy phenotype into state
+
+                // mutate and develop
+                temp.MutateInto(target.Genome, context, rrules);
+                target.DevelopInplace(context, drules);
+            }
+            else
+            {
+                // mutate and develop
+                Genome.MutateInto(target.Genome, context, rrules);
+                target.DevelopInplace(context, drules);
+            }
+        }
+
+        public void MutateInplace(ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            if (!CanRecycle)
+                throw new Exception("Cannot mutate a potencially escaped target individual");
+
+            if (Epigenetic)
+                Genome.CopyOverInitialState(Phenotype.Vector);
+
+            Genome.MutateInplace(context, rrules);
+            DevelopInplace(context, drules);
+        }
+
+        public void CombineAndMutateInto(DenseIndividual other, DenseIndividual target, ModelExecutionContext context, DevelopmentRules drules, ReproductionRules rrules)
+        {
+            if (!target.CanRecycle)
+                throw new Exception("Cannot combine into a potencially escaped target individual");
+
+            if (Epigenetic)
+            {
+                var temp1 = Genome.Clone(context); // clone me
+                temp1.CopyOverInitialState(Phenotype.Vector);
+
+                var temp2 = other.Genome.Clone(context); // clone other
+                temp2.CopyOverInitialState(other.Phenotype.Vector);
+
+                Genome.CombineInto(temp1, temp2, context, rrules);
+                target.Genome.MutateInplace(context, rrules);
+                target.DevelopInplace(context, drules);
+            }
+            else
+            {
+                Genome.CombineInto(other.Genome, target.Genome, context, rrules);
+                target.Genome.MutateInplace(context, rrules);
+                target.DevelopInplace(context, drules);
+            }
+        }
+
+        public MultiMeasureJudgement Judge(JudgementRules jrules, ITarget target)
+        {
+            return MultiMeasureJudgement.Judge(Genome, Phenotype, jrules, target);
+        }
+
+        public void Recycle(ModelExecutionContext context)
+        {
+            if (CanRecycle)
+            {
+                Genome.Recycle(context);
+            }
+        }
+
+        public void Escaped()
+        {
+            CanRecycle = false;
+        }
+
+        public void WriteOut(System.IO.StreamWriter cw)
+        {
+            string d = "(Default)";
+
+            cw.WriteLine($"Individual is DenseIndividual:");
+            cw.WriteLine($"{nameof(Epigenetic)} = {Epigenetic}");
+
+            cw.WriteLine();
+            cw.WriteLine($"Genome Info:");
+            cw.WriteLine($"{nameof(Genome.Size)} = {Genome.Size}");
+            cw.WriteLine($"{nameof(Genome.InitialStateIndexOpenEntries)} = {(Genome.InitialStateIndexOpenEntries == null ? d : String.Join(" ", Genome.InitialStateIndexOpenEntries))}");
+            cw.WriteLine($"{nameof(Genome.TransMatIndexOpenEntries)} = {(Genome.TransMatIndexOpenEntries == null ? d : String.Join(" ", Genome.TransMatIndexOpenEntries))}");
+            cw.WriteLine($"{nameof(Genome.CustomInitialStateMutator)} = {Genome.CustomInitialStateMutator?.Name ?? d}");
+            cw.WriteLine($"{nameof(Genome.CustomTransMatMutator)} = {Genome.CustomTransMatMutator?.Name ?? d}");
+            cw.WriteLine($"{nameof(Genome.CustomInitialStateCombiner)} = {Genome.CustomInitialStateCombiner?.Name ?? d}");
+            cw.WriteLine($"{nameof(Genome.CustomTransMatCombiner)} = {Genome.CustomTransMatCombiner?.Name ?? d}");
+            cw.WriteLine($"{nameof(Genome.CustomDevelopmentStep)} = {Genome.CustomDevelopmentStep?.Name ?? d}");
+            cw.WriteLine($"{nameof(Genome.BiasVector)} non null = {Genome.BiasVector != null}");
+        }
+    }
+
+    public delegate T IndividualGenerator<T>(ModelExecutionContext context) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Represents an individual and its judgement.
+    /// </summary>
+    [StateClass]
+    public class IndividualJudgement<T> where T : IIndividual<T>
+    {
+        [Obsolete]
+        protected IndividualJudgement()
+        { }
+
+        public IndividualJudgement(T individual, MultiMeasureJudgement judgement)
+        {
+            Individual = individual;
+            Judgement = judgement;
+        }
+
+        [SimpleStateProperty("Individual")]
+        public T Individual { get; private set; }
+
+        [SimpleStateProperty("Judgement")]
+        public MultiMeasureJudgement Judgement { get; private set; }
+    }
+
+    /// <summary>
+    /// Providers a method to prepare a selector for the given type of individual.
+    /// </summary>
+    /// <typeparam name="T">The type of individual.</typeparam>
+    public interface ISelectorPreparer<T> where T : IIndividual<T>
+    {
+        string Name { get; }
+
+        /// <summary>
+        /// Prepares a selector from a <see cref="RandomSource"/> and collection of <see cref="IndividualJudgement{T}"/>, which can be used to sample individuals.
+        /// </summary>
+        ISelector<T> Prepare(IReadOnlyList<IndividualJudgement<T>> individualJudgements, RandomSource rand);
+    }
+
+    public interface IPopulationSpinner<T> where T : IIndividual<T>
+    {
+        string Name { get; }
+        string Description { get; }
+
+        /// <summary>
+        /// Spins the population for the given number of generations.
+        /// This method will call <see cref="ITarget.NextGeneration(RandomSource, JudgementRules)"/> as necessary, but will not call <see cref="ITarget.NextExposure(RandomSource, JudgementRules, int, ref ExposureInformation)"/>.
+        /// Parameters generally come directly from a <see cref="PopulationExperiment{T}"/> and <see cref="ExperimentConfiguration"/>.
+        /// </summary>
+        IReadOnlyList<IndividualJudgement<T>> SpinPopulation(Population<T> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<T> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback, int eliteCount, bool hillclimberMode);
+    }
+
+    /// <summary>
+    /// Just wraps a call to <see cref="Population{T}.SpinPopulation(ModelExecutionContext, ReproductionRules, DevelopmentRules, JudgementRules, ITarget, ISelectorPreparer{T}, int, Action{int, IReadOnlyList{IndividualJudgement{T}}}, int, bool)"/>.
+    /// </summary>
+    [StateClass]
+    public class DefaultPopulationSpinner<T> : IPopulationSpinner<T> where T : IIndividual<T>
+    {
+        public static readonly DefaultPopulationSpinner<T> Instance = new DefaultPopulationSpinner<T>();
+
+        public string Name => "DefaultPopulationSpinner";
+        public string Description => "DefaultPopulationSpinner";
+
+        public IReadOnlyList<IndividualJudgement<T>> SpinPopulation(Population<T> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<T> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            return population.SpinPopulation(context, rrules, drules, jrules, target, selectorPreparer, generations, judgementFeedback, eliteCount, hillclimberMode);
+        }
+    }
+
+    [StateClass]
+    public class PairedPopulationSpinner<T> : IPopulationSpinner<T> where T : IIndividual<T>
+    {
+        public static readonly PairedPopulationSpinner<T> Instance = new PairedPopulationSpinner<T>();
+
+        public string Name => "PairedPopulationSpinner";
+        public string Description => "PairedPopulationSpinner";
+
+        public IReadOnlyList<IndividualJudgement<T>> SpinPopulation(Population<T> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<T> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            return population.SpinPopulationPaired(context, rrules, drules, jrules, target, generations, judgementFeedback);
+        }
+    }
+
+    [StateClass]
+    public class RecombinantTriosPopulationSpinner<T> : IPopulationSpinner<T> where T : IIndividual<T>
+    {
+        public static readonly RecombinantTriosPopulationSpinner<T> Instance = new RecombinantTriosPopulationSpinner<T>();
+
+        public string Name => "RecombinantTriosPopulationSpinner";
+        public string Description => "RecombinantTriosPopulationSpinner";
+
+        public IReadOnlyList<IndividualJudgement<T>> SpinPopulation(Population<T> population, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<T> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            return population.SpinPopulationRecombinantTrios(context, rrules, drules, jrules, target, generations, judgementFeedback);
+        }
+    }
+
+    [StateClass]
+    public class Population<T> where T : IIndividual<T>
+    {
+        /// <summary>
+        /// Controls the number of threads that may be used when processing a population. It hasn't been tested for months, so don't touch it.
+        /// Note: <see cref="ModelExecutionContext"/> is NOT thread safe, so all threads will be given their own context.
+        /// </summary>
+        public int PopulationParallelism { get; set; } = 1;
+
+        [SimpleStateProperty("Individuals")]
+        private List<T> Individuals { get; set; } = new List<T>();
+
+        public int Count => Individuals.Count;
+
+        /// <summary>
+        /// Initializes a population of random individuals
+        /// </summary>
+        public Population(ModelExecutionContext context, IndividualGenerator<T> randomIndividualGenerator, int count)
+        {
+            for (int i = 0; i < count; i++)
+            {
+                Individuals.Add(randomIndividualGenerator(context));
+            }
+        }
+
+        /// <summary>
+        /// Initializes a population of identifcal individuals (reuses same reference, no attempt to clone)
+        /// </summary>
+        public Population(T template, int count)
+        {
+            for (int i = 0; i < count; i++)
+            {
+                Individuals.Add(template);
+            }
+        }
+
+        /// <summary>
+        /// Initializes an empty population
+        /// </summary>
+        public Population()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a population with the given individuals
+        /// </summary>
+        public Population(IEnumerable<T> individuals)
+        {
+            Individuals.AddRange(individuals);
+        }
+
+        /// <summary>
+        /// Creates a new population, containing the same individuals
+        /// </summary>
+        /// <returns></returns>
+        public Population<T> Clone()
+        {
+            foreach (var i in Individuals)
+                i.Escaped();
+
+            return new Population<T>(Individuals);
+        }
+
+        /// <summary>
+        /// Creates a new population, containing clones of the current individuals
+        /// </summary>
+        public Population<T> Clone(ModelExecutionContext context)
+        {
+            bool doRenable = context.TryDisableRandom(out var rand);
+
+            var newPop = new Population<T>(Individuals.Select(i => i.Clone(context)));
+            
+            if (doRenable)
+                context.EnableRandom(rand);
+            
+            return newPop;
+        }
+
+        public void Clear()
+        {
+            Individuals.Clear();
+        }
+
+        private void RecycleAndClear(ModelExecutionContext context)
+        {
+            foreach (var i in Individuals)
+                i.Recycle(context);
+
+            Clear();
+        }
+
+        public T[] ExtractAll()
+        {
+            var res = Individuals.ToArray();
+
+            foreach (var i in res)
+                i.Escaped();
+
+            Clear();
+            return res;
+        }
+
+        public T[] PeekAll()
+        {
+            var res = Individuals.ToArray();
+            foreach (var i in res)
+                i.Escaped();
+
+            return res;
+        }
+
+        public IndividualJudgement<T>[] PeekAndJudgeAll(JudgementRules jrules, ITarget target)
+        {
+            var peeked = PeekAll();
+
+            return JudgeAll(peeked, jrules, target, false);
+        }
+
+        public IndividualJudgement<T>[] ExtractAndJudgeAll(JudgementRules jrules, ITarget target)
+        {
+            var extracted = ExtractAll();
+
+            return JudgeAll(extracted, jrules, target, false);
+        }
+
+        private IndividualJudgement<T>[] ExtractAndJudgeAllNoEscape(JudgementRules jrules, ITarget target)
+        {
+            var extracted = ExtractAll(); // does actually escape... but this whole design is a mess, and it's not worth fixing it now
+
+            return JudgeAll(extracted, jrules, target, true);
+        }
+
+        private static IndividualJudgement<T>[] JudgeAll(T[] individuals, JudgementRules jrules, ITarget target, bool noEscape)
+        {
+            IndividualJudgement<T>[] res = new IndividualJudgement<T>[individuals.Length];
+
+            for (int i = 0; i < individuals.Length; i++)
+            {
+                T t = individuals[i];
+                if (!noEscape)
+                    t.Escaped();
+
+                res[i] = new IndividualJudgement<T>(t, t.Judge(jrules, target));
+            }
+
+            return res;
+
+            // NOTE: reckon the Linq was adding ~20% overhead
+            //return individuals.Select(t => new IndividualJudgement<T>(t, t.Judge(jrules, target))).ToArray();
+        }
+
+        /// <summary>
+        /// Experimental
+        /// Spins the population (repeatedly cycles it) with paired displacement replacement competition selection, whatever it should be calle
+        /// </summary>
+        public IReadOnlyList<IndividualJudgement<T>> SpinPopulationPaired(ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback)
+        {
+            if (Count < 2)
+                throw new Exception("Misuse of SpinPopulationPaired; population must have atleast 2 individuals");
+
+            // this doesn't actually recycle anything...
+            T[] arr = ExtractAll().Select(i => { var r = i.Clone(context); i.Recycle(context); return r;}).ToArray(); // start by cloning them (assumes many generations)
+
+            var rand = context.Rand;
+
+            IReadOnlyList<IndividualJudgement<T>> judgements = null;
+
+            for (int generation = 0; generation < generations; generation++)
+            {
+                target.NextGeneration(rand, jrules);
+
+                if (generation == 0 && judgementFeedback != null)
+                {
+                    // cloning here shouldn't add much overhead over creating new things directly, and is more efficient if there is no IndividualJudgement (usual case)
+                    judgements = JudgeAll(arr.Select(i => i.Clone(context)).ToArray(), jrules, target, false);
+                    judgementFeedback.Invoke(0, judgements);
+                }
+
+                bool lastGeneration = generation == generations - 1;
+
+                int a = rand.Next(arr.Length);
+                int b = rand.Next(arr.Length - 1);
+                if (b >= a)
+                    b++;
+
+                var aj = arr[a].Judge(jrules, target);
+                var bj = arr[b].Judge(jrules, target);
+
+                if (bj.CombinedFitness > aj.CombinedFitness)
+                { // arbitrary tie-breaking
+                    var t = a;
+                    a = b;
+                    b = t;
+
+                    var tj = aj;
+                    aj = bj;
+                    bj = tj;
+                }
+
+                // replace b with a mutant of a
+                arr[a].MutateInto(arr[b], context, drules, rrules);
+
+                if (judgementFeedback != null || lastGeneration)
+                {
+                    // cloning here shouldn't add much overhead over creating new things directly, and is more efficient if there is no IndividualJudgement (usual case)
+                    judgements = JudgeAll(arr.Select(i => i.Clone(context)).ToArray(), jrules, target, false);
+                    judgementFeedback?.Invoke(generation + 1, judgements);
+                }
+            }
+
+            Individuals.AddRange(arr);
+            return judgements;
+        }
+
+        /// <summary>
+        /// Experimental
+        /// Spins the population (repeatedly cycles it) with trio competition and recombination
+        /// Foreach Generation
+        ///   Select 3 individuals at random
+        ///   Kill the weakest
+        ///   Cross the other 2 (recombine) with mutation to produce a replacement
+        /// </summary>
+        public IReadOnlyList<IndividualJudgement<T>> SpinPopulationRecombinantTrios(ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback)
+        {
+            if (Count < 3)
+                throw new Exception("Misuse of SpinPopulationRecombinantTrios; population must have atleast 3 individuals");
+
+            // this doesn't actually recycle anything
+            T[] arr = ExtractAll().Select(i => { var r = i.Clone(context); i.Recycle(context); return r; }).ToArray(); // start by cloning them (assumes many generations)
+
+            var rand = context.Rand;
+
+            IReadOnlyList<IndividualJudgement<T>> judgements = null;
+
+            for (int generation = 0; generation < generations; generation++)
+            {
+                target.NextGeneration(rand, jrules);
+
+                if (generation == 0 && judgementFeedback != null)
+                {
+                    // cloning here shouldn't add much overhead over creating new things directly, and is more efficient if there is no IndividualJudgement (usual case)
+                    judgements = JudgeAll(arr.Select(i => i.Clone(context)).ToArray(), jrules, target, false);
+                    judgementFeedback.Invoke(0, judgements);
+                }
+
+                bool lastGeneration = generation == generations - 1;
+
+                int a = rand.Next(arr.Length);
+                int b = rand.Next(arr.Length - 1);
+                if (b >= a)
+                    b++;
+                int c = rand.Next(arr.Length - 2);
+                if (c >= a)
+                    c++;
+                if (c >= b)
+                    c++;
+
+                var aj = arr[a].Judge(jrules, target);
+                var bj = arr[b].Judge(jrules, target);
+                var cj = arr[c].Judge(jrules, target);
+
+                if (bj.CombinedFitness > aj.CombinedFitness)
+                { // arbitrary tie-breaking
+                    var t = a;
+                    a = b;
+                    b = t;
+
+                    var tj = aj;
+                    aj = bj;
+                    bj = tj;
+                }
+
+                if (cj.CombinedFitness > bj.CombinedFitness)
+                { // arbitrary tie-breaking
+                    var t = b;
+                    b = c;
+                    c = t;
+
+                    var tj = bj;
+                    bj = cj;
+                    cj = tj;
+                }
+                
+                // replace c with a recombined mutant of a and b
+                arr[a].CombineAndMutateInto(arr[b], arr[c], context, drules, rrules);
+
+                if (judgementFeedback != null || lastGeneration)
+                {
+                    // cloning here shouldn't add much overhead over creating new things directly, and is more efficient if there is no IndividualJudgement (usual case)
+                    judgements = JudgeAll(arr.Select(i => i.Clone(context)).ToArray(), jrules, target, false);
+                    judgementFeedback?.Invoke(generation + 1, judgements);
+                }
+            }
+
+            Individuals.AddRange(arr);
+            return judgements;
+        }
+
+        /// <summary>
+        /// Spins the population (repeatedly cycles it)
+        /// Using this method enables extra recycling
+        /// </summary>
+        public IReadOnlyList<IndividualJudgement<T>> SpinPopulation(ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target, ISelectorPreparer<T> selectorPreparer, int generations, Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback, int eliteCount, bool hillclimberMode)
+        {
+            bool noEscape = judgementFeedback == null;
+
+            // hill-climber spin fast path (good if generations are high and bmutate rate is also high)
+            bool hcSpinFastPath1 = noEscape && hillclimberMode && Count == 1 && eliteCount == 1;
+            if (hcSpinFastPath1)
+            {
+                return new[] { SpinHillclimb1FastPathNoEscape(generations, context, jrules, target, rrules, drules) };
+            }
+
+            // (no fast path)
+            var rand = context.Rand;
+            IReadOnlyList<IndividualJudgement<T>> judgements = null;
+
+            for (int generation = 0; generation < generations; generation++)
+            {
+                target.NextGeneration(rand, jrules);
+
+                judgements = CyclePopulationInternal(context, jrules, target, rrules, drules, selectorPreparer, eliteCount, hillclimberMode, noEscape, generation == 0 ? judgementFeedback : null);
+                judgementFeedback?.Invoke(generation + 1, judgements);
+
+                if (noEscape)
+                {
+                    bool isLast = generation == generations - 1;
+
+                    if (isLast)
+                    {
+                        // CyclePopulationInternal didn't escape them (because they havn't escaped yet), so we need to escape them now
+                        foreach (var ij in judgements)
+                            ij.Individual.Escaped();
+                    }
+                    else
+                    {
+                        // we can recycle them!
+                        foreach (var ij in judgements)
+                            ij.Individual.Recycle(context);
+                    }
+                }
+            }
+
+            return judgements;
+        }
+
+        /// <summary>
+        /// Judges everyone, and then produces mutatant offspring by weighted selection
+        /// Returns the previous members if the population, and their judgements
+        /// </summary>
+        public IReadOnlyList<IndividualJudgement<T>> CyclePopulation(ModelExecutionContext context, JudgementRules jrules, ITarget target, ReproductionRules rrules, DevelopmentRules drules, ISelectorPreparer<T> selectorPreparer, int eliteCount, bool hillclimberMode, Action<int, IReadOnlyList<IndividualJudgement<T>>> genZeroJudgementFeedback)
+        {
+            return CyclePopulationInternal(context, jrules, target, rrules, drules, selectorPreparer, eliteCount, hillclimberMode, false, genZeroJudgementFeedback);
+        }
+
+        /// <summary>
+        /// Judges everyone, and then produces mutatant offspring by weighted selection
+        /// Returns the previous members of the population, and their judgements
+        /// </summary>
+        private IReadOnlyList<IndividualJudgement<T>> CyclePopulationInternal(ModelExecutionContext context, JudgementRules jrules, ITarget target, ReproductionRules rrules, DevelopmentRules drules, ISelectorPreparer<T> selectorPreparer, int eliteCount, bool hillclimberMode, bool noEscape, Action<int, IReadOnlyList<IndividualJudgement<T>>> genZeroJudgementFeedback)
+        {
+            int count = Count;
+            var judgements = noEscape && (genZeroJudgementFeedback == null)
+                ? ExtractAndJudgeAllNoEscape(jrules, target)
+                : ExtractAndJudgeAll(jrules, target);
+
+            // this should only be non-null in the first generation
+            genZeroJudgementFeedback?.Invoke(0, judgements);
+
+            if (hillclimberMode)
+            {
+                if (count == 1 && eliteCount == 1)
+                {
+                    // fast path
+                    Hillclimb1FastPath(context, jrules, target, rrules, drules, judgements[0]);
+                }
+                else
+                {
+                    var sel = selectorPreparer.Prepare(judgements, context.Rand);
+                    var elites = judgements.OrderByDescending(j => j.Judgement.CombinedFitness).Take(eliteCount);
+                    HillclimbFrom(sel, count, context, jrules, target, rrules, drules, elites);
+                }
+            }
+            else
+            {
+                var sel = selectorPreparer.Prepare(judgements, context.Rand);
+                var elites = judgements.OrderByDescending(j => j.Judgement.CombinedFitness).Take(eliteCount);
+                PopulateFrom(sel, count - eliteCount, context, rrules, drules, elites.Select(j => j.Individual));
+            }
+
+            return judgements;
+        }
+
+        private void PopulateFrom(ISelector<T> selector, int count, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, IEnumerable<T> elites)
+        {
+            for (int i = 0; i < count; i++)
+            {
+                Individuals.Add(selector.SelectRandom().Mutate(context, drules, rrules));
+            }
+
+            foreach (var elite in elites)
+                Individuals.Add(elite);
+        }
+
+        /// <summary>
+        /// Runs an efficient Hill-Climbing (assuming large number of generations)
+        /// </summary>
+        private IndividualJudgement<T> SpinHillclimb1FastPathNoEscape(int generations, ModelExecutionContext context, JudgementRules jrules, ITarget target, ReproductionRules rrules, DevelopmentRules drules)
+        {
+            if (Count != 1)
+                throw new Exception("Misuse of SpinHillclimb1FastPathNoEscape; population should have exactly one individual");
+
+            var rand = context.DisableRandom(); // don't try: if this fails, we require random later on
+            var currentIndividual = ExtractAll()[0].Clone(context); // start with a clone: that means we know it hasn't escaped (contract says it won't change the context state)
+            var candidateIndividual = currentIndividual.Clone(context);
+            context.EnableRandom(rand);
+
+            IndividualJudgement<T> result = null;
+
+            for (int generation = 0; generation < generations; generation++)
+            {
+                bool lastGeneration = generation == generations - 1;
+
+                target.NextGeneration(rand, jrules);
+
+                var currentJudgement = currentIndividual.Judge(jrules, target);
+
+                if (lastGeneration)
+                {
+                    currentIndividual.Escaped();
+                    result = new IndividualJudgement<T>(currentIndividual, currentJudgement);
+                }
+
+                currentIndividual.MutateInto(candidateIndividual, context, drules, rrules);
+                var candidateJudgement = candidateIndividual.Judge(jrules, target);
+
+                if (candidateJudgement.CombinedFitness > currentJudgement.CombinedFitness)
+                {
+                    // keep new (offspring)
+
+                    var t = currentIndividual;
+                    currentIndividual = candidateIndividual;
+                    candidateIndividual = t;
+
+                    currentJudgement = candidateJudgement;
+                }
+            }
+
+            // don't need this guy any more
+            candidateIndividual.Recycle(context);
+
+            // keep current
+            Individuals.Add(currentIndividual);
+
+            // return current judgement
+            return result;
+        }
+
+        public void Introduce(T individual)
+        {
+            Individuals.Add(individual);
+        }
+
+        public void IntroduceMany(IEnumerable<T> individuals)
+        {
+            Individuals.AddRange(individuals);
+        }
+
+        private void Hillclimb1FastPath(ModelExecutionContext context, JudgementRules jrules, ITarget target, ReproductionRules rrules, DevelopmentRules drules, IndividualJudgement<T> current)
+        {
+            T next = current.Individual.Mutate(context, drules, rrules);
+            var j = next.Judge(jrules, target);
+
+            if (j.CombinedFitness > current.Judgement.CombinedFitness)
+            {
+                // keep new (offspring)
+                Individuals.Add(next);
+            }
+            else
+            {
+                // keep old (elite)
+                Individuals.Add(current.Individual);
+
+                // recycle new
+                next.Recycle(context);
+            }
+        }
+
+        private void HillclimbFrom(ISelector<T> selector, int count, ModelExecutionContext context, JudgementRules jrules, ITarget target, ReproductionRules rrules, DevelopmentRules drules, IEnumerable<IndividualJudgement<T>> elites)
+        {
+            // put the elites in a list
+            List<IndividualJudgement<T>> provisionals = new List<IndividualJudgement<T>>(elites);
+
+            if (count == 1 && provisionals.Count == 1)
+            {
+                // fast path
+                Hillclimb1FastPath(context, jrules, target, rrules, drules, provisionals[0]);
+            }
+            else
+            {
+                int threads = PopulationParallelism;
+
+                if (threads == 1)
+                {
+                    // create lots of offspring, and add them to the list
+                    for (int i = 0; i < count; i++)
+                    {
+                        T next = selector.SelectRandom().Mutate(context, drules, rrules);
+                        var j = next.Judge(jrules, target);
+                        provisionals.Add(new IndividualJudgement<T>(next, j));
+                    }
+                }
+                else
+                {
+                    // NOTE: really need to pull CyclePopulation out into an interface, make these implementations a struct, and pass them as a generic parameter to RunEpochs (OSLT)
+                    // for the time being, we have to make new random sources every time, which is really bad (really need a type with a 'CyclePopulation' method which holds randoms internally (can generate them once)
+                    ModelExecutionContext[] contexts = new ModelExecutionContext[threads];
+                    for (int i = 0; i < threads; i++)
+                        contexts[i] = new ModelExecutionContext(new MathNet.Numerics.Random.MersenneTwister(context.Rand.Next(0, int.MaxValue), false));
+
+                    // trivial lock based implementation to start with
+                    // max savings of ~10% with 4 threads with population of 16
+                    // conclusion: not worth the effort at this time
+                    T[] parents = new T[count];
+                    IndividualJudgement<T>[] cache = new IndividualJudgement<T>[count];
+
+                    for (int i = 0; i < count; i++)
+                        parents[i] = selector.SelectRandom();
+
+                    System.Threading.CountdownEvent signaller = new System.Threading.CountdownEvent(threads);
+
+                    Action<int> work = p =>
+                    {
+                        for (int i = p; i < count; i += threads) // probably not what the cache wants... but unless the population is huge it won't make a difference
+                        {
+                            T next = parents[i].Mutate(contexts[p], drules, rrules);
+                            var j = next.Judge(jrules, target);
+                            cache[i] = new IndividualJudgement<T>(next, j);
+                        }
+
+                        signaller.Signal();
+                    };
+
+                    for (int p = 0; p < threads; p++)
+                    {
+                        if (p == threads - 1)
+                        {
+                            work(p);
+                        }
+                        else
+                        {
+                            work.BeginInvoke(p, null, null);
+                        }
+                    }
+
+                    signaller.Wait();
+
+                    provisionals.AddRange(cache);
+                }
+
+                // add only the best to the population
+
+                // no LINQ (and no allocations)
+                provisionals.Sort(Descending);
+                for (int i = 0; i < count; i++)
+                {
+                    Individuals.Add(provisionals[i].Individual);
+                }
+
+                for (int i = count; i < provisionals.Count; i++)
+                {
+                    provisionals[i].Individual.Recycle(context);
+                }
+
+                // LINQ (various allocations)
+                //foreach (var ij in provisionals.OrderByDescending(j => j.Judgement.CombinedFitness).Take(count))
+                //{
+                //    Individuals.Add(ij.Individual);
+                //}
+            }
+        }
+
+        private readonly Comparison<IndividualJudgement<T>> Descending = (a, b) => b.Judgement.CombinedFitness.CompareTo(a.Judgement.CombinedFitness);
+
+        /// <summary>
+        /// Returns a member of the population at random, without remove it from the population
+        /// Do NOT hold onto the individual, it is liable to be corrupted
+        /// </summary>
+        public T PeekRandom(RandomSource rand)
+        {
+            var sample = Individuals[rand.Next(Count)];
+            sample.Escaped();
+            return sample;
+        }
+
+        /// <summary>
+        /// Performs the given operation on each individual in the population without removing them from the population.
+        /// </summary>
+        /// <param name="processor"></param>
+        public void Process(Func<T, T> processor)
+        {
+            for (int i = 0; i < Individuals.Count; i++)
+                Individuals[i] = processor(Individuals[i]);
+        }
+    }
+
+    [StateClass]
+    public class PopulationExperimentConfig<T> where T : IIndividual<T>
+    {
+        [Obsolete]
+        protected PopulationExperimentConfig()
+        { }
+
+        public PopulationExperimentConfig(ExperimentConfiguration experimentConfiguration, ISelectorPreparer<T> selectorPreparer, int eliteCount, bool hillclimberMode, IPopulationEndTargetOperation<T> populationEndTargetOperation, IPopulationResetOperation<T> populationResetOperation, IPopulationSpinner<T> customPopulationSpinner)
+        {
+            ExperimentConfiguration = experimentConfiguration;
+            SelectorPreparer = selectorPreparer;
+            EliteCount = eliteCount;
+            HillclimberMode = hillclimberMode;
+            PopulationEndTargetOperation = populationEndTargetOperation;
+            PopulationResetOperation = populationResetOperation;
+            CustomPopulationSpinner = customPopulationSpinner;
+        }
+
+        /// <summary>
+        /// The base configuration for the experiment
+        /// </summary>
+        [SimpleStateProperty("ExperimentConfiguration")]
+        public ExperimentConfiguration ExperimentConfiguration { get; private set; }
+
+        /// <summary>
+        /// The selector preparer
+        /// </summary>
+        [SimpleStateProperty("SelectorPreparer")]
+        public ISelectorPreparer<T> SelectorPreparer { get; private set; }
+
+        /// <summary>
+        /// The number of elites to keep each generation
+        /// </summary>
+        [SimpleStateProperty("EliteCount")]
+        public int EliteCount { get; private set; }
+
+        /// <summary>
+        /// Whether to employ elites in a hill-climber fashion
+        /// </summary>
+        [SimpleStateProperty("HillclimberMode")]
+        public bool HillclimberMode { get; private set; }
+        
+        /// <summary>
+        /// The Custom Population Spinner
+        /// (null means use the default)
+        /// </summary>
+        [SimpleStateProperty("CustomPopulationSpinner")]
+        public IPopulationSpinner<T> CustomPopulationSpinner { get; private set; }
+
+        /// <summary>
+        /// The Population end-of-target operation
+        /// </summary>
+        [SimpleStateProperty("PopulationEndTargetOperation")]
+        public IPopulationEndTargetOperation<T> PopulationEndTargetOperation { get; private set; }
+
+        /// <summary>
+        /// The Population reset operation
+        /// </summary>
+        [SimpleStateProperty("PopulationResetOperation")]
+        public IPopulationResetOperation<T> PopulationResetOperation { get; private set; }
+
+        public void WriteOut(System.IO.StreamWriter cw)
+        {
+            cw.WriteLine();
+            cw.WriteLine();
+            cw.WriteLine($"Population+SelectorPreparer: {SelectorPreparer.Name}");
+            cw.WriteLine($"Population+EliteCount: {EliteCount}");
+            cw.WriteLine($"Population+HillclimberMode: {HillclimberMode}");
+            cw.WriteLine($"Population+CustomPopulationSpinner: {CustomPopulationSpinner?.Description ?? "(null)"}");
+            cw.WriteLine($"Population+PopulationEndTargetOperation: {PopulationEndTargetOperation?.Name ?? "(null)"}");
+            cw.WriteLine($"Population+PopulationResetOperation: {PopulationResetOperation?.Name ?? "(null)"}");
+
+            ExperimentConfiguration.WriteOut(cw);
+        }
+    }
+
+    /// <summary>
+    /// Signals that a new round is about to begin with a new target
+    /// </summary>
+    /// <param name="stuff">Stuff</param>
+    /// <param name="population">The population (transient)</param>
+    /// <param name="target">The new target</param>
+    /// <param name="epoch">The current epoch (1-indexed)</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    /// <param name="oldPopulationJudgements">The population from which the current population was produced, along with the judgement of each individual</param>
+    public delegate void EndPopulationTargetDelegate<T>(FileStuff stuff, Population<T> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<T>> oldPopulationJudgements) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Signals that a round has just ended
+    /// </summary>
+    /// <param name="stuff">Stuff</param>
+    /// <param name="population">The population (transient)</param>
+    /// <param name="target">The current (about to be swapped out) target</param>
+    /// <param name="epoch">The current epoch (1-indexed)</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void StartPopulationTargetDelegate<T>(FileStuff stuff, Population<T> population, ITarget target, int epoch, long generationCount) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Signals that an epoch has just ended
+    /// </summary>
+    /// <param name="stuff">Stuff</param>
+    /// <param name="population">The population (transient)</param>
+    /// <param name="epochCount">The number of epochs run</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    /// <param name="oldPopulationJudgements">The population from which the current population was produced, along with the judgement of each individual</param>
+    public delegate void EndPopulationEpochDelegate<T>(FileStuff stuff, Population<T> population, int epochCount, long generationCount, IReadOnlyList<IndividualJudgement<T>> oldPopulationJudgements) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Signals that an experiment has just finished
+    /// </summary>
+    /// <param name="stuff">Stuff</param>
+    /// <param name="population">The population (transient)</param>
+    /// <param name="epochCount">The number of epochs run</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void PopulationFinishedDelegate<T>(FileStuff stuff, Population<T> population, int epochCount, long generationCount) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Signals that an experiment has just started
+    /// </summary>
+    /// <param name="stuff">Stuff</param>
+    /// <param name="population">The population (transient)</param>
+    public delegate void PopulationStartedDelegate<T>(FileStuff stuff, Population<T> population) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Signals the end of the population judgement for the given generation
+    /// </summary>
+    /// <param name="stuff">Stuff</param>
+    /// <param name="populationJudgements">The population judgements</param>
+    /// <param name="generationCount">The total number of generations seen</param>
+    public delegate void PopulationJudgementFeedbackDelegate<T>(FileStuff stuff, IReadOnlyList<IndividualJudgement<T>> populationJudgements, int epochCount, long generationCount, ITarget target) where T : IIndividual<T>;
+
+    /// <summary>
+    /// Something that configures a PopulationExperimentFeedback
+    /// </summary>
+    /// <param name="populationExperimentFeedback">The PopulationExperimentFeedback</param>
+    public delegate void PopulationExperimentFeedbackConfigurator<T>(PopulationExperimentFeedback<T> populationExperimentFeedback) where T : IIndividual<T>;
+
+    public class PopulationExperimentFeedback<T> where T : IIndividual<T>
+    {
+        public Event<EndPopulationTargetDelegate<T>> EndTarget => _endTarget;
+        private Event<EndPopulationTargetDelegate<T>> _endTarget = new Event<EndPopulationTargetDelegate<T>>();
+
+        public Event<StartPopulationTargetDelegate<T>> StartTarget => _startTarget;
+        private Event<StartPopulationTargetDelegate<T>> _startTarget = new Event<StartPopulationTargetDelegate<T>>();
+
+        public Event<EndPopulationEpochDelegate<T>> EndEpoch => _endEpoch;
+        private Event<EndPopulationEpochDelegate<T>> _endEpoch = new Event<EndPopulationEpochDelegate<T>>();
+
+        public Event<PopulationStartedDelegate<T>> Started => _started;
+        private Event<PopulationStartedDelegate<T>> _started = new Event<PopulationStartedDelegate<T>>();
+
+        public Event<PopulationFinishedDelegate<T>> Finished => _finished;
+        private Event<PopulationFinishedDelegate<T>> _finished = new Event<PopulationFinishedDelegate<T>>();
+
+        public Event<PopulationJudgementFeedbackDelegate<T>> Judged => _judged;
+        private Event<PopulationJudgementFeedbackDelegate<T>> _judged = new Event<PopulationJudgementFeedbackDelegate<T>>();
+    }
+
+    public interface IPopulationEndTargetOperation<T> where T : IIndividual<T>
+    {
+        string Name { get; }
+        void Process(ModelExecutionContext context, Population<T> population, DevelopmentRules drules, JudgementRules jrules, ITarget target);
+    }
+
+    public interface IPopulationResetOperation<T> where T : IIndividual<T>
+    {
+        string Name { get; }
+        void Reset(ModelExecutionContext context, Population<T> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target);
+    }
+
+    [StateClass]
+    public class NeutralMutationPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        [Obsolete]
+        protected NeutralMutationPopulationResetOperation()
+        {
+        }
+
+        public NeutralMutationPopulationResetOperation(int neutralMutationCount)
+        {
+            NeutralMutationCount = neutralMutationCount;
+        }
+
+        public string Name => "NeutralMutation" + NeutralMutationCount;
+
+        [SimpleStateProperty("NeutralMutationCount")]
+        public int NeutralMutationCount { get; private set; }
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            DenseIndividual neutralise(DenseIndividual individual)
+            {
+                individual = individual.Clone(context);
+
+                for (int i = 0; i < NeutralMutationCount; i++)
+                    individual.Genome.MutateInplace(context, rrules);
+
+                individual.DevelopInplace(context, drules);
+
+                return individual;
+            }
+
+            population.Process(neutralise);
+        }
+    }
+
+    [StateClass]
+    public class RandomIndependantPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "RandomIndependant";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(di.Genome.Size, bleh => gResetRange.Sample(context.Rand))), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class RandomBinaryIndependantPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "RandomBinaryIndependant";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(di.Genome.Size, bleh => context.Rand.NextBoolean() ? gResetRange.Min : gResetRange.Max)), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class RandomModulesPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        [Obsolete]
+        protected RandomModulesPopulationResetOperation()
+        {
+        }
+
+        public RandomModulesPopulationResetOperation(Modules modules, NoiseType noiseType, double mag)
+        {
+            Modules = modules ?? throw new ArgumentNullException(nameof(modules));
+            NoiseType = noiseType;
+            Magnitude = mag;
+        }
+
+        public string Name => $"RandomModules (NoiseType: {NoiseType}, Magnitude: {Magnitude}, Modules: {Modules.ToString()})";
+
+        [SimpleStateProperty("Modules")]
+        public Modules Modules { get; private set; }
+
+        [SimpleStateProperty("NoiseType")]
+        public NoiseType NoiseType { get; private set; }
+
+        [SimpleStateProperty("Magnitude")]
+        public double Magnitude { get; private set; }
+
+        public MathNet.Numerics.LinearAlgebra.Vector<double> Sample(RandomSource rand)
+        {
+            var v = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(Modules.ElementCount);
+
+            foreach (var m in Modules.ModuleAssignments)
+            {
+                var r = Misc.NextNoise(rand, Magnitude, NoiseType);
+
+                foreach (var i in m)
+                {
+                    v[i] = r;
+                }
+            }
+
+            return v;
+        }
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: Sample(context.Rand)), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class MatchTargetVectorPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "MatchTargetVector";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: ((VectorTarget)target.Unwrap()).Vector), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class MatchTargetVectorPerfectPhenotypePopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "MatchTargetVectorPerfectPhenotype";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: ((VectorTarget)target.Unwrap()).PreparePerfectP()), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class MatchTargetForcingPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public MatchTargetForcingPopulationResetOperation()
+        {
+        }
+
+        public string Name => "MatchTargetForcing";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: ((CorrelationMatrixTarget)target.Unwrap()).ForcingVector.Clone()), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class AdditiveNoisePopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        [Obsolete]
+        protected AdditiveNoisePopulationResetOperation()
+        {
+        }
+
+        public AdditiveNoisePopulationResetOperation(NoiseType noiseType, double noiseMagnitude, bool clamp)
+        {
+            NoiseType = noiseType;
+            NoiseMagnitude = noiseMagnitude;
+            Clamp = clamp;
+        }
+
+        public string Name => "AdditiveNoise" + NoiseType + NoiseMagnitude + (Clamp ? "Clamp" : "");
+
+        [SimpleStateProperty("NoiseType")]
+        public NoiseType NoiseType { get; private set; }
+
+        [SimpleStateProperty("NoiseMagnitude")]
+        public double NoiseMagnitude { get; private set; }
+        
+        [SimpleStateProperty("Clamp")]
+        public bool Clamp { get; private set; }
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            DenseIndividual addNoise(DenseIndividual individual)
+            {
+                individual = individual.Clone(context);
+
+                var initialState = individual.Genome.InitialState;
+
+                for (int i = 0; i < initialState.Count; i++)
+                {
+                    var μ = Misc.NextNoise(context.Rand, NoiseMagnitude, NoiseType);
+
+                    if (Clamp)
+                        initialState[i] = gResetRange.Clamp(initialState[i] + μ);
+                    else
+                        initialState[i] += μ;
+                }
+
+                individual.DevelopInplace(context, drules);
+
+                return individual;
+            }
+
+            population.Process(addNoise);
+        }
+    }
+
+    [StateClass]
+    public class ZeroPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "Zero";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newInitialState: MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(di.Genome.Size, 0.0)), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class NoPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "None";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context), context, drules, di.Epigenetic)); // clone directly
+        }
+    }
+
+    [StateClass]
+    public class DenseNoPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public string Name => "None";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            // TODO: do we need to clone?
+            population.Process(di => di.Clone(context));
+        }
+    }
+
+    [StateClass]
+    public class GenericNoPopulationResetOperation<TIndividual> : IPopulationResetOperation<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        public string Name => "None";
+
+        public void Reset(ModelExecutionContext context, Population<TIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            // TODO: do we need to clone?
+            population.Process(i => i.Clone(context));
+        }
+    }
+
+    [StateClass]
+    public class IterativeNormalisationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public IterativeNormalisationResetOperation(double rowSum)
+        {
+            RowSum = rowSum;
+        }
+
+        // TODO: verify this is a Sinkhorn-Knopp with mul
+        public string Name => "IterativeNormalisation" + RowSum;
+
+        [SimpleStateProperty("RowSum")]
+        public double RowSum { get; private set; }
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(di => DenseIndividual.Develop(di.Genome.Clone(context: context, newTransMat: Misc.IterativeNormalise(di.Genome.TransMat, MathNet.Numerics.LinearAlgebra.CreateVector.Dense(di.Genome.Size, RowSum), MathNet.Numerics.LinearAlgebra.CreateVector.Dense(di.Genome.Size, RowSum))), context, drules, di.Epigenetic));
+        }
+    }
+
+    [StateClass]
+    public class CombinedPopulationResetOperation<TIndividual> : IPopulationResetOperation<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        [Obsolete]
+        protected CombinedPopulationResetOperation()
+        { }
+
+        public CombinedPopulationResetOperation(IReadOnlyList<IPopulationResetOperation<TIndividual>> subOps)
+        {
+            SubOps = subOps ?? throw new ArgumentNullException(nameof(subOps));
+        }
+
+        public string Name => $"CombinedPopulationResetOperation({string.Join("||", SubOps.Select(so => so.Name))})";
+
+        [SimpleStateProperty("SubOps")]
+        public IReadOnlyList<IPopulationResetOperation<TIndividual>> SubOps { get; private set; }
+
+        public void Reset(ModelExecutionContext context, Population<TIndividual> population, Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            foreach (var subOp in SubOps)
+                subOp.Reset(context, population, gResetRange, drules, rrules, target);
+        }
+    }
+
+    public static class PopulationResetOperations
+    {
+        public static readonly IPopulationResetOperation<DenseIndividual> RandomIndependent = new RandomIndependantPopulationResetOperation();
+        public static readonly IPopulationResetOperation<DenseIndividual> RandomBinaryIndependent = new RandomBinaryIndependantPopulationResetOperation();
+        public static readonly IPopulationResetOperation<DenseIndividual> MatchTargetVector = new MatchTargetVectorPopulationResetOperation();
+        public static readonly IPopulationResetOperation<DenseIndividual> MatchTargetVectorPerfectPhenotype = new MatchTargetVectorPerfectPhenotypePopulationResetOperation();
+        public static readonly IPopulationResetOperation<DenseIndividual> MatchForcingVector = new MatchTargetForcingPopulationResetOperation();
+        public static readonly IPopulationResetOperation<DenseIndividual> Zero = new ZeroPopulationResetOperation();
+        public static readonly IPopulationResetOperation<DenseIndividual> None = new NoPopulationResetOperation();
+        public static IPopulationResetOperation<TIndividual> GenericNone<TIndividual>() where TIndividual : IIndividual<TIndividual> => new GenericNoPopulationResetOperation<TIndividual>();
+    }
+
+    [StateClass]
+    public class ProportionalSelectorPreparer<T> : ISelectorPreparer<T> where T : IIndividual<T>
+    {
+        public string Name => "Proportional";
+
+        public ISelector<T> Prepare(IReadOnlyList<IndividualJudgement<T>> judgements, RandomSource rand)
+        {
+            var wsel = new WeightedSelectorMk2<T>(rand, judgements.Count);
+
+            foreach (var ij in judgements)
+            {
+                wsel.Add(ij.Individual, ij.Judgement.CombinedFitness);
+            }
+
+            return wsel;
+        }
+    }
+
+    [StateClass]
+    public class RankedSelectorPreparer<T> : ISelectorPreparer<T> where T : IIndividual<T>
+    {
+        public string Name => "Ranked";
+
+        public ISelector<T> Prepare(IReadOnlyList<IndividualJudgement<T>> judgements, RandomSource rand)
+        {
+            var wsel = new WeightedSelectorMk2<T>(rand, judgements.Count);
+
+            int rank = 1;
+            foreach (var ij in judgements.OrderBy(_ij => _ij.Judgement.CombinedFitness))
+            {
+                wsel.Add(ij.Individual, rank);
+                rank++;
+            }
+
+            return wsel;
+        }
+    }
+
+    [StateClass]
+    public class ParetoSelectorPreparer<T> : ISelectorPreparer<T> where T : IIndividual<T>
+    {
+        public string Name => "Pareto";
+
+        public ISelector<T> Prepare(IReadOnlyList<IndividualJudgement<T>> judgements, RandomSource rand)
+        {
+            var psel = new ParetoSelector<T>(rand);
+
+            foreach (var ij in judgements)
+            {
+                psel.Add(ij.Individual, new[] { ij.Judgement.Benefit, ij.Judgement.Cost });
+            }
+
+            return psel;
+        }
+    }
+
+    public static class SelectorPreparers<T> where T : IIndividual<T>
+    {
+        public static readonly ISelectorPreparer<T> Proportional = new ProportionalSelectorPreparer<T>();
+        public static readonly ISelectorPreparer<T> Ranked = new RankedSelectorPreparer<T>();
+        public static readonly ISelectorPreparer<T> Pareto = new ParetoSelectorPreparer<T>();
+    }
+
+    // really want to be able to save/load these as a single package... need to think about some stuff first...
+
+    public interface IUnknownPopulationExperimentTypeReceiver<TResult>
+    {
+        TResult Receive<T>(PopulationExperiment<T> populationExperiment) where T : IIndividual<T>;
+    }
+
+    public static class PopulationExperimentHelpers
+    {
+        public static TResult LoadUnknownType<TResult>(string fileName, IUnknownPopulationExperimentTypeReceiver<TResult> receiver)
+        {
+            using (var fs = System.IO.File.Open(fileName, System.IO.FileMode.Open))
+            {
+                object untyped = GraphSerialisation.Read<object>(fs); // we assume this is a PopulationExperiment<T>
+
+                // find out T
+                Type individualType = untyped.GetType().GetGenericArguments()[0];
+
+                // type gap
+                return NetState.AutoState.AutoSerialisationHelpers.CallNamedStatic<TResult>(typeof(PopulationExperimentHelpers), null, nameof(CallbackReceiver), new[] { individualType, typeof(TResult) }, untyped, receiver);
+            }
+        }
+
+        private static TResult CallbackReceiver<T, TResult>(PopulationExperiment<T> populationExperiment, IUnknownPopulationExperimentTypeReceiver<TResult> receiver) where T : IIndividual<T>
+        {
+            return receiver.Receive(populationExperiment);
+        }
+    }
+
+    [StateClass]
+    public class PopulationExperiment<T> where T : IIndividual<T>
+    {
+        public PopulationExperiment(Population<T> population, PopulationExperimentConfig<T> populationConfig, FileStuff fileStuff)
+        {
+            Population = population;
+            PopulationConfig = populationConfig;
+            FileStuff = fileStuff;
+            Epoch = 0;
+            TotalGenerationCount = 0;
+        }
+
+        public PopulationExperiment(Population<T> population, PopulationExperimentConfig<T> populationConfig, FileStuff fileStuff, int startingEpoch, long startingGenerationCount)
+        {
+            Population = population;
+            PopulationConfig = populationConfig;
+            FileStuff = fileStuff;
+            Epoch = startingEpoch;
+            TotalGenerationCount = startingGenerationCount;
+        }
+
+        [Obsolete]
+        protected PopulationExperiment()
+        { }
+
+        /// <summary>
+        /// The current Population
+        /// </summary>
+        [SimpleStateProperty("Population")]
+        public Population<T> Population { get; private set; }
+
+        /// <summary>
+        /// The Population Configuration
+        /// </summary>
+        [SimpleStateProperty("PopulationConfig")]
+        public PopulationExperimentConfig<T> PopulationConfig { get; private set; }
+
+        /// <summary>
+        /// The file stuff we are spitting stuff into
+        /// </summary>
+        [SimpleStateProperty("FileStuff")]
+        public FileStuff FileStuff { get; private set; }
+
+        /// <summary>
+        /// The current epoch
+        /// </summary>
+        [SimpleStateProperty("Epoch")]
+        public int Epoch { get; private set; }
+
+        /// <summary>
+        /// The current generations
+        /// </summary>
+        [SimpleStateProperty("TotalGenerationCountLong")]
+        public long TotalGenerationCount { get; private set; }
+
+        [DeprecatedSimpleStateProperty("TotalGenerationCount")]
+        private int _totalGenerationCountShort
+        {
+            get
+            {
+                throw new InvalidOperationException("Attempted to access deprecated property '_totalGenerationCountShort'");
+            }
+            set
+            {
+                TotalGenerationCount = value;
+            }
+        }
+
+        /// <summary>
+        /// The current target index
+        /// </summary>
+        [SimpleStateProperty("CurrentTargetIndex")]
+        public int CurrentTargetIndex { get; private set; }
+
+        public void Save(string postfix, bool appendTimestamp)
+        {
+            string fname = "epoch" + Epoch + "save" + postfix;
+
+            if (appendTimestamp)
+                fname += Misc.NowTime;
+
+            fname += ".dat";
+            string tempfname = "~" + fname;
+
+            using (var fs = FileStuff.CreateBin(tempfname))
+            {
+                SaveTo(fs);
+            }
+
+            System.IO.File.Copy(FileStuff.File(tempfname), FileStuff.File(fname), true);
+            System.IO.File.Delete(FileStuff.File(tempfname));
+        }
+
+        public void SaveTo(string filename)
+        {
+            using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Create))
+            {
+                SaveTo(fs);
+            }
+        }
+
+        public void SaveTo(System.IO.FileStream fs)
+        {
+            GraphSerialisation.Write(this, fs);
+        }
+
+        public PopulationExperiment<T> Clone(FileStuff fileStuff)
+        {
+            return new PopulationExperiment<T>(Population.Clone(), PopulationConfig, fileStuff, Epoch, TotalGenerationCount);
+        }
+
+        public static PopulationExperiment<T> Load(string fileName)
+        {
+            using (var fs = System.IO.File.OpenRead(fileName))
+            {
+                return GraphSerialisation.Read<PopulationExperiment<T>>(fs);
+            }
+        }
+
+        /// <summary>
+        /// Runs a single epoch of the experiment.
+        /// Feedback may be null.
+        /// If you don't want to use this (e.g. you have a population but not a population experiment), then be sure to understand the order in which things need to happen:
+        ///  shuffle the targets if necessary
+        ///  for as many times as we have targets
+        ///    cycle to the next target
+        ///    call NextExposure on the target with appropriate ExposureInformation
+        ///    reset the population with probability config.InitialStateResetProbability
+        ///    give up on the exposure at this point if exposureInformation.ExposureDuration is less than zero (proceed if it is exactly zero, e.g. to allow delta/hebbian spinner to do its thing)
+        ///    spin the population with the given exposureInformation.ExposureDuration
+        ///    perform the PopulationEndTargetOperation
+        /// </summary>
+        public IReadOnlyList<IndividualJudgement<T>> RunEpoch(ModelExecutionContext context, PopulationExperimentFeedback<T> feedback)
+        {
+            var config = PopulationConfig.ExperimentConfiguration;
+            var populationConfig = PopulationConfig;
+            var population = Population;
+
+            int size = config.Size;
+            DevelopmentRules drules = config.DevelopmentRules;
+            ReproductionRules rrules = config.ReproductionRules;
+            JudgementRules jrules = config.JudgementRules;
+
+            int[] targetShuffling = config.ShuffleTargets
+                ? Misc.Create<int>(config.Targets.Count, i => i)
+                : null;
+
+            IReadOnlyList <IndividualJudgement<T>> judgements = null;
+
+            var populationSpinner = populationConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<T>.Instance;
+
+            Epoch++;
+
+            // shuffle targets, if we must
+            if (config.ShuffleTargets)
+            {
+                Misc.ShuffleInplace(context.Rand, targetShuffling);
+            }
+            int currentTargetShuffleIndex = config.ShuffleTargets
+                ? 0
+                : CurrentTargetIndex;
+
+            for (int ti = 0; ti < config.Targets.Count; ti++)
+            {
+                // NOTE: by doing this here, we are skipping the 0th target and moving onto the 1th: important when running mirror tracees and when assessing wholesamples
+                // (this behaviour is preserved for backwards compatability, but is not generally considered desirable, though it doesn't affect single-target experiments)
+                // select next currentTarget
+                currentTargetShuffleIndex = config.TargetCycler.Cycle(context.Rand, currentTargetShuffleIndex, config.Targets.Count);
+                CurrentTargetIndex = config.ShuffleTargets
+                    ? targetShuffling[currentTargetShuffleIndex]
+                    : currentTargetShuffleIndex;
+
+                ITarget currentTarget = config.Targets[CurrentTargetIndex];
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                currentTarget.NextExposure(context.Rand, jrules, Epoch, ref exposureInformation);
+
+                // reset if we should
+                if (context.Rand.NextDouble() < config.InitialStateResetProbability)
+                    populationConfig.PopulationResetOperation?.Reset(context, population, config.InitialStateResetRange, drules, rrules, currentTarget);
+ 
+                if (exposureInformation.ExposureDuration < 0)
+                    continue; // allow zero through as a special case; < 0 means skip altogether
+
+                feedback?.StartTarget.Call(st => st(FileStuff, population, currentTarget, Epoch, TotalGenerationCount));
+
+                // mutate, judge, select cycle
+                Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementCallback = (feedback?.Judged.HasAny ?? false) ? // do a special check, so that we only incur the cost in the loop if it is going somewhere
+                    new Action<int, IReadOnlyList<IndividualJudgement<T>>>((g, pjs) => feedback?.Judged.Call(act => act(FileStuff, pjs, Epoch, TotalGenerationCount + g, currentTarget))) : null;
+                judgements = populationSpinner.SpinPopulation(population, context, rrules, drules, jrules, currentTarget, populationConfig.SelectorPreparer, exposureInformation.ExposureDuration, judgementCallback, populationConfig.EliteCount, populationConfig.HillclimberMode);
+                populationConfig.PopulationEndTargetOperation?.Process(context, population, populationConfig.ExperimentConfiguration.DevelopmentRules, populationConfig.ExperimentConfiguration.JudgementRules, currentTarget);
+
+                TotalGenerationCount += exposureInformation.ExposureDuration;
+
+                feedback?.EndTarget.Call(et => et(FileStuff, population, currentTarget, Epoch, TotalGenerationCount, judgements));
+            }
+
+            feedback?.EndEpoch.Call(ee => ee(FileStuff, population, Epoch, TotalGenerationCount, judgements));
+
+            return judgements;
+        }
+
+        public void WriteOutConfig(System.IO.StreamWriter cw, RandomSource rand)
+        {
+            cw.WriteLine("# Config for " + FileStuff.OutDir);
+
+            PopulationConfig.WriteOut(cw);
+
+            cw.WriteLine();
+            cw.WriteLine();
+            cw.WriteLine("# Population Info");
+
+            cw.WriteLine($"Size = {Population.Count}");
+
+            cw.WriteLine();
+            cw.WriteLine();
+            cw.WriteLine("# Example Inidividual Info");
+
+            T sample = Population.PeekRandom(rand);
+            sample.WriteOut(cw);
+        }
+    }
+
+    public class PopulationExperimentRunners
+    {
+        public static PopulationExperiment<T> PrepareExperiment<T>(Population<T> population, PopulationExperimentConfig<T> populationConfig, string outDir, string filePrefix = "", string titlePrefix = "", bool disableAllIO = false, bool appendTimestamp = true) where T : IIndividual<T>
+        {
+            FileStuff stuff = FileStuff.CreateNow(outDir, filePrefix, titlePrefix, !disableAllIO, appendTimestamp);
+            var populationExperiment = new PopulationExperiment<T>(population, populationConfig, stuff);
+            return populationExperiment;
+        }
+
+        public static PopulationExperiment<T> PrepareExperiment<T>(Population<T> population, PopulationExperimentConfig<T> populationConfig, string outDir, int startingEpoch, long startingGenerationCount, string filePrefix = "", string titlePrefix = "", bool disableAllIO = false, bool appendTimestamp = true) where T : IIndividual<T>
+        {
+            FileStuff stuff = FileStuff.CreateNow(outDir, filePrefix, titlePrefix, !disableAllIO, appendTimestamp);
+            var populationExperiment = new PopulationExperiment<T>(population, populationConfig, stuff, startingEpoch, startingGenerationCount);
+            return populationExperiment;
+        }
+
+        public static void WriteOutInitials<T>(RandomSource rand, PopulationExperiment<T> populationExperiment) where T : IIndividual<T>
+        {
+            var stuff = populationExperiment.FileStuff;
+            var populationConfig = populationExperiment.PopulationConfig;
+            var population = populationExperiment.Population;
+
+            // save config
+            using (var cfs = stuff.CreateBin("config.dat"))
+            {
+                GraphSerialisation.Write(populationConfig, cfs);
+            }
+
+            // save population
+            using (var cfs = stuff.CreateBin("initialpopulation.dat"))
+            {
+                GraphSerialisation.Write(population, cfs);
+            }
+
+            // print out config
+            using (var cw = stuff.Open("config.txt"))
+            {
+                populationExperiment.WriteOutConfig(cw, rand);
+            }
+        }
+
+        /// <summary>
+        /// Runs many epochs.
+        /// </summary>
+        /// <param name="console">The <see cref="TextWriter"/> to spam.</param>
+        /// <param name="cliPrefix">The prefix to use when writing to <paramref name="console"/>.</param>
+        /// <param name="epochCount">The number of epochs to run.</param>
+        /// <param name="context">The execution context.</param>
+        /// <param name="populationExperiment">The experiment.</param>
+        /// <param name="feedback">The feedback.</param>
+        /// <param name="savePeriodEpochs">The number of epochs between saving the experiment.</param>
+        /// <param name="savePeriodSeconds">The number sections between saving the experiment if <paramref name="savePeriodEpochs"/> is exceeded.</param>
+        /// <param name="nosave">Whether to not save outputs as it goes.</param>
+        public static void RunEpochs<T>(TextWriter console, string cliPrefix, int epochCount, ModelExecutionContext context, PopulationExperiment<T> populationExperiment, PopulationExperimentFeedback<T> feedback, int savePeriodEpochs = 500, int savePeriodSeconds = 60 * 60, bool nosave = false) where T : IIndividual<T>
+        {
+            var config = populationExperiment.PopulationConfig.ExperimentConfiguration;
+            var stuff = populationExperiment.FileStuff;
+
+            int progressReportingPeriod = Math.Max(1, epochCount / 500);
+
+            feedback?.Started.Call(_f => _f(stuff, populationExperiment.Population));
+
+            Stopwatch sw = new Stopwatch();
+            sw.Reset();
+            sw.Start();
+
+            Stopwatch saveSw = new Stopwatch();
+            saveSw.Reset();
+            saveSw.Start();
+
+            for (int epoch = 1; epoch <= epochCount; epoch++)
+            {
+                var judgements = populationExperiment.RunEpoch(context, feedback);
+
+                if (epoch % progressReportingPeriod == 0)
+                {
+                    double remEstMilliseconds = sw.ElapsedMilliseconds * ((double)(epochCount - epoch) / epoch);
+                    TimeSpan remEst = TimeSpan.FromMilliseconds(remEstMilliseconds);
+
+                    double percentProgress = (epoch / (double)config.Epochs) * 100.0;
+                    double meanFitness = judgements.Average(ij => ij.Judgement.CombinedFitness);
+                    double meanBenefit = judgements.Average(ij => ij.Judgement.Benefit);
+                    double meanCost = judgements.Average(ij => ij.Judgement.Cost);
+                    console.WriteLine(cliPrefix + $"Epoch{epoch} ({percentProgress:00.0}%):\t f̅={meanFitness:0.000}, b̅={meanBenefit:0.000}, c̅={meanCost:0.000}\tre={remEst.ToString(@"d\:hh\:mm\:ss")}");
+                }
+
+                if (!nosave)
+                {
+                    if ((savePeriodEpochs != -1 && (populationExperiment.Epoch % savePeriodEpochs == 0))
+                    || (savePeriodSeconds != -1 && savePeriodSeconds * 1000 < saveSw.ElapsedMilliseconds))
+                    {
+                        populationExperiment.Save("", false);
+                        saveSw.Restart();
+                    }
+                }
+            }
+
+            feedback?.Finished.Call(_f => _f(stuff, populationExperiment.Population, populationExperiment.Epoch, populationExperiment.TotalGenerationCount));
+
+            if (!nosave)
+                populationExperiment.Save("terminal", false);
+        }
+
+        public static void RunExperiment<T>(TextWriter console, string cliPrefix, ModelExecutionContext context, Population<T> population, PopulationExperimentConfig<T> populationConfig, string outDir, bool disableTransientIO, bool disableAllIO = false, PopulationExperimentFeedback<T> feedback = null, string filePrefix = "", string titlePrefix = "") where T : IIndividual<T>
+        {
+            FileStuff stuff = FileStuff.CreateNow(outDir, filePrefix, titlePrefix, !disableAllIO);
+            RunExperiment<T>(console, cliPrefix, context, population, populationConfig, stuff, disableTransientIO, disableAllIO, feedback);
+        }
+
+        public static PopulationExperiment<T> RunExperiment<T>(TextWriter console, string cliPrefix, ModelExecutionContext context, Population<T> population, PopulationExperimentConfig<T> populationConfig, FileStuff stuff, bool disableTransientIO, bool disableAllIO = false, PopulationExperimentFeedback<T> feedback = null) where T : IIndividual<T>
+        {
+            var rand = context.Rand;
+
+            var populationExperiment = new PopulationExperiment<T>(population, populationConfig, stuff);
+            populationExperiment.Save("start", false);
+
+            WriteOutInitials(rand, populationExperiment);
+            console.WriteLine(populationExperiment.FileStuff.OutDir);
+
+            RunEpochs(console, cliPrefix, populationConfig.ExperimentConfiguration.Epochs, context, populationExperiment, feedback);
+
+            return populationExperiment;
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.Model/Selection.cs b/M4MCode/M4M_MkI/M4M.Model/Selection.cs
new file mode 100644
index 0000000..a9f1760
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Selection.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+
+namespace M4M
+{
+    public interface ISelector<T>
+    {
+        T SelectRandom();
+    }
+    public interface IRerandomableSelector<T> : ISelector<T>
+    {
+        void SetRandomSource(Random rnd);
+    }
+
+    public class ParetoSelector<T> : IRerandomableSelector<T>
+    {
+        public int Count => Options.Count;
+        private List<T> Options { get; }
+        private IList<double[]> Measures { get; }
+        private Random Random;
+
+        public ParetoSelector(Random rnd)
+        {
+            Random = rnd;
+            Options = new List<T>();
+            Measures = new List<double[]>();
+        }
+
+        public void Add(T option, double[] measures)
+        {
+            Options.Add(option);
+            Measures.Add(measures);
+        }
+
+        public T SelectRandom()
+        {
+            int a = Random.Next(Count);
+            int b = Random.Next(Count - 1);
+
+            if (b >= a)
+                b++;
+
+            int comparison = Compare(Measures[a], Measures[b]);
+
+            // a dominates
+            if (comparison > 0)
+                return Options[a];
+
+            // b dominates
+            if (comparison < 0)
+                return Options[b];
+
+            // random
+            if (Random.NextDouble() >= 0.5)
+                return Options[a];
+            else
+                return Options[b];
+        }
+
+        private int Compare(double[] a, double[] b)
+        {
+            bool aDominates = false;
+            bool bDominates = false;
+            for (int i = 0; i < a.Length; i++)
+            {
+                if (a[i] > b[i])
+                    aDominates = true;
+                else if (b[i] > a[i])
+                    bDominates = true;
+            }
+
+            if (aDominates && !bDominates)
+                return 1;
+
+            if (bDominates && !aDominates)
+                return -1;
+
+            return 0;
+        }
+
+        public void SetRandomSource(Random rnd)
+        {
+            Random = rnd;
+        }
+    }
+    
+    public class UniformSelector<T> : IRerandomableSelector<T>
+    {
+        public int Count => Options.Count;
+        private readonly List<T> Options = new List<T>();
+        private Random Random;
+
+        public UniformSelector(Random rnd) : this(rnd, 256)
+        {
+        }
+
+        public UniformSelector(Random rnd, int initialCapacity)
+        {
+            if (initialCapacity < 1)
+                throw new ArgumentException("Must be greater than 0", "initialCapactity");
+
+            Options = new List<T>(initialCapacity);
+            Random = rnd;
+        }
+
+        public void Add(T option)
+        {
+            Options.Add(option);
+        }
+
+        public T SelectRandom()
+        {
+            if (Count == 0)
+                throw new Exception("No items!");
+
+            return Options[Random.Next(Count)];
+        }
+
+        public void SetRandomSource(Random rnd)
+        {
+            Random = rnd;
+        }
+    }
+
+    /// <summary>
+    /// Pretty fast and maybe reliable
+    /// </summary>
+    public class WeightedSelectorMk2<T> : IRerandomableSelector<T>
+	{
+		public int Count { get; private set; }
+		private T[] Options;
+		private double[] BaseWeights;
+		private double TotalWeight;
+		private Random Random;
+        
+		public WeightedSelectorMk2(Random rnd) : this(rnd, 256)
+		{
+		}
+
+		public WeightedSelectorMk2(Random rnd, int initialCapacity)
+		{
+			if (initialCapacity < 1)
+				throw new ArgumentException("Must be greater than 0", "initialCapactity");
+			
+			Count = 0;
+			Options = new T[initialCapacity];
+			BaseWeights = new double[initialCapacity];
+			TotalWeight = 0.0;
+			Random = rnd;
+		}
+
+		public void Add(T option, double weight)
+		{
+			if (Count + 1 >= BaseWeights.Length)
+			{
+				// resize
+				T[] noarr = new T[Options.Length * 2];
+				Array.Copy(Options, noarr, Count);
+				Options = noarr;
+				
+				double[] nbwarr = new double[BaseWeights.Length * 2];
+				Array.Copy(BaseWeights, nbwarr, Count);
+				BaseWeights = nbwarr;
+			}
+			
+			Options[Count] = option;
+			TotalWeight += weight;
+			BaseWeights[Count] = TotalWeight;
+			Count++;
+		}
+
+		public T SelectRandom()
+		{
+			if (Count == 0)
+				throw new Exception("No items!");
+
+			double v = Random.NextDouble() * TotalWeight;
+			int k = Array.BinarySearch(BaseWeights, 0, Count, v);
+			
+			if (k < 0)
+				k = ~k;
+			
+			return Options[k];
+		}
+		
+        public void SetRandomSource(Random rnd)
+        {
+            Random = rnd;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/State/GraphSerialisation.cs b/M4MCode/M4M_MkI/M4M.Model/State/GraphSerialisation.cs
new file mode 100644
index 0000000..d8aeed9
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/State/GraphSerialisation.cs
@@ -0,0 +1,145 @@
+using NetState.AutoState;
+using NetState.Serialisation;
+using NetState.SoftState;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace M4M.State
+{
+    /// <summary>
+    /// Marks the class for automatric (soft) serialisation
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class)]
+    public class StateClassAttribute : SoftClassAttribute
+    {
+    }
+
+    /// <summary>
+    /// Marks the methods as available for use by the automatic (soft) serialiser
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method)]
+    public class StateMethodAttribute : SoftStateMethodAttribute
+    {
+    }
+
+    /// <summary>
+    /// Automatically deals with simple primitives and classes
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
+    public class SimpleStatePropertyAttribute : SoftPropertyAttributeAttribute
+    {
+        public SimpleStatePropertyAttribute(string name) : base(name, null, false, typeof(M4MPrimitiveTable))
+        {
+            // nix
+        }
+    }
+
+    /// <summary>
+    /// Automatically deals with simple primitives and classes
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property)]
+    public class DeprecatedSimpleStatePropertyAttribute : SoftPropertyAttributeAttribute
+    {
+        public DeprecatedSimpleStatePropertyAttribute(string name) : base(name, null, true, typeof(M4MPrimitiveTable))
+        {
+            // nix
+        }
+    }
+
+    public class MatrixStatePropertyAttribute : SoftPropertyAttributeAttribute
+    {
+        public MatrixStatePropertyAttribute(string name) : base(name, null, typeof(AutoDenseMatrixStateProvider<double, DoubleStateProvider>))
+        {
+            // nix
+        }
+    }
+
+    public class VectorStatePropertyAttribute : SoftPropertyAttributeAttribute
+    {
+        public VectorStatePropertyAttribute(string name) : base(name, null, typeof(AutoDenseVectorStateProvider<double, DoubleStateProvider>))
+        {
+            // nix
+        }
+    }
+
+    public static class GraphSerialisation
+    {
+        public static void Write<T>(T root, string filename) where T : class
+        {
+            using (var fs = System.IO.File.OpenRead(filename))
+            {
+                Write(root, fs);
+            }
+        }
+
+        public static void Write<T>(T root, Stream stream) where T : class
+        {
+            var gsi = PrepareGraphSerialisationInfo();
+            var writer = AutoGraphSerialisation.CreateWriter<object>(new StreamReaderWriter(stream), gsi);
+
+            writer.WriteRoot(root);
+        }
+
+        public static T Read<T>(string filename) where T : class
+        {
+            using (var fs = System.IO.File.OpenRead(filename))
+            {
+                return Read<T>(fs);
+            }
+        }
+
+        public static T Read<T>(Stream stream) where T : class
+        {
+            var gsi = PrepareGraphSerialisationInfo();
+            var reader = AutoGraphSerialisation.CreateReader<object>(new StreamReaderWriter(stream), gsi);
+
+            return reader.ReadRoot<T>();
+        }
+
+        public static GraphStreamSerialisationInfo<object> PrepareGraphSerialisationInfo()
+        {
+            // adds our structs and such to an AutoGraphStateProviderTable
+            var primitiveTable = new M4MPrimitiveTable();
+
+            // init gsi
+            var gsi = new GraphStreamSerialisationInfo<object>(
+                new CustomClassStateProviderTable<object, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>>(),
+                new CustomStateProviderProviders<object, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>>(),
+                false
+                );
+
+            // automatic handling for collections etc.
+            gsi.CustomClassStateProviderProviders.AddProvider(new AutoGraphClassStateProviderTable(primitiveTable));
+
+            // automatic soft-class serialisation
+            gsi.CustomClassStateProviderProviders.AddProvider(AutoSoftStateProviders<IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>>.Instance); // this is a good thing
+            
+            return gsi;
+        }
+    }
+
+    public class M4MPrimitiveTable : AutoGraphStateProviderTable
+    {
+        public M4MPrimitiveTable() : base(true)
+        {
+            // Mathnet
+            AddProvider(typeof(MathNet.Numerics.LinearAlgebra.Vector<double>), typeof(AutoDenseVectorStateProvider<double, DoubleStateProvider>));
+            AddProvider(typeof(MathNet.Numerics.LinearAlgebra.Matrix<double>), typeof(AutoDenseMatrixStateProvider<double, DoubleStateProvider>));
+
+            // Structs
+            AddProvider(typeof(MatrixEntryAddress), typeof(MatrixEntryAddressStateProvider));
+            AddProvider(typeof(MultiMeasureJudgement), typeof(MultiMeasureJudgementStateProvider));
+
+            // Enums
+            AddProvider(typeof(NoiseType), typeof(AutoEnumStateProvider<NoiseType, int, IntStateProvider>));
+            AddProvider(typeof(Hebbian.HebbianType), typeof(AutoEnumStateProvider<Hebbian.HebbianType, int, IntStateProvider>));
+            AddProvider(typeof(Hebbian.MatrixNormalisation), typeof(AutoEnumStateProvider<Hebbian.MatrixNormalisation, int, IntStateProvider>));
+            AddProvider(typeof(Hopfield.BiasType), typeof(AutoEnumStateProvider<Hopfield.BiasType, int, IntStateProvider>));
+            AddProvider(typeof(Hopfield.InteractionType), typeof(AutoEnumStateProvider<Hopfield.InteractionType, int, IntStateProvider>));
+            AddProvider(typeof(Hopfield.InteractionMatrixSource), typeof(AutoEnumStateProvider<Hopfield.InteractionMatrixSource, int, IntStateProvider>));
+            AddProvider(typeof(Hopfield.BiasVectorSource), typeof(AutoEnumStateProvider<Hopfield.BiasVectorSource, int, IntStateProvider>));
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/State/StateProviders.cs b/M4MCode/M4M_MkI/M4M.Model/State/StateProviders.cs
new file mode 100644
index 0000000..a3724aa
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/State/StateProviders.cs
@@ -0,0 +1,264 @@
+using NetState.AutoState;
+using NetState.Serialisation;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M.State
+{
+    //
+    // Vector
+    //
+
+    public class AutoDenseVectorStateProvider<T, TElementProvider> where TElementProvider : IStateProvider<T, IStreamContext, IStreamContext> where T : struct, IFormattable, IEquatable<T>
+    {
+        public static readonly DenseVectorStateProvider<T, IStreamContext, IStreamContext> Instance = new DenseVectorStateProvider<T, IStreamContext, IStreamContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, IStreamContext, IStreamContext>>(typeof(TElementProvider)));
+    }
+
+    public class AutoDenseVectorStateProvider<T, TElementProvider, TWriteContext, TReadContext> where TElementProvider : IStateProvider<T, TWriteContext, TReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext where T : struct, IFormattable, IEquatable<T>
+    {
+        public static readonly DenseVectorStateProvider<T, TWriteContext, TReadContext> Instance = new DenseVectorStateProvider<T, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, TWriteContext, TReadContext>>(typeof(TElementProvider)));
+    }
+
+    public class DenseVectorStateProvider<T, TWriteContext, TReadContext> : IClassStateProvider<Linear.Vector<T>, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<Linear.Vector<T>, TWriteContext, TReadContext, object, object> where TWriteContext : IStreamContext where TReadContext : IStreamContext where T : struct, IFormattable, IEquatable<T>
+    {
+        public DenseVectorStateProvider(IStateProvider<T, TWriteContext, TReadContext> elementStateProvider)
+        {
+            ElementStateProvider = elementStateProvider;
+        }
+
+        public IStateProvider<T, TWriteContext, TReadContext> ElementStateProvider { get; }
+
+        public IClassStateProvider<Linear.Vector<T>, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this;
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public Linear.Vector<T> ReadCreate(TReadContext context)
+        {
+            int count = context.StreamRw.ReadInt32();
+            
+            T[] arr = new T[count];
+
+            return Linear.CreateVector.DenseOfArray<T>(arr);
+        }
+
+        public void ReadState(TReadContext context, Linear.Vector<T> state)
+        {
+            for (int i = 0; i < state.Count; i++)
+            {
+                state[i] = ElementStateProvider.Read(context);
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, Linear.Vector<T> state)
+        {
+            int count = state.Count;
+            context.StreamRw.WriteInt32(count);
+        }
+
+        public void WriteState(TWriteContext context, Linear.Vector<T> state)
+        {
+            for (int i = 0; i < state.Count; i++)
+            {
+                ElementStateProvider.Write(context, state[i]);
+            }
+        }
+        
+        public Linear.Vector<T> Read(TReadContext context)
+        {
+            int count = context.StreamRw.ReadInt32();
+
+            if (count < 0)
+                return null;
+
+            T[] arr = new T[count];
+
+            for (int i = 0; i < count; i++)
+            {
+                arr[i] = ElementStateProvider.Read(context);
+            }
+
+            return Linear.CreateVector.DenseOfArray(arr);
+        }
+
+        public void Write(TWriteContext context, Linear.Vector<T> state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt32(-1);
+                return;
+            }
+
+            int count = state.Count;
+            context.StreamRw.WriteInt32(count);
+            
+            for (int i = 0; i < count; i++)
+            {
+                ElementStateProvider.Write(context, state[i]);
+            }
+        }
+    }
+
+    //
+    // Matrix
+    //
+    
+    public class AutoDenseMatrixStateProvider<T, TElementProvider> where TElementProvider : IStateProvider<T, IStreamContext, IStreamContext> where T : struct, IFormattable, IEquatable<T>
+    {
+        public static readonly DenseMatrixStateProvider<T, IStreamContext, IStreamContext> Instance = new DenseMatrixStateProvider<T, IStreamContext, IStreamContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, IStreamContext, IStreamContext>>(typeof(TElementProvider)));
+    }
+
+    public class AutoDenseMatrixStateProvider<T, TElementProvider, TWriteContext, TReadContext> where TElementProvider : IStateProvider<T, TWriteContext, TReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext where T : struct, IFormattable, IEquatable<T>
+    {
+        public static readonly DenseMatrixStateProvider<T, TWriteContext, TReadContext> Instance = new DenseMatrixStateProvider<T, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, TWriteContext, TReadContext>>(typeof(TElementProvider)));
+    }
+
+    public class DenseMatrixStateProvider<T, TWriteContext, TReadContext> : IClassStateProvider<Linear.Matrix<T>, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<Linear.Matrix<T>, TWriteContext, TReadContext, object, object> where TWriteContext : IStreamContext where TReadContext : IStreamContext where T : struct, IFormattable, IEquatable<T>
+    {
+        public DenseMatrixStateProvider(IStateProvider<T, TWriteContext, TReadContext> elementStateProvider)
+        {
+            ElementStateProvider = elementStateProvider;
+        }
+
+        public IStateProvider<T, TWriteContext, TReadContext> ElementStateProvider { get; }
+        
+        public IClassStateProvider<Linear.Matrix<T>, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this;
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public Linear.Matrix<T> ReadCreate(TReadContext context)
+        {
+            int rows = context.StreamRw.ReadInt32();
+            int cols = context.StreamRw.ReadInt32();
+            
+            T[,] arr = new T[rows, cols];
+
+            return Linear.CreateMatrix.DenseOfArray<T>(arr);
+        }
+
+        public void ReadState(TReadContext context, Linear.Matrix<T> state)
+        {
+            for (int i = 0; i < state.RowCount; i++)
+            {
+                for (int j = 0; j < state.ColumnCount; j++)
+                {
+                    state[i, j] = ElementStateProvider.Read(context);
+                }
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, Linear.Matrix<T> state)
+        {
+            int rows = state.RowCount;
+            int cols = state.ColumnCount;
+            context.StreamRw.WriteInt32(rows);
+            context.StreamRw.WriteInt32(cols);
+        }
+
+        public void WriteState(TWriteContext context, Linear.Matrix<T> state)
+        {
+            for (int i = 0; i < state.RowCount; i++)
+            {
+                for (int j = 0; j < state.ColumnCount; j++)
+                {
+                    ElementStateProvider.Write(context, state[i, j]);
+                }
+            }
+        }
+        
+        public Linear.Matrix<T> Read(TReadContext context)
+        {
+            int rows = context.StreamRw.ReadInt32();
+
+            if (rows == -1)
+                return null;
+
+            int cols = context.StreamRw.ReadInt32();
+            
+            T[,] arr = new T[rows, cols];
+
+            var res = Linear.CreateMatrix.DenseOfArray<T>(arr);
+
+            ReadState(context, res);
+
+            return res;
+        }
+
+        public void Write(TWriteContext context, Linear.Matrix<T> state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt32(-1);
+                return;
+            }
+
+            WriteCreate(context, state);
+            WriteState(context, state);
+        }
+    }
+
+    //
+    // Structs
+    //
+
+    public class MatrixEntryAddressStateProvider : IStateProvider<MatrixEntryAddress, IStreamContext, IStreamContext>
+    {
+        public static readonly MatrixEntryAddressStateProvider Instance = new MatrixEntryAddressStateProvider();
+
+        public MatrixEntryAddress Read(IStreamContext context)
+        {
+            int r = context.StreamRw.ReadInt32();
+            int c = context.StreamRw.ReadInt32();
+            return new MatrixEntryAddress(r, c);
+        }
+
+        public void Write(IStreamContext context, MatrixEntryAddress state)
+        {
+            context.StreamRw.WriteInt32(state.Row);
+            context.StreamRw.WriteInt32(state.Col);
+        }
+    }
+
+    public class MultiMeasureJudgementStateProvider : IStateProvider<MultiMeasureJudgement, IStreamContext, IStreamContext>
+    {
+        public static readonly MultiMeasureJudgementStateProvider Instance = new MultiMeasureJudgementStateProvider();
+
+        public MultiMeasureJudgement Read(IStreamContext context)
+        {
+            double b = DoubleStateProvider.Instance.Read(context);
+            double c = DoubleStateProvider.Instance.Read(context);
+            double cf = DoubleStateProvider.Instance.Read(context);
+            return new MultiMeasureJudgement(b, c, cf);
+        }
+
+        public void Write(IStreamContext context, MultiMeasureJudgement state)
+        {
+            DoubleStateProvider.Instance.Write(context, state.Benefit);
+            DoubleStateProvider.Instance.Write(context, state.Cost);
+            DoubleStateProvider.Instance.Write(context, state.CombinedFitness);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Stats.cs b/M4MCode/M4M_MkI/M4M.Model/Stats.cs
new file mode 100644
index 0000000..a5923c4
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Stats.cs
@@ -0,0 +1,349 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public class Label
+    {
+        public Label(string name)
+        {
+            Name = name ?? throw new ArgumentNullException(nameof(name));
+        }
+
+        public string Name { get; set; }
+    }
+
+    /// <summary>
+    /// Counts the number of elements in an enumerable while enumerating it.
+    /// </summary>
+    public class Counter
+    {
+        private int _total = 0;
+        public int Total => _total;
+
+        public IEnumerable<T> Count<T>(IEnumerable<T> enumerable)
+        {
+            foreach (var t in enumerable)
+            {
+                // why on earth is this interlocked? enumerables are not meant to be thread safe
+                System.Threading.Interlocked.Increment(ref _total);
+                yield return t;
+            }
+        }
+    }
+    
+    public class BasicStats
+    {
+        public BasicStats(int count, double mean, double stdDev)
+        {
+            Count = count;
+            Mean = mean;
+            StdDev = stdDev;
+        }
+
+        public int Count;
+        public double Mean { get; }
+        public double StdDev { get; }
+            
+        public static BasicStats Measure(IEnumerable<double> samples)
+        {
+            var counter = new Counter();
+                
+            var stats = MathNet.Numerics.Statistics.Statistics.MeanStandardDeviation(counter.Count(samples));
+            int count = counter.Total;
+            var μ = stats.Item1;
+            var σ = stats.Item2;
+
+            return new BasicStats(count, μ, σ);
+        }
+    }
+
+    // consider making these structs
+    public class Labelled<TItem, TLabel> : IComparable<Labelled<TItem, TLabel>> where TItem : IComparable<TItem>
+    {
+        public Labelled(TItem item, TLabel label)
+        {
+            Item = item;
+            Label = label;
+        }
+
+        public TItem Item { get; }
+        public TLabel Label { get; }
+
+        public int CompareTo(Labelled<TItem, TLabel> other)
+        {
+            return Item.CompareTo(other.Item);
+        }
+    }
+
+    public class Ranked<T> where T : IComparable<T>
+    {
+        public Ranked(T item, double rank)
+        {
+            Item = item;
+            Rank = rank;
+        }
+
+        public T Item { get; }
+        public double Rank { get; }
+    }
+
+    public enum TestType
+    {
+        LowerTailed,
+        TwoTailed,
+        UpperTailed,
+    }
+
+    public static class Stats
+    {
+        public static double UnpairedTScore(BasicStats sa, BasicStats sb)
+        {
+            var diff = sa.Mean - sb.Mean;
+
+            var sea = (sa.StdDev * sa.StdDev) / sa.Count;
+            var seb = (sb.StdDev * sb.StdDev) / sb.Count;
+            var se = Math.Sqrt(sea + seb);
+
+            return diff / se;
+        }
+
+        public static double Sqr(double x) => x * x;
+        public static double Sqr(int x) => x * x;
+        public static double Cube(double x) => x * x * x;
+        public static double Cube(int x) => x * x * x;
+
+        public static double UnpairedDegreesFreedom(BasicStats sa, BasicStats sb)
+        {
+            double dfn = Sqr(Sqr(sa.StdDev) / sa.Count + Sqr(sb.StdDev) / sb.Count);
+            double dfda = Sqr(Sqr(sa.StdDev) / sa.Count) / (sa.Count - 1);
+            double dfdb = Sqr(Sqr(sb.StdDev) / sb.Count) / (sb.Count - 1);
+            double dfd = dfda + dfdb;
+
+            double df = dfn / dfd;
+
+            return df;
+        }
+
+        public static double UnpairedPValue(BasicStats sa, BasicStats sb)
+        {
+            double tscore = UnpairedTScore(sa, sb);
+            double df = UnpairedDegreesFreedom(sa, sb);
+
+            double p = MathNet.Numerics.Distributions.StudentT.CDF(0, 1.0, df, tscore);
+
+            return p;
+        }
+
+        public static double UnpairedPValue(IEnumerable<double> a, IEnumerable<double> b)
+        {
+            // should really to check that a and b are normally distributed
+
+            var sa = BasicStats.Measure(a);
+            var sb = BasicStats.Measure(b);
+
+            return UnpairedPValue(sa, sb);
+        }
+
+        public static IEnumerable<Ranked<T>> Rank<T>(IEnumerable<T> seq) where T : IComparable<T>
+        {
+            var sorted = seq.OrderBy(x => x);
+
+            bool first = true;
+            int position = 0;
+
+            List<T> currents = new List<T>();
+
+            foreach (var s in sorted)
+            {
+                if (first)
+                {
+                    currents.Add(s);
+                    first = false;
+                }
+                else
+                {
+                    if (s.CompareTo(currents.Last()) > 0)
+                    {
+                        // yield the currents (with rank)
+                        double rank = position - (currents.Count - 1) / 2.0;
+                        foreach (var c in currents)
+                        {
+                            yield return new Ranked<T>(c, rank);
+                        }
+                        currents.Clear();
+                    }
+
+                    currents.Add(s);
+                }
+
+                position++;
+            }
+
+            // end
+            {
+                // yield the currents (with rank)
+                double rank = position - (currents.Count - 1) / 2.0;
+                foreach (var c in currents)
+                {
+                    yield return new Ranked<T>(c, rank);
+                }
+            }
+        }
+
+        public static IEnumerable<Labelled<TItem, TLabel>> Label<TItem, TLabel>(IEnumerable<TItem> items, TLabel label) where TItem : IComparable<TItem>
+        {
+            return items.Select(item => new Labelled<TItem, TLabel>(item, label));
+        }
+
+        public static IEnumerable<Labelled<TItem, TLabel>> Label<TItem, TLabel>(IEnumerable<TItem> items, Func<TItem, TLabel> labeller) where TItem : IComparable<TItem>
+        {
+            return items.Select(item => new Labelled<TItem, TLabel>(item, labeller(item)));
+        }
+
+        public static double ZTestStatisticToPValue(double z, TestType testType)
+        {
+            var normal = new MathNet.Numerics.Distributions.Normal();
+
+            switch (testType)
+            {
+                case TestType.LowerTailed:
+                    return normal.CumulativeDistribution(z);
+                case TestType.TwoTailed:
+                    return 2.0 * normal.CumulativeDistribution(-Math.Abs(z));
+                case TestType.UpperTailed:
+                    return normal.CumulativeDistribution(-z);
+                default:
+                    throw new ArgumentException(nameof(testType), $"Unrecognised TestType {testType}");
+            }
+        }
+
+        /// <summary>
+        /// Computes the Z-statistic continuity correction component.
+        /// </summary>
+        /// <param name="offset">The different between the sample and the distribution's mean.</param>
+        /// <param name="testType">The type of Z-Test</param>
+        public static double MannWhitneyUCorrection(double offset, TestType testType)
+        {
+            // In each case, we return something of magnitude 0.5 which pushes the Z values toward zero.
+            // This has the effect of increasing the P-value.
+            // In the two-tailed case, we ensure the Z values doesn't pass 0.
+
+            switch (testType)
+            {
+                case TestType.LowerTailed:
+                    return 0.5;
+                case TestType.TwoTailed:
+                    return -Math.Sign(offset) * Math.Min(0.5, Math.Abs(offset)); // don't pass 0: beyond zero it represents a nonsensical reduction in P-value
+                case TestType.UpperTailed:
+                    return -0.5;
+                default:
+                    throw new ArgumentException(nameof(testType), "Unrecognised TestType");
+            }
+        }
+
+        /// <summary>
+        /// Computes the Mann-Whitney U test statistic for the two independent distributions.
+        /// The Z and U values are those of the first distribution, <paramref name="a"/>.
+        /// </summary>
+        /// <typeparam name="T">The type of elements in the distributions. Must provide a total ordering.</typeparam>
+        /// <param name="a">The first distribution.</param>
+        /// <param name="b">The second distribution.</param>
+        /// <param name="testType">The type of Z-Test to perform.</param>
+        /// <param name="u">The U-statistic.</param>
+        /// <returns>The continuity corrected Z-statistic.</returns>
+        public static double MannWhitneyU<T>(IEnumerable<T> a, IEnumerable<T> b, TestType testType, out double u) where T : IComparable<T>
+        {
+            // Uses normal approximation; not found a nice source to explain exact yet.
+
+            var alabel = new Label("a");
+            var blanel = new Label("b");
+
+            var n1counter = new Counter();
+            var n2counter = new Counter();
+
+            var al = Label(n1counter.Count(a), alabel);
+            var bl = Label(n2counter.Count(b), blanel);
+
+            var ranked = Rank(al.Concat(bl)).ToArray();
+            var grouped = ranked.GroupBy(r => r.Rank);
+
+            var r1 = grouped.SelectMany(g => g).Where(e => e.Item.Label == alabel).Sum(e => e.Rank);
+
+            var n1 = n1counter.Total;
+            var n2 = n2counter.Total;
+            var n = n1 + n2;
+
+            if (n1 == 0 || n2 == 0)
+                throw new ArgumentException("One or both sequences contained no elements.");
+
+            var u1 = r1 - n1 * (n1 + 1) / 2.0;
+            // don't bother computing u2 (or r2)
+            u = u1;
+
+            var m = (n1 * n2) / 2.0;
+            var c = MannWhitneyUCorrection(u - m, testType);
+
+            var tieAdjustment = grouped.Sum(g => Cube(g.Count()) - g.Count()) / (n * (n - 1));
+            var σ = Math.Sqrt((n1 * n2 / 12.0) * ((n + 1) - tieAdjustment));
+
+            var z = (u - m + c) / σ;
+
+            return z;
+        }
+
+        /// <summary>
+        /// Computes the Mann-Whitney U test results for the two independent distributions using a normal-distribution approximation for the p value.
+        /// The results are those for the first distribution, <paramref name="a"/>.
+        /// </summary>
+        /// <typeparam name="T">The type of elements in the distributions. Must provide a total ordering.</typeparam>
+        /// <param name="a">The first distribution.</param>
+        /// <param name="b">The second distribution.</param>
+        /// <param name="testType">The type of Z-Test to perform.</param>
+        /// <returns>The test results.</returns>
+        public static MannWhitneyUTestResult MannWhitneyU<T>(IEnumerable<T> a, IEnumerable<T> b, TestType testType) where T : IComparable<T>
+        {
+            var z = MannWhitneyU<T>(a, b, testType, out var u);
+            var p = ZTestStatisticToPValue(z, testType);
+            return new MannWhitneyUTestResult(testType, z, u, p);
+        }
+    }
+
+    public class MannWhitneyUTestResult
+    {
+        public MannWhitneyUTestResult(TestType testType, double z, double u, double p)
+        {
+            TestType = testType;
+            Z = z;
+            U = u;
+            P = p;
+        }
+
+        /// <summary>
+        /// The type of Z-Test performed.
+        /// </summary>
+        public TestType TestType { get; }
+
+        /// <summary>
+        /// The Z test-statistic.
+        /// </summary>
+        public double Z { get; }
+
+        /// <summary>
+        /// The U test-statistic.
+        /// </summary>
+        public double U { get; }
+
+        /// <summary>
+        /// The P-value.
+        /// </summary>
+        public double P { get; }
+
+        public override string ToString()
+        {
+            return $"Mann-Whitney-U {this.TestType}: Z={this.Z}, U={this.U}, P={this.P}";
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.Model/Trace.cs b/M4MCode/M4M_MkI/M4M.Model/Trace.cs
new file mode 100644
index 0000000..432e12f
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Trace.cs
@@ -0,0 +1,830 @@
+using M4M.State;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public interface IUnknownWholeSamplesTypeReceiver<TResult>
+    {
+        TResult Receive<T>(List<WholeSample<T>> wholeSamples) where T : IIndividual<T>;
+    }
+
+    public static class WholeSamplesHelpers
+    {
+        public static TResult LoadUnknownType<TResult>(string fileName, IUnknownWholeSamplesTypeReceiver<TResult> receiver)
+        {
+            using (var fs = System.IO.File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
+            {
+                object untyped = GraphSerialisation.Read<object>(fs); // we assume this is a List<WholeSamples<T>>
+                
+                 // find out T
+                Type individualType = untyped.GetType().GetGenericArguments()[0].GetGenericArguments()[0];
+
+                // type gap
+                return NetState.AutoState.AutoSerialisationHelpers.CallNamedStatic<TResult>(typeof(WholeSamplesHelpers), null, nameof(CallbackReceiver), new[] { individualType, typeof(TResult) }, untyped, receiver);
+            }
+        }
+
+        private static TResult CallbackReceiver<T, TResult>(List<WholeSample<T>> WholeSamples, IUnknownWholeSamplesTypeReceiver<TResult> receiver) where T : IIndividual<T>
+        {
+            return receiver.Receive(WholeSamples);
+        }
+    }
+
+    public class WholeSamplesManipulatorReceiver : IUnknownWholeSamplesTypeReceiver<IWholeSamplesManipulator>
+    {
+        public IWholeSamplesManipulator Receive<T>(List<WholeSample<T>> wholeSamples) where T : IIndividual<T>
+        {
+            return new WholeSamplesExtractor<T>(wholeSamples);
+        }
+    }
+
+    public interface IWholeSamplesManipulator
+    {
+        /// <summary>
+        /// Appends samples loaded from a file
+        /// </summary>
+        void AppendSamples(string filename, int epochOffset, long generationOffset, bool skipFirst);
+
+        /// <summary>
+        /// Appends samples loaded from multiple files (in order)
+        /// </summary>
+        void AppendSamples(IEnumerable<string> filenames, bool resequence, bool skipFirst);
+
+        /// <summary>
+        /// Saves the samples to a file
+        /// </summary>
+        void SaveSamples(string outFilename);
+
+        /// <summary>
+        /// The number of samples
+        /// </summary>
+        int Count { get; }
+    }
+
+    // this is a pretty tiresome type
+    public class WholeSamplesExtractor<T> : IWholeSamplesManipulator where T : IIndividual<T>
+    {
+        public WholeSamplesExtractor(List<WholeSample<T>> wholeSamples)
+        {
+            WholeSamples = wholeSamples;
+        }
+
+        private List<WholeSample<T>> WholeSamples;
+        
+        public int Count => WholeSamples.Count;
+
+        public void AppendSamples(string filename, int epochOffset, long generationOffset, bool skipFirst)
+        {
+            var moreSamples = WholeSample<T>.LoadWholeSamples(filename);
+
+            if (skipFirst)
+            {
+                moreSamples.RemoveAt(0);
+            }
+
+            foreach (var ws in moreSamples)
+            {
+                ws.Retime(ws.Epoch + epochOffset, ws.Generations + generationOffset);
+            }
+
+            WholeSamples.AddRange(moreSamples);
+        }
+
+        public void AppendSamples(IEnumerable<string> filenames, bool resequence, bool skipFirst)
+        {
+            foreach (var filename in filenames)
+            {
+                int epochOffset = resequence ? WholeSamples.Last().Epoch : 0;
+                long generationOffset = resequence ? WholeSamples.Last().Generations : 0;
+                AppendSamples(filename, epochOffset, generationOffset, skipFirst);
+            }
+        }
+
+        public void SaveSamples(string outFilename)
+        {
+            WholeSample<T>.SaveWholeSamples(outFilename, WholeSamples);
+        }
+    }
+
+    [StateClass]
+    public class WholeSample<T> where T : IIndividual<T>
+    {
+        [Obsolete]
+        protected WholeSample()
+        {
+        }
+
+        public WholeSample(long generations, int epoch, ITarget target, IReadOnlyList<IndividualJudgement<T>> judgements)
+        {
+            Generations = generations;
+            Epoch = epoch;
+            Target = target;
+            Judgements = judgements;
+        }
+
+        // provided for backward compatibility
+        [Obsolete]
+        [DeprecatedSimpleStateProperty("Generations")]
+        private int _generationsShort
+        {
+            get
+            {
+                throw new InvalidOperationException("Property _generationsShort is deprecated, and cannot be used for serialisation");
+            }
+            set
+            {
+                Generations = value;
+            }
+        }
+
+        [SimpleStateProperty("GenerationsLong")]
+        public long Generations { get; private set; }
+
+        [SimpleStateProperty("Epoch")]
+        public int Epoch { get; private set; }
+        
+        [SimpleStateProperty("Target")]
+        public ITarget Target { get; private set; }
+        
+        [SimpleStateProperty("Judgements")]
+        public IReadOnlyList<IndividualJudgement<T>> Judgements { get; private set; }
+
+        public void Retime(int epoch, long generations)
+        {
+            Epoch = epoch;
+            Generations = generations;
+        }
+        
+        public static void SaveWholeSamples(string filename, List<WholeSample<T>> wholeSamples)
+        {
+            Misc.EnsureDirectory(System.IO.Path.GetDirectoryName(filename));
+            using (var fs = System.IO.File.OpenWrite(filename))
+            {
+                GraphSerialisation.Write(wholeSamples, fs);
+            }
+        }
+
+        public static List<WholeSample<T>> LoadWholeSamples(string filename)
+        {
+            using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
+            {
+                var wholeSamples = GraphSerialisation.Read<List<WholeSample<T>>>(fs);
+                return wholeSamples;
+            }
+        }
+
+        public static IEnumerable<List<WholeSample<T>>> EnumerateWholeSamplesSeries(IReadOnlyList<string> filenames)
+        {
+            foreach (var filename in filenames)
+            {
+                var wholeSamples = LoadWholeSamples(filename);
+                yield return wholeSamples;
+            }
+        }
+
+        public static IEnumerable<WholeSample<T>> EnumerateWholeSamplesSeries2(IReadOnlyList<string> filenames)
+        {
+            return EnumerateWholeSamplesSeries(filenames).SelectMany(lws => lws);
+        }
+    }
+
+    [StateClass]
+    public class TraceInfo<T> where T : IIndividual<T>
+    {
+        [Obsolete]
+        protected TraceInfo()
+        {
+        }
+
+        public TraceInfo(List<WholeSample<T>> traceWholeSamples)
+        {
+            TraceWholeSamples = traceWholeSamples;
+        }
+
+        [SimpleStateProperty("TraceWholeSamples")]
+        public List<WholeSample<T>> TraceWholeSamples { get; private set; }
+
+        public TraceInfo<T> ExtractGenerations(long start, long end)
+        {
+            return Filter(ws => ws.Generations >= start && ws.Generations <= end);
+        }
+
+        public TraceInfo<T> ExtractEpochs(int start, int end)
+        {
+            return Filter(ws => ws.Epoch >= start && ws.Epoch <= end);
+        }
+
+        public TraceInfo<T> Filter(Func<WholeSample<T>, bool> filter)
+        {
+            var wholeSamples = TraceWholeSamples.Where(filter).ToList();
+            return new TraceInfo<T>(wholeSamples);
+        }
+
+        /// <summary>
+        /// Drops epoch boundaries (i.e. samples which share a generation, but not an epoch
+        /// Useful for rough downsamplings
+        /// Default of dropFirst is equivalent to the old behaviour of not bothering to record the first generation of each epoch
+        /// </summary>
+        public TraceInfo<T> DropEpochBoundaries(bool dropFirst = true)
+        {
+            List<WholeSample<T>> wholeSamples = new List<WholeSample<T>>();
+
+            WholeSample<T> last = null;
+
+            for (int i = 0; i < TraceWholeSamples.Count; i++)
+            {
+                var cur = TraceWholeSamples[i];
+
+                if (last == null)
+                {
+                    last = cur;
+                }
+                else if (last.Epoch != cur.Epoch && last.Generations == cur.Generations)
+                {
+                    if (dropFirst)
+                    {
+                        wholeSamples.Add(last);
+                        last = null;
+                    }
+                    else
+                    {
+                        last = cur;
+                    }
+                }
+                else
+                {
+                    wholeSamples.Add(last);
+                    last = cur;
+                }
+            }
+
+            return new TraceInfo<T>(wholeSamples);
+        }
+
+        /// <summary>
+        /// Performs a trivial downsampling
+        /// </summary>
+        public TraceInfo<T> DownSample(int samplePeriod, bool keepLast = true)
+        {
+            List<WholeSample<T>> wholeSamples = new List<WholeSample<T>>();
+
+            int end = keepLast
+                ? TraceWholeSamples.Count - 1 // ignore last in loop (we add it explictly later)
+                : TraceWholeSamples.Count;
+
+            for (int i = 0; i < end; i += samplePeriod)
+            {
+                wholeSamples.Add(TraceWholeSamples[i]);
+            }
+
+            if (keepLast)
+            {
+                wholeSamples.Add(TraceWholeSamples.Last());
+            }
+
+            return new TraceInfo<T>(wholeSamples);
+        }
+
+        public void RetimeInplace(int startEpoch, long startGeneration)
+        {
+            var ws1 = TraceWholeSamples[0];
+
+            int epochOffset = startEpoch - ws1.Epoch;
+            long generationOffset = startGeneration - ws1.Generations;
+
+            OffsetInplace(epochOffset, generationOffset);
+        }
+        
+        public void OffsetInplace(int epochOffset, long generationOffset)
+        {
+            foreach (var ws in TraceWholeSamples)
+            {
+                ws.Retime(ws.Epoch + epochOffset, ws.Generations + generationOffset);
+            }
+        }
+
+        public void Save(string filename)
+        {
+            using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Create))
+            {
+                GraphSerialisation.Write(this, fs);
+            }
+        }
+
+        public static TraceInfo<T> Load(string filename)
+        {
+            using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
+            {
+                return GraphSerialisation.Read<TraceInfo<T>>(fs);
+            }
+        }
+    }
+
+    public class PopulationTraceRecorder<T> where T : IIndividual<T>
+    {
+        public List<WholeSample<T>> TraceWholeSamples = new List<WholeSample<T>>();
+
+        public PopulationJudgementFeedbackDelegate<T> Recorder { get; }
+
+        public int SamplePeriod { get; }
+
+        public PopulationTraceRecorder(int samplePeriod)
+        {
+            Recorder = Record;
+            SamplePeriod = samplePeriod;
+        }
+
+        private void Record(FileStuff stuff, IReadOnlyList<IndividualJudgement<T>> populationJudgements, int epochCount, long generationCount, ITarget target)
+        {
+            if (generationCount % SamplePeriod == 0)
+            {
+                TraceWholeSamples.Add(new WholeSample<T>(generationCount, epochCount, target, populationJudgements));
+            }
+        }
+    }
+
+    public interface ITraceWatcher<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        void StartTrace();
+        void EndTrace();
+        void StartTarget(long generation, ITarget target, Population<TIndividual> population);
+        void Judged(long generation, ITarget target, IReadOnlyList<IndividualJudgement<TIndividual>> individualJudgement);
+    }
+
+    public class TrajectoryTraceWatcher<TIndividual> : ITraceWatcher<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        public TrajectoryTraceWatcher(Func<ITarget, IReadOnlyList<IndividualJudgement<TIndividual>>, double>[] samplers)
+        {
+            Samplers = samplers;
+            AllTrajectories = samplers.Select(s => new List<double[]>()).ToArray();
+        }
+
+        public Func<ITarget, IReadOnlyList<IndividualJudgement<TIndividual>>, double>[] Samplers { get; }
+        private List<double[]>[] AllTrajectories { get; }
+        private List<double>[] Current;
+
+        public double[][] GetTrajectories(int samplerIndex)
+        {
+            return AllTrajectories[samplerIndex].ToArray();
+        }
+
+        public void EndTrace()
+        {
+            for (int i = 0; i < Samplers.Length; i++)
+            {
+                AllTrajectories[i].Add(Current[i].ToArray());
+            }
+            Current = null;
+        }
+
+        public void Judged(long generation, ITarget target, IReadOnlyList<IndividualJudgement<TIndividual>> individualJudgements)
+        {
+            for (int i = 0; i < Samplers.Length; i++)
+            {
+                Current[i].Add(Samplers[i](target, individualJudgements));
+            }
+        }
+
+        public void StartTarget(long generation, ITarget target, Population<TIndividual> population)
+        {
+            // nix
+        }
+
+        public void StartTrace()
+        {
+            Current = Misc.Create(Samplers.Length, i => new List<double>());
+        }
+    }
+
+    public class PopulationTrace
+    {
+        /// <summary>
+        /// Runs a tracee over a population; you probably want the other overload which return a <see cref="TraceInfo{T}"/>.
+        /// Basically, it does the same thing as RunEpochs, but without a PopulationExperiment (indeed, should produce exactly the same results; if not, something is wrong).
+        /// </summary>
+        public static void RunTrace<T>(TextWriter console, ModelExecutionContext context, FileStuff fileStuff, Population<T> population, PopulationExperimentConfig<T> popConfig, ITarget[] targets, PopulationJudgementFeedbackDelegate<T> feedback, EndPopulationTargetDelegate<T> endTargetFeedback, int startEpoch, int epochs) where T : IIndividual<T>
+        {
+            var populationSpinner = popConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<T>.Instance;
+
+            int generation = 0;
+            for (int i = 0; i < epochs; i++)
+            {
+                int epoch = startEpoch + i;
+
+                foreach (var target in targets)
+                {
+                    ExposureInformation exposureInformation = new ExposureInformation(popConfig.ExperimentConfiguration.GenerationsPerTargetPerEpoch);
+                    target.NextExposure(context.Rand, popConfig.ExperimentConfiguration.JudgementRules, epoch, ref exposureInformation);
+
+                    if (context.Rand.NextDouble() < popConfig.ExperimentConfiguration.InitialStateResetProbability)
+                        popConfig.PopulationResetOperation?.Reset(context, population, popConfig.ExperimentConfiguration.InitialStateResetRange, popConfig.ExperimentConfiguration.DevelopmentRules, popConfig.ExperimentConfiguration.ReproductionRules, target);
+
+                    if (exposureInformation.ExposureDuration < 0)
+                        continue; // allow zero through as a special case; < 0 means skip altogether
+
+                    Action<int, IReadOnlyList<IndividualJudgement<T>>> judgementFeedback = (g, ijs) =>
+                    {
+                        feedback?.Invoke(fileStuff, ijs, i, generation + g, target);
+                    };
+
+                    var terminalIjs = populationSpinner.SpinPopulation(population, context, popConfig.ExperimentConfiguration.ReproductionRules, popConfig.ExperimentConfiguration.DevelopmentRules, popConfig.ExperimentConfiguration.JudgementRules, target, popConfig.SelectorPreparer, exposureInformation.ExposureDuration, judgementFeedback, popConfig.EliteCount, popConfig.HillclimberMode);
+
+                    popConfig.PopulationEndTargetOperation?.Process(context, population, popConfig.ExperimentConfiguration.DevelopmentRules, popConfig.ExperimentConfiguration.JudgementRules, target);
+
+                    generation += exposureInformation.ExposureDuration;
+                    
+                    endTargetFeedback?.Invoke(fileStuff, population, target, epoch, generation, terminalIjs);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Runs a tracee over a population.
+        /// Basically, it does the same thing as RunEpochs, but without a PopulationExperiment (indeed, should produce exactly the same results; if not, something is wrong).
+        /// </summary>
+        public static TraceInfo<T> RunTrace<T>(TextWriter console, ModelExecutionContext context, Population<T> population, PopulationExperimentConfig<T> popConfig, ITarget[] targets, int samplePeriod, int startEpoch, int epochs) where T : IIndividual<T>
+        {
+            PopulationTraceRecorder<T> recorder = new PopulationTraceRecorder<T>(samplePeriod);
+            RunTrace(console, context, null, population, popConfig, targets, recorder.Recorder, null, startEpoch, epochs);
+            return new TraceInfo<T>(recorder.TraceWholeSamples);
+        }
+
+        /// <summary>
+        /// Runs multiples traces.
+        /// </summary>
+        public static TracesInfo RunTraces(TextWriter console, ModelExecutionContext context, Population<DenseIndividual> population, PopulationExperimentConfig<DenseIndividual> popConfig, ITarget[] targets, int count, int samplePeriod, int startEpoch, int epochs)
+        {
+            var rand = context.Rand;
+            var pop = population;
+
+            TracesRecorder tracesRecorder = TracesRecorder.FromFeedback(popConfig.ExperimentConfiguration.Size, samplePeriod, null);
+            var populationSpinner = popConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<DenseIndividual>.Instance;
+
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+            sw.Start();
+
+            for (int i = 0; i < count; i++)
+            {
+                int generation = 0;
+                for (int j = 0; j < epochs; j++)
+                {
+                    int epoch = startEpoch + j;
+
+                    Action<int, IReadOnlyList<IndividualJudgement<DenseIndividual>>> judgementFeedback = (g, ijs) => tracesRecorder.Judged(g + generation, ijs);
+
+                    population = pop.Clone();
+
+                    if (sw.ElapsedMilliseconds > 30000)
+                    {
+                        console.WriteLine($"{i} / {count}");
+                        sw.Restart();
+                    }
+
+                    tracesRecorder.StartTrace();
+                    foreach (var target in targets)
+                    {
+                        ExposureInformation exposureInformation = new ExposureInformation(popConfig.ExperimentConfiguration.GenerationsPerTargetPerEpoch);
+                        target.NextExposure(rand, popConfig.ExperimentConfiguration.JudgementRules, epoch, ref exposureInformation);
+
+                        tracesRecorder.StartTarget(generation, target);
+
+                        if (exposureInformation.ExposureDuration < 0)
+                            continue; // allow zero through as a special case; < 0 means skip altogether
+
+                        popConfig.PopulationResetOperation?.Reset(context, population, popConfig.ExperimentConfiguration.InitialStateResetRange, popConfig.ExperimentConfiguration.DevelopmentRules, popConfig.ExperimentConfiguration.ReproductionRules, target);
+                        populationSpinner.SpinPopulation(population, context, popConfig.ExperimentConfiguration.ReproductionRules, popConfig.ExperimentConfiguration.DevelopmentRules, popConfig.ExperimentConfiguration.JudgementRules, target, popConfig.SelectorPreparer, exposureInformation.ExposureDuration, judgementFeedback, popConfig.EliteCount, popConfig.HillclimberMode);
+                        popConfig.PopulationEndTargetOperation?.Process(context, population, popConfig.ExperimentConfiguration.DevelopmentRules, popConfig.ExperimentConfiguration.JudgementRules, target);
+
+                        generation += exposureInformation.ExposureDuration;
+                    }
+                    tracesRecorder.EndTrace();
+                }
+            }
+
+            return new TracesInfo(0, targets, samplePeriod, tracesRecorder.BestFitnesses, tracesRecorder.MeanFitnesses, tracesRecorder.BestInitialStates, tracesRecorder.BestTerminalStates, tracesRecorder.TargetSwitches);
+        }
+
+        /// <summary>
+        /// Runs multiple traces, and returns the results as sampled trajectories.
+        /// (Used by M4M_DED).
+        /// </summary>
+        public static double[][] TracesToTrajectories<TIndividual>(ModelExecutionContext context, PopulationExperiment<TIndividual> templateExp, IEnumerable<Population<TIndividual>> populations, Func<ITarget, IReadOnlyList<IndividualJudgement<TIndividual>>, double> sampler, int epochCount) where TIndividual : IIndividual<TIndividual>
+        {
+            var watcher = new TrajectoryTraceWatcher<TIndividual>(new [] { sampler });
+            RunTraces(context, templateExp, populations, watcher, epochCount);
+            return watcher.GetTrajectories(0);
+        }
+
+        /// <summary>
+        /// Runs multiples traces.
+        /// </summary>
+        public static void RunTraces<TIndividual>(ModelExecutionContext context, PopulationExperiment<TIndividual> templateExp, IEnumerable<Population<TIndividual>> populations, ITraceWatcher<TIndividual> traceWatcher, int epochCount) where TIndividual : IIndividual<TIndividual>
+        {
+            foreach (var population in populations)
+            {
+                var traj = new List<double>();
+
+                var exp = new PopulationExperiment<TIndividual>(population.Clone(context), templateExp.PopulationConfig, null, templateExp.Epoch, templateExp.TotalGenerationCount);
+
+                void judged(FileStuff stuff, IReadOnlyList<IndividualJudgement<TIndividual>> ijs, int _epochCount, long generationCount, ITarget target)
+                {
+                    traceWatcher.Judged(generationCount, target, ijs);
+                }
+
+                void startTarget(FileStuff stuff, Population<TIndividual> _population, ITarget target, int epoch, long generationCount)
+                {
+                    traceWatcher.StartTarget(generationCount, target, _population);
+                }
+
+                var feedback = new PopulationExperimentFeedback<TIndividual>();
+                feedback.StartTarget.Register(startTarget);
+                feedback.Judged.Register(judged);
+
+                // run trace
+                traceWatcher.StartTrace();
+                for (int i = 0; i < epochCount; i++)
+                    exp.RunEpoch(context, feedback);
+                traceWatcher.EndTrace();
+            }
+        }
+    }
+
+    [StateClass]
+    public class TraceTargetSwitch
+    {
+        public TraceTargetSwitch(long generation, ITarget target)
+        {
+            Generation = generation;
+            Target = target;
+        }
+
+        [Obsolete]
+        protected TraceTargetSwitch()
+        {
+        }
+        
+        // provided for backward compatibility
+        [Obsolete]
+        [DeprecatedSimpleStateProperty("Generation")]
+        private int _generationShort
+        {
+            get
+            {
+                throw new InvalidOperationException("Property _generationShort is deprecated, and cannot be used for serialisation");
+            }
+            set
+            {
+                Generation = value;
+            }
+        }
+
+        [SimpleStateProperty("GenerationLong")]
+        public long Generation { get; private set; }
+
+        [SimpleStateProperty("Target")]
+        public ITarget Target { get; private set; }
+    }
+
+    [StateClass]
+    public class TracesInfo
+    {
+        [Obsolete]
+        protected TracesInfo()
+        {
+        }
+
+        public TracesInfo(long startGeneration, IReadOnlyList<ITarget> targets, int samplePeriod, IReadOnlyList<double[]> bestFitnesses, IReadOnlyList<double[]> meanFitnesses, IReadOnlyList<double[][]> bestInitialStates, IReadOnlyList<double[][]> bestTerminalStates, TraceTargetSwitch[] targetSwitches)
+        {
+            StartGeneration = startGeneration;
+            Targets = targets;
+            SamplePeriod = samplePeriod;
+            BestFitnesses = bestFitnesses;
+            MeanFitnesses = meanFitnesses;
+            BestInitialStates = bestInitialStates;
+            BestTerminalStates = bestTerminalStates;
+            TargetSwitches = targetSwitches;
+        }
+        
+        // provided for backward compatibility
+        [Obsolete]
+        [DeprecatedSimpleStateProperty("StartGeneration")]
+        private int _startGeneration
+        {
+            get
+            {
+                throw new InvalidOperationException("Property _startGeneration is deprecated, and cannot be used for serialisation");
+            }
+            set
+            {
+                StartGeneration = value;
+            }
+        }
+
+        [SimpleStateProperty("StartGenerationLong")]
+        public long StartGeneration { get; private set; }
+        
+        public long EndGeneration => StartGeneration + BestFitnesses[0].Length * SamplePeriod;
+
+        [SimpleStateProperty("Targets")]
+        public IReadOnlyList<ITarget> Targets { get; private set; }
+
+        [SimpleStateProperty("SamplePeriod")]
+        public int SamplePeriod { get; private set; }
+
+        [SimpleStateProperty("BestFitnesses")]
+        public IReadOnlyList<double[]> BestFitnesses { get; private set; }
+
+        [SimpleStateProperty("MeanFitnesses")]
+        public IReadOnlyList<double[]> MeanFitnesses { get; private set; }
+
+        [SimpleStateProperty("BestInitialStates")]
+        public IReadOnlyList<double[][]> BestInitialStates { get; private set; }
+
+        [SimpleStateProperty("BestTerminalStates")]
+        public IReadOnlyList<double[][]> BestTerminalStates { get; private set; }
+
+        [SimpleStateProperty("TargetSwitches")]
+        public TraceTargetSwitch[] TargetSwitches { get; private set; }
+
+        public void Save(string filename)
+        {
+            using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Create))
+            {
+                GraphSerialisation.Write(this, fs);
+            }
+        }
+
+        public static TracesInfo Load(string filename)
+        {
+            using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
+            {
+                return GraphSerialisation.Read<TracesInfo>(fs);
+            }
+        }
+
+        public TracesInfo Extract(long start, long end)
+        {
+            var tses = TargetSwitches.Where(ts => ts.Generation >= start && ts.Generation < end).ToArray();
+
+            start -= StartGeneration;
+            end -= StartGeneration;
+            
+            int skip = (int)(start / SamplePeriod);
+            int take = (int)Math.Min((end + 1 - start) / SamplePeriod, BestFitnesses[0].Length - skip);
+
+            return new TracesInfo(skip * SamplePeriod + StartGeneration, Targets, SamplePeriod,
+                BestFitnesses.Select(s => s.Skip(skip).Take(take).ToArray()).ToArray(),
+                MeanFitnesses.Select(s => s.Skip(skip).Take(take).ToArray()).ToArray(),
+                BestInitialStates.Select(s => s.Select(t => t.Skip(skip).Take(take).ToArray()).ToArray()).ToArray(),
+                BestTerminalStates.Select(s => s.Select(t => t.Skip(skip).Take(take).ToArray()).ToArray()).ToArray(),
+                tses
+                );
+        }
+
+        public double[] ExtractMeanSamples(long generation)
+        {
+            int skip = (int)(generation / SamplePeriod);
+            return MeanFitnesses.Select(s => s.Skip(skip).First()).ToArray();
+        }
+
+        public double[] ExtractBestSamples(long generation)
+        {
+            int skip = (int)(generation / SamplePeriod);
+            return BestFitnesses.Select(s => s.Skip(skip).First()).ToArray();
+        }
+    }
+
+    public class TracesRecorder : ITraceWatcher<DenseIndividual>
+    {
+        // uses feedback
+        private TracesRecorder(int size, int samplePeriod, PopulationExperimentFeedback<DenseIndividual> feedback)
+        {
+            Size = size;
+            SamplePeriod = samplePeriod;
+            Feedback = feedback ?? new PopulationExperimentFeedback<DenseIndividual>();
+
+            Feedback.Judged.Register(Judged);
+            Feedback.Started.Register(Started);
+            Feedback.Finished.Register(Finished);
+            Feedback.StartTarget.Register(TargetStarted);
+        }
+
+        // expects ITraceWatcher
+        private TracesRecorder(int size, int samplePeriod)
+        {
+            Size = size;
+            SamplePeriod = samplePeriod;
+            Feedback = null;
+        }
+
+        public static TracesRecorder FromFeedback(int size, int samplePeriod, PopulationExperimentFeedback<DenseIndividual> feedback)
+        {
+            return new TracesRecorder(size, samplePeriod, feedback);
+        }
+
+        public static TracesRecorder AsTraceWatcher(int size, int samplePeriod)
+        {
+            return new TracesRecorder(size, samplePeriod);
+        }
+
+        public List<double[]> BestFitnesses { get; } = new List<double[]>();
+        public List<double[]> MeanFitnesses { get; } = new List<double[]>();
+        public List<double[][]> BestInitialStates { get; } = new List<double[][]>();
+        public List<double[][]> BestTerminalStates { get; } = new List<double[][]>();
+        public TraceTargetSwitch[] TargetSwitches { get; private set; } = null;
+
+        private List<double> BestFitness;
+        private List<double> MeanFitness;
+        private List<double>[] BestInitialState;
+        private List<double>[] BestTerminalState;
+        private List<TraceTargetSwitch> TargetSwitchList;
+
+        public int SamplePeriod { get; }
+        public int Size { get; }
+        public PopulationExperimentFeedback<DenseIndividual> Feedback { get; }
+
+        private void Started(FileStuff stuff, Population<DenseIndividual> population)
+        {
+            StartTrace();
+        }
+
+        public void StartTrace()
+        {
+            BestFitness = new List<double>();
+            MeanFitness = new List<double>();
+
+            BestInitialState = Misc.Create(Size, i => new List<double>());
+            BestTerminalState = Misc.Create(Size, i => new List<double>());
+
+            if (TargetSwitches == null)
+                TargetSwitchList = new List<TraceTargetSwitch>();
+        }
+
+        private void Finished(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount)
+        {
+            EndTrace();
+        }
+
+        public void EndTrace()
+        {
+            BestFitnesses.Add(BestFitness.ToArray());
+            MeanFitnesses.Add(MeanFitness.ToArray());
+            
+            BestInitialStates.Add(BestInitialState.Select(r => r.ToArray()).ToArray());
+            BestTerminalStates.Add(BestTerminalState.Select(r => r.ToArray()).ToArray());
+
+            if (TargetSwitches == null)
+            {
+                TargetSwitches = TargetSwitchList.ToArray();
+                TargetSwitchList = null;
+            }
+        }
+
+        private void TargetStarted(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount)
+        {
+            StartTarget(generationCount, target);
+        }
+
+        public void StartTarget(long generation, ITarget target, Population<DenseIndividual> population)
+        {
+            StartTarget(generation, target);
+        }
+
+        public void StartTarget(long generation, ITarget target)
+        {
+            if (TargetSwitchList != null)
+                TargetSwitchList.Add(new TraceTargetSwitch(generation, target));
+        }
+
+        private void Judged(FileStuff stuff, IReadOnlyList<IndividualJudgement<DenseIndividual>> populationJudgements, int epochCount, long generationCount, ITarget target)
+        {
+            Judged(generationCount, populationJudgements);
+        }
+
+        public void Judged(long generation, ITarget target, IReadOnlyList<IndividualJudgement<DenseIndividual>> populationJudgements)
+        {
+            Judged(generation, populationJudgements);
+        }
+
+        public void Judged(long generation, IReadOnlyList<IndividualJudgement<DenseIndividual>> populationJudgements)
+        {
+            if (generation % SamplePeriod != 0)
+                return;
+
+            var best = populationJudgements.ArgMax(ij => ij.Judgement.CombinedFitness);
+
+            BestFitness.Add(best.Judgement.CombinedFitness);
+            MeanFitness.Add(populationJudgements.Average(ij => ij.Judgement.CombinedFitness));
+            
+            for (int i = 0; i < BestInitialState.Length; i++)
+                BestInitialState[i].Add(best.Individual.Genome.InitialState[i]);
+            for (int i = 0; i < BestTerminalState.Length; i++)
+                BestTerminalState[i].Add(best.Individual.Phenotype[i]);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Model/Typical.cs b/M4MCode/M4M_MkI/M4M.Model/Typical.cs
new file mode 100644
index 0000000..6f4f6fc
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Model/Typical.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using static M4M.Misc;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public static class TypicalConfiguration
+    {
+        public static DevelopmentRules CreateStandardDevelopmentRules(ISquash squash, int timeSteps = 10, double updateRate = 1.0, double decayRate = 0.2, double rescaleScale = 1.0, double rescaleOffset = 0.0)
+        {
+            DevelopmentRules drules = new DevelopmentRules(timeSteps, updateRate, decayRate, squash, rescaleScale, rescaleOffset);
+
+            return drules;
+        }
+
+        public static ReproductionRules CreateStandardReproductionRules(double gMutationMagnitude, double bMutationMagnitude, double bMutationProbability, bool exclusiveBMutation = false, int gUpdates = 1, Range gClamping = null, NoiseType gMutationType = NoiseType.Uniform, NoiseType bMutationType = NoiseType.Uniform, Range bClamping = null)
+        {
+            ReproductionRules rrules = new ReproductionRules(gMutationMagnitude, bMutationMagnitude, bMutationProbability, exclusiveBMutation, gUpdates, gClamping, gMutationType, bMutationType, bClamping);
+
+            return rrules;
+        }
+
+        public static JudgementRules CreateStandardJudgementRules(double regularisationFactor, IRegularisationFunction<IGenome> regularisationFunction, double noiseFactor = 0.0)
+        {
+            JudgementRules jrules = new JudgementRules(regularisationFactor, regularisationFunction, noiseFactor);
+
+            return jrules;
+        }
+
+        public static Population<DenseIndividual> CreatePopulation(ModelExecutionContext context, int populationSize, int N, DevelopmentRules drules, Linear.Vector<double> g0 = null, Linear.Matrix<double> b0 = null, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, ITransMatMutator transMatMutator = null, IInitialStateMutator initialStateMutator = null, ITransMatCombiner transMatCombiner = null, IInitialStateCombiner initialStateCombiner = null, IDevelopmentStep developmentStep = null, bool epigenetic = false, double stateNoiseFactor = 0.0, Linear.Vector<double> biasVector0 = null)
+        {
+            if (epigenetic || stateNoiseFactor != 0.0)
+            {
+                Trace.TraceWarning($"{nameof(stateNoiseFactor)} is non-zero, but {nameof(epigenetic)} is false: {nameof(stateNoiseFactor)} may be ignored");
+            }
+
+            g0 = g0 ?? Linear.CreateVector.Dense(N, 0.0);
+            b0 = b0 ?? Linear.CreateMatrix.Dense(N, N, 0.0);
+            developmentStep = developmentStep ?? Development.DefaultDevelopmentStep(N, stateNoiseFactor);
+
+            DenseIndividual CreateDefaultIndividual(ModelExecutionContext _context)
+            {
+                var genome = new DenseGenome(g0, b0, gOpenEntries, bOpenEntries, transMatMutator, initialStateMutator, transMatCombiner, initialStateCombiner, developmentStep, biasVector0, false);
+                var individual = DenseIndividual.Develop(genome, _context, drules, epigenetic);
+                return individual;
+            }
+
+            // population stuff
+            Population<DenseIndividual> population = new Population<DenseIndividual>(context, _context => CreateDefaultIndividual(_context), populationSize);
+
+            return population;
+        }
+
+        public static ExperimentConfiguration CreateConfig(int N, DevelopmentRules drules, ReproductionRules rrules, JudgementRules jrules, IReadOnlyList<ITarget> targets, int epochs, int generationsPerTargetPerEpoch, double gResetProb, Range gResetRange, ICycler targetCycler = null, bool shuffleTargets = false)
+        {
+            targetCycler = targetCycler ?? Cyclers.Loop;
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, targetCycler, epochs, generationsPerTargetPerEpoch, gResetProb, gResetRange, shuffleTargets, drules, rrules, jrules);
+
+            return config;
+        }
+
+        public static PopulationExperimentConfig<DenseIndividual> CreatePopulationConfig(ExperimentConfiguration config, int eliteCount, bool hillclimberMode, ISelectorPreparer<DenseIndividual> selector = null, IPopulationEndTargetOperation<DenseIndividual> populationEndTargetOperation = null, IPopulationResetOperation<DenseIndividual> resetIndividualInitialStateOperation = null, IPopulationSpinner<DenseIndividual> customPopulationSpinner = null)
+        {
+            selector = selector ?? SelectorPreparers<DenseIndividual>.Ranked;
+            resetIndividualInitialStateOperation = resetIndividualInitialStateOperation ?? PopulationResetOperations.None;
+
+            PopulationExperimentConfig<DenseIndividual> popConfig = new PopulationExperimentConfig<DenseIndividual>(config, selector, eliteCount, hillclimberMode, populationEndTargetOperation, resetIndividualInitialStateOperation, customPopulationSpinner);
+
+            return popConfig;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/Cli.cs b/M4MCode/M4M_MkI/M4M.New/CLI/Cli.cs
new file mode 100644
index 0000000..7a7d9b5
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/Cli.cs
@@ -0,0 +1,792 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.IO;
+
+namespace M4M
+{
+    public interface ICli
+    {
+        void Run(TextWriter console, string[] args);
+    }
+
+    public class CliParams
+    {
+        private Dictionary<string, string> Table { get; } // don't support concurrent write
+        private ConcurrentDictionary<string, object> Unobserved { get; } // do support concurrent observe
+
+        // cloning constructor
+        private CliParams(CliParams orig)
+        {
+            Table = new Dictionary<string, string>(orig.Table, orig.Table.Comparer);
+            Unobserved = orig.Unobserved; // I think we share this... nothing else makes any sense
+        }
+
+        public CliParams(bool caseSensitive = false)
+        {
+            StringComparer strComparer;
+
+            if (caseSensitive)
+            {
+                strComparer = StringComparer.Ordinal;
+            }
+            else
+            {
+                strComparer = StringComparer.OrdinalIgnoreCase;
+            }
+            
+            Table = new Dictionary<string, string>(strComparer);
+            Unobserved = new ConcurrentDictionary<string, object>(strComparer);
+        }
+
+        private void SetInternal(string key, string value, bool unobserved = true)
+        {
+            Table[key] = value;
+            if (unobserved)
+                Unobserved.TryAdd(key, null);
+        }
+
+        private bool TryGet(string key, out string value)
+        {
+            if (Table.TryGetValue(key, out value))
+            {
+                Unobserved.TryRemove(key, out _);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public IEnumerable<string> Entries => Table.Keys;
+
+        public void ConsumeLine(string line)
+        {
+            Consume(StringHelpers.Split(line).ToArray());
+        }
+
+        public void ConsumeFile(string fileName)
+        {
+            foreach (var line in System.IO.File.ReadLines(fileName))
+            {
+                if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith("//"))
+                {
+                    ConsumeLine(line);
+                }
+            }
+        }
+
+        public void Consume(string[] args)
+        {
+            bool currentIsAppend = false;
+            string currentCommand = null;
+
+            void setIntercepted(string key, string value, bool append)
+            {
+                if (Table.Comparer.Equals(key, "params"))
+                {
+                    // expand (ignore add)
+                    ConsumeLine(value);
+                }
+                else
+                {
+                    if (append)
+                    {
+                        var old = Get(key, null) ?? ""; // treat null/assigned as empty string
+                        SetInternal(key, old + value);
+                    }
+                    else
+                    {
+                        // overwrite
+                        SetInternal(key, value);
+                    }
+                }
+            }
+
+            foreach (string arg in args)
+            {
+                // +/-cmd val
+                if (currentCommand != null)
+                {
+                    setIntercepted(currentCommand, arg, currentIsAppend);
+                    currentCommand = null;
+                    continue;
+                }
+
+                // -cmd
+                if (arg.Length > 0 && arg[0] == '-')
+                {
+                    currentCommand = arg.Substring(1);
+                    currentIsAppend = false;
+                    continue;
+                }
+
+                // +cmd
+                if (arg.Length > 0 && arg[0] == '+')
+                {
+                    currentCommand = arg.Substring(1);
+                    currentIsAppend = true;
+                    continue;
+                }
+
+                // cmd=val or cmd+=val
+                int sidx = arg.IndexOf('=');
+                if (sidx != -1)
+                {
+                    if (sidx > 0 && arg[sidx - 1] == '+')
+                    {
+                        // cmd+=val
+                        string key = arg.Substring(0, sidx - 1);
+                        string val = arg.Substring(sidx + 1);
+
+                        setIntercepted(key, val, true);
+                    }
+                    else
+                    {
+                        // cmd=value
+                        string key = arg.Substring(0, sidx);
+                        string val = arg.Substring(sidx + 1);
+
+                        setIntercepted(key, val, false);
+                    }
+
+                    continue;
+                }
+
+                // cmd
+                setIntercepted(arg, null, false);
+            }
+
+            if (currentCommand != null)
+                throw new Exception("Unspecified argument for command " + currentCommand);
+        }
+
+        public bool IsSet(string key)
+        {
+            return TryGet(key, out _);
+        }
+        
+        /// <summary>
+        /// Attempts to retrieve the value for the given key
+        /// Throws if the key is not present
+        /// </summary>
+        public string Get(string key)
+        {
+            if (TryGet(key, out string val))
+            {
+                return val;
+            }
+            else
+            {
+                throw new Exception("No argument provided for required parameter '" + key + "'");
+            }
+        }
+        
+        /// <summary>
+        /// Attempts to retrieve the value for the given key
+        /// Returns the default value if the key is not present
+        /// </summary>
+        public string Get(string key, string @default)
+        {
+            if (TryGet(key, out string val))
+            {
+                return val;
+            }
+            else
+            {
+                return @default;
+            }
+        }
+        
+        /// <summary>
+        /// Attempts to retrieve the value for the given key, returning the parsed value
+        /// Throws is the key is not present
+        /// </summary>
+        public T Get<T>(string key, Func<string, T> parser)
+        {
+            if (TryGet(key, out string val))
+            {
+                try
+                {
+                    return parser(val);
+                }
+                catch (Exception ex)
+                {
+                    throw new Exception("Argument for parameter '" + key + "' was of the wrong format", ex);
+                }
+            }
+            else
+            {
+                throw new Exception("No argument provided for required parameter '" + key + "'");
+            }
+        }
+        
+        /// <summary>
+        /// Attempts to retrieve the value for the given key, returning the parsed value
+        /// If key is not found, returns the default value
+        /// </summary>
+        public T Get<T>(string key, Func<string, T> parser, T @default)
+        {
+            if (TryGet(key, out string val))
+            {
+                try
+                {
+                    return parser(val);
+                }
+                catch (Exception ex)
+                {
+                    throw new Exception("Argument for parameter '" + key + "' was of the wrong format", ex);
+                }
+            }
+            else
+            {
+                return @default;
+            }
+        }
+
+        /// <summary>
+        /// Attempts to retrieve the value for the given key, returning the parsed value
+        /// If key is not found, generates the value from the creator (only called if the key is no present)
+        /// </summary>
+        public string GetOrCreate(string key, Func<string> creator)
+        {
+            if (TryGet(key, out string val))
+            {
+                return val;
+            }
+            else
+            {
+                return creator();
+            }
+        }
+
+        /// <summary>
+        /// Attempts to retrieve the value for the given key, returning the parsed value
+        /// If key is not found, generates the value from the creator (only called if the key is no present)
+        /// </summary>
+        public T GetOrCreate<T>(string key, Func<string, T> parser, Func<T> creator)
+        {
+            if (TryGet(key, out string val))
+            {
+                try
+                {
+                    return parser(val);
+                }
+                catch (Exception ex)
+                {
+                    throw new Exception("Argument for parameter '" + key + "' was of the wrong format", ex);
+                }
+            }
+            else
+            {
+                return creator();
+            }
+        }
+
+        /// <summary>
+        /// Sets the value for the given key to null if it is not already set
+        /// Returns true if the value was set (it was previously unset)
+        /// Does not mark the key as unobserved
+        /// </summary>
+        public bool SetIfUnset(string key)
+        {
+            return SetIfUnset(key, null);
+        }
+
+        /// <summary>
+        /// Sets the value for the given key to null
+        /// Does not mark the key as unobserved
+        /// </summary>
+        public void Set(string key)
+        {
+            SetInternal(key, null);
+        }
+
+        /// <summary>
+        /// Sets the value for the given key to null
+        /// Does not mark the key as unobserved
+        /// </summary>
+        public void Set(string key, string value)
+        {
+            SetInternal(key, value);
+        }
+
+        /// <summary>
+        /// Clears the value for the given key if it is already set
+        /// Returns true if the value was cleared (it was previously set)
+        /// Does not mark the key as observed
+        /// </summary>
+        public bool ClearIfSet(string key)
+        {
+            return Table.Remove(key);
+        }
+
+        /// <summary>
+        /// Sets the value for the given key to null if it is not already set
+        /// Returns true if the value was set (it was previously unset)
+        /// Marks the key as unobserved if it is set
+        /// </summary>
+        public bool SetUnobservedIfUnset(string key)
+        {
+            return SetUnobservedIfUnset(key, null);
+        }
+        
+        /// <summary>
+        /// Sets the value for the given key to given value if it is not already set
+        /// Returns true if the value was set (it was previously unset)
+        /// Does not mark the key as unobserved
+        /// </summary>
+        public bool SetIfUnset(string key, string value)
+        {
+            if (!IsSet(key))
+            {
+                SetInternal(key, value, false);
+
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+        
+        /// <summary>
+        /// Sets the value for the given key to given value if it is not already set
+        /// Returns true if the value was set (it was previously unset)
+        /// Marks the key as unobserved if it is set
+        /// </summary>
+        public bool SetUnobservedIfUnset(string key, string value)
+        {
+            if (!IsSet(key))
+            {
+                SetInternal(key, value);
+
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// Creates a new CliParams object with the same set of parameters and equality comparer
+        /// </summary>
+        public CliParams CloneCliParams()
+        {
+            return new CliParams(this);
+        }
+
+        /// <summary>
+        /// Enumerates all unobserved keys
+        /// </summary>
+        public IEnumerable<string> EnumerateUnobsered() => Unobserved.Keys;
+
+        private static string EscapeAndQuote(string str)
+        {
+            str = StringHelpers.Escape(str, '"', '`');
+            if (str.IndexOfAny(StringHelpers.DefaultDelimiters.ToArray()) > 0)
+            {
+                str = '"' + str + '"';
+            }
+
+            return str;
+        }
+
+        public string[] ToStringArray()
+        {
+            return Table.Select(kv => EscapeAndQuote(kv.Key) + (kv.Value == null ? "" : "=" + EscapeAndQuote(kv.Value))).ToArray();
+        }
+
+        public string ToStringLine()
+        {
+            return string.Join(" ", ToStringArray());
+        }
+
+        /// <summary>
+        /// Invaludes parameters from the other <see cref="CliParams"/> instance where the key is unset in this instance.
+        /// </summary>
+        /// <param name="other">The other instance.</param>
+        public void IncludeWhereUnset(CliParams other)
+        {
+            foreach (var vk in other.Table)
+            {
+                if (!IsSet(vk.Key))
+                {
+                    Set(vk.Key, vk.Value);
+                }
+            }
+        }
+    }
+
+    public interface ICliProvider
+    {
+        string Key { get; }
+        void Run(TextWriter console, CliParams clips);
+    }
+
+    public class Cli : ICli
+    {
+        public static string PlatformTopDir
+        {
+            get
+            {
+                var platform = System.Environment.OSVersion.Platform;
+                string topdir = platform == PlatformID.Unix ? "./M4MExperiments" : "C:/M4MExperiments"; // ik ben lui
+                return topdir;
+            }
+        }
+
+        public static Cli PrepareDefaultCli(IDensePopulationExperimentFactoryCliFactory feedbackFactoryCliFactory = null, string defaultTopdir = null, Dictionary<string, Action<TextWriter,string, CliParams>> genSets = null, IEnumerable<ICliProvider> extraProviders = null, IPlotExporter plotExporter = null, IEnumerable<ICliPlotter> extraPlotters = null)
+        {
+            var exp = new CliExp(feedbackFactoryCliFactory ?? new CommonFeedbackFactoryCliFactory(CommonFeedbackFactoryCliFactory.DefaultConfiguratorFactories));
+            var run = new CliRun(exp);
+            var gen = new CliGen(defaultTopdir ?? PlatformTopDir, genSets ?? CliGen.DefaultGenSets);
+            var plot = new CliPlot(plotExporter ?? new SimplePdfPlotExporter(), CliPlot.CreateDefaultPlotters().Concat(extraPlotters ?? Enumerable.Empty<ICliPlotter>()), CliPlot.DefaultPresets);
+            var spit = new CliSpit();
+            var tracee = new CliTracee();
+            var traces = new CliTraces();
+            var concat = new CliConcat();
+            var extract = new CliExtract();
+            var project = new CliProject();
+            var analyse = new CliAnalyse(CliAnalyse.CreateDefaultAnalysers());
+            var hebbian = new CliHebbian();
+            var reconfig = new CliReconfig(CliReconfig.DefaultPresets);
+            var bestiary = new CliBestiary();
+            var satgroup = new CliSatGroup();
+            var multiplot = new CliMultiPlot(plot);
+            var runongroup = new CliRunonGroup();
+            var stackGroup = new CliStackGroup();
+            var sparseswitchers = new CliSparseSwitchers();
+            var switchingModules = new CliSwitchingModules();
+
+            Cli cli = new Cli(new ICliProvider[] { exp, run, gen, plot, spit, tracee, traces, concat, extract, project, analyse, hebbian, reconfig, bestiary, satgroup, multiplot, runongroup, stackGroup, sparseswitchers, switchingModules }.Concat(extraProviders ?? Enumerable.Empty<ICliProvider>()));
+            return cli;
+        }
+
+        public Cli(IEnumerable<ICliProvider> providers)
+        {
+            Providers = providers.ToArray();
+        }
+
+        public ICliProvider[] Providers { get; }
+
+        public static void LoadParamFiles(CliParams clips, string referenceDirectory = ".")
+        {
+            var files = new HashSet<string>();
+            var due = new Stack<string>();
+
+            void acquireParamFiles(CliParams _clips, string refDir)
+            {
+                var paramFiles = _clips.Get("paramfile", null);
+                if (paramFiles != null)
+                {
+                    foreach (var candidate in StringHelpers.Split(paramFiles, new CodePoint[] { ';' }, '"', '`'))
+                    {
+                        var fullPath = System.IO.Path.GetFullPath(System.IO.Path.Combine(refDir, candidate));
+                        if (files.Add(fullPath))
+                        {
+                            due.Push(fullPath);
+                        }
+                    }
+                }
+            }
+
+            acquireParamFiles(clips, referenceDirectory);
+
+            while (due.Count > 0)
+            {
+                var next = due.Pop();
+                var _clips = new CliParams();
+                _clips.ConsumeFile(next);
+                acquireParamFiles(_clips, System.IO.Path.GetDirectoryName(next));
+                clips.IncludeWhereUnset(_clips);
+            }
+        }
+
+        public void Run(TextWriter console, string[] args)
+        {
+            CliParams clips = new CliParams();
+            clips.Consume(args);
+            LoadParamFiles(clips);
+
+            foreach (var provider in Providers)
+            {
+                if (clips.IsSet(provider.Key))
+                {
+                    provider.Run(console, clips);
+                    PrintUnobserved(console, clips);
+
+                    return;
+                }
+            }
+
+            console.WriteLine("Available providers:");
+
+            foreach (var provider in Providers)
+                console.WriteLine(provider.Key);
+        }
+
+        public static void PrintUnobserved(TextWriter console, CliParams clips)
+        {
+            bool doNotListUnobserved = clips.IsSet("quiet") || clips.IsSet("donotlistunobserved");
+            if (!doNotListUnobserved)
+            {
+                var unobserved = clips.EnumerateUnobsered().ToArray();
+                if (unobserved.Length > 0)
+                    console.WriteLine("Unobserved CliParams: " + string.Join(", ", unobserved));
+            }
+        }
+    }
+
+    public class CliPrompt : ICli
+    {
+        public CliPrompt(Cli cli)
+        {
+            Cli = cli;
+        }
+
+        public Cli Cli { get; }
+
+        public void Run(TextWriter console, string[] args)
+        {
+            CliParams clips = new CliParams();
+            clips.Consume(args);
+            CliParams diagClips = new CliParams();
+            diagClips.Consume(args);
+
+            bool quiet = clips.IsSet("quiet");
+
+            if (diagClips.IsSet("diag") && !diagClips.IsSet("noprediag"))
+            {
+                console.WriteLine("Start Pre-Diagnostics");
+                PrintDiagnostics(console, clips, diagClips);
+                console.WriteLine("End Pre-Diagnostics");
+            }
+
+            try
+            {
+                if (clips.IsSet("MathNetUseMultiThreading"))
+                {
+                    MathNet.Numerics.Control.UseMultiThreading();
+                }
+
+                if (clips.IsSet("forceInvarientCulture") || clips.IsSet("fic"))
+                {
+                    CliBodge.ForceInvarientCulture();
+                    if (!quiet)
+                        console.WriteLine("Forced Invarient Culture");
+                }
+
+                if (clips.IsSet("prompt"))
+                {
+                    console.WriteLine("M4M Prompt, 'quit' to exit");
+                    RunPrompt();
+                }
+                else
+                {
+                    bool timeMe = clips.IsSet("timeme");
+                    var sw = timeMe ? new System.Diagnostics.Stopwatch() : null;
+
+                    sw?.Restart();
+                    Cli.Run(console, args);
+                    sw?.Stop();
+
+                    if (timeMe)
+                    {
+                        console.WriteLine(sw.ElapsedMilliseconds + "ms");
+                    }
+                }    
+            }
+            catch (Exception ex)
+            {
+                console.WriteLine("~~~ Exception Thrown at "  + DateTime.Now.ToString("O") + " ~~~ ");
+                console.WriteLine(ex.Message);
+                console.WriteLine(ex.StackTrace);
+
+                while (ex.InnerException != null)
+                {
+                    ex = ex.InnerException;
+                    console.WriteLine("Inner Exception");
+                    console.WriteLine(ex.Message);
+                    console.WriteLine(ex.StackTrace);
+                }
+
+                if (diagClips.IsSet("rethrow"))
+                    throw;
+            }
+            
+            if (diagClips.IsSet("diag") && !diagClips.IsSet("nopostdiag"))
+            {
+                console.WriteLine("Start Post-Diagnostics");
+                CliParams clips2 = new CliParams();
+                clips2.Consume(args);
+                PrintDiagnostics(console, clips, diagClips);
+                console.WriteLine("End Post-Diagnostics");
+            }
+        }
+
+        public void RunPrompt()
+        {
+            while (true)
+            {
+                Console.Write("M4M> ");
+                string line = Console.ReadLine();
+                string[] args = line.Split(' ');
+
+                CliParams clips = new CliParams();
+                clips.Consume(args);
+
+                bool doQuit = clips.IsSet("quit") || clips.IsSet("exit");
+                if (doQuit)
+                    break;
+
+                Cli.Run(System.Console.Out, args);
+            }
+        }
+
+        private static string TryGetAssemblyLocation(System.Reflection.Assembly assembly)
+        {
+            try
+            {
+                return assembly.Location;
+            }
+            catch (Exception ex)
+            {
+                return "(" + ex.Message + ")";
+            }
+        }
+
+        public static void PrintDiagnostics(TextWriter console, CliParams clips, CliParams diagClips)
+        {
+            console.WriteLine("~~ M4M Diag ~~~");
+            console.WriteLine("DateTime:\t" + DateTime.Now.ToString("O"));
+            console.WriteLine("Entry Assembly Location:\t" + TryGetAssemblyLocation(System.Reflection.Assembly.GetEntryAssembly()));
+            console.WriteLine("M4M.New Assembly Location:\t" + TryGetAssemblyLocation(typeof(Cli).Assembly));
+            console.WriteLine("M4M.Model Assembly Location:\t" + TryGetAssemblyLocation(typeof(ExperimentConfiguration).Assembly));
+            console.WriteLine("NetState Assembly Location:\t" + TryGetAssemblyLocation(typeof(NetState.AutoState.AutoGraphSerialisation).Assembly));
+            console.WriteLine("MathNET Assembly Location:\t" + TryGetAssemblyLocation(typeof(MathNet.Numerics.LinearAlgebra.Matrix<double>).Assembly));
+            console.WriteLine("OxyPlot Assembly Location:\t" + TryGetAssemblyLocation(typeof(OxyPlot.PlotModel).Assembly));
+            console.WriteLine("OS Version:\t" + System.Environment.OSVersion);
+            console.WriteLine("Runtime Version:\t" + System.Environment.Version);
+            console.WriteLine("Culture:\t" + System.Threading.Thread.CurrentThread.CurrentCulture.DisplayName);
+            console.WriteLine("UI Culture:\t" + System.Threading.Thread.CurrentThread.CurrentUICulture.DisplayName);
+            console.WriteLine("NetState DisallowAssemblyLoading:\t" + NetState.AutoState.AutoSerialisationHelpers.DisallowAssemblyLoading);
+            console.WriteLine("NetState EnableAllowLoadWhiteList:\t" + NetState.AutoState.AutoSerialisationHelpers.EnableAllowLoadWhiteList);
+            console.WriteLine("NetState AllowLoadWhiteList:\t" + string.Join(", ", NetState.AutoState.AutoSerialisationHelpers.AllowLoadWhiteList));
+            console.WriteLine("");
+
+            if (diagClips.Get("help", bool.Parse, true))
+            {
+                console.WriteLine("# Diagnostics Options");
+                console.WriteLine(" - stacktrace: prints a stacktrace, so the calling code is apparent");
+                console.WriteLine(" - clips: prints the CliParams");
+                console.WriteLine(" - assemblies: prints information for loaded assemblies");
+                console.WriteLine("   - types: prints type information for loaded assemblies");
+                console.WriteLine("");
+            }
+
+            if (diagClips.IsSet("stacktrace"))
+            {
+                console.WriteLine("# StackTrace");
+                console.WriteLine(System.Environment.StackTrace);
+                console.WriteLine("");
+            }
+
+            if (diagClips.IsSet("clips"))
+            {
+                console.WriteLine("# CliParams");
+                foreach (var k in clips.Entries)
+                {
+                    var v = clips.Get(k);
+                    if (v == null)
+                    {
+                        console.WriteLine($"{k}\t(null)");
+                    }
+                    else
+                    {
+                        console.WriteLine($"{k}\t'{v}'");
+                    }
+                }
+                console.WriteLine("");
+            }
+
+            if (diagClips.IsSet("assemblies"))
+            {
+                bool printTypes = diagClips.IsSet("types");
+
+                console.WriteLine("# Assemblies");
+                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+                {
+                    console.WriteLine(assembly.GetName().Name);
+                    console.WriteLine(" - FullName:\t" + assembly.GetName().FullName);
+                    console.WriteLine(" - Version:\t" + assembly.GetName().Version);
+                    console.WriteLine(" - Location:\t" + assembly.Location);
+                    if (ManifestResources.TryReadResourceString(assembly, "Info.version.txt", out var infoVersionString))
+                        console.WriteLine(" - Info.Version:\t" + infoVersionString);
+                    if (ManifestResources.TryReadResourceString(assembly, "Info.buildinfo.txt", out var infoBuildInfoString))
+                        console.WriteLine(" - Info.BuildInfo:\n     " + infoBuildInfoString.Replace("\n", "\n     "));
+
+                    if (printTypes)
+                    {
+                        foreach (var module in assembly.GetLoadedModules())
+                        {
+                            console.WriteLine(" - Module " + module.Name);
+
+                            foreach (var type in module.GetTypes())
+                            {
+                                console.WriteLine("   - " + type.FullName);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public class ManifestResources
+    {
+        public static System.Text.Encoding Enc { get; } =  new System.Text.UTF8Encoding(false);
+
+        public static string ReadResourceString(System.Reflection.Assembly assembly, string name)
+        {
+            var qualifiedName = assembly.GetName().Name + "." + name;
+            var resourceStream = assembly.GetManifestResourceStream(qualifiedName); 
+            using (var reader = new System.IO.StreamReader(resourceStream, Enc)) 
+            { 
+                return reader.ReadToEnd(); 
+            }
+        }
+
+        public static bool TryReadResourceString(System.Reflection.Assembly assembly, string name, out string contents)
+        {
+            try
+            {
+                var qualifiedName = assembly.GetName().Name + "." + name;
+                var resourceStream = assembly.GetManifestResourceStream(qualifiedName);
+                using (var reader = new System.IO.StreamReader(resourceStream, Enc))
+                {
+                    contents = reader.ReadToEnd();
+                }
+                return true;
+            }
+            catch
+            {
+                contents = null;
+                return false;
+            }
+        }
+    }
+
+    public static class CliBodge
+    {
+        public static void ForceInvarientCulture()
+        {
+            // we do this, so that numerical formatting is always US style, and we can keep using commas to separate things (because I'm lazy)
+            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
+            System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliAnalyse.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliAnalyse.cs
new file mode 100644
index 0000000..b4cb657
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliAnalyse.cs
@@ -0,0 +1,387 @@
+using M4M.Modular;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace M4M
+{
+    public interface ICliAnalyser
+    {
+        string Prefix { get; }
+        string Name { get; }
+        void Run(TextWriter console, CliParams clips, string filename);
+    }
+
+    public class CliAnalyse : ICliProvider
+    {
+        public static IEnumerable<ICliAnalyser> CreateDefaultAnalysers()
+        {
+            var defaultAnalysers = new ICliAnalyser[] {
+                new CliRcsAnalyser(),
+                new CliExpAnalyser(),
+                };
+
+            return defaultAnalysers;
+        }
+
+        public string Key => "analyse";
+
+        public ICliAnalyser[] Analysers { get; }
+
+        public CliAnalyse(IEnumerable<ICliAnalyser> analysers)
+        {
+            Analysers = analysers.ToArray();
+        }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            string infname = clips.Get("analyse");
+
+            if (infname != null)
+            {
+                string outfname = clips.Get("out", infname);
+
+                string ifname = System.IO.Path.GetFileName(infname);
+                string ofname = System.IO.Path.GetFileName(outfname);
+
+                string title = clips.Get("title", ofname);
+
+                bool any = false;
+
+                foreach (var analyser in Analysers)
+                {
+                    if (ifname.StartsWith(analyser.Prefix))
+                    {
+                        any = true;
+                        analyser.Run(console, clips, infname);
+                    }
+                }
+
+                if (any)
+                    return;
+
+                console.WriteLine("Unsure how to analyse " + infname);
+            }
+
+            console.WriteLine("Recognised files (by start of file-name) are:");
+
+            foreach (var analyser in Analysers)
+            {
+                console.WriteLine(analyser.Prefix + " for " + analyser.Name);
+            }
+        }
+    }
+
+    public class CliRcsAnalyser : ICliAnalyser
+    {
+        public string Prefix => "rcs";
+        public string Name => "RcsAnalyser";
+
+        public void Run(TextWriter console, CliParams clips, string filename)
+        {
+            var trajectories = Analysis.LoadTrajectories(filename, out int samplePeriod);
+
+            int start = clips.Get("start", int.Parse, 0);
+            int end = clips.Get("end", int.Parse, trajectories[0].Length * samplePeriod);
+
+            int skip = start / samplePeriod;
+            int take = Math.Min((end + samplePeriod - start) / samplePeriod, trajectories[0].Length - skip);
+            trajectories = trajectories.Select(traj => traj.Skip(skip).Take(take).ToArray()).ToArray();
+
+            double[] thresholds = clips.Get("thresholds", s => s.Split(';').Select(double.Parse).ToArray());
+
+            var samples = ComputeHuskynessesSamples(console, clips, trajectories, samplePeriod, start, out var modules);
+
+            int hnum = 5;
+
+            //
+            console.WriteLine("## Huskyness thresholds");
+            console.WriteLine("Threshold\tEpoch");
+            foreach (var threshold in thresholds)
+            {
+                var index = DetectThreshold(samples, threshold, hnum);
+
+                if (index > 0)
+                {
+                    var epoch = start + index * samplePeriod;
+                    console.WriteLine($"{threshold}\t{epoch}");
+                }
+                else
+                {
+                    console.WriteLine($"{threshold}\t(not reached)");
+                }
+            }
+            console.WriteLine("");
+
+            //
+            console.WriteLine("## Terminal DTM Info");
+            var terminalDtm = Analysis.ExtractMatrix(trajectories, trajectories[0].Length - 1, modules.ElementCount, modules.ElementCount);
+            SimplePdfPlotExporter.ExportToPdf(CliGenomePlotter.PlotDtm(clips, terminalDtm, "test"), "test.pdf", 200, 200, false, true, null);
+            var terminalDtmInfo = new DtmInfo(terminalDtm, DtmClassification.DiscerneTrueModules(terminalDtm, DtmClassification.ComputeAutoThreshold(terminalDtm, 10.0)));
+            console.WriteLine(terminalDtmInfo.Summary);
+            console.WriteLine("");
+        }
+
+        public static double[][] ComputeHuskynessesSamples(TextWriter console, CliParams clips, double[][] trajectories, int samplePeriod, long startTime, out Modular.Modules modules)
+        {
+            double completeThreshold = clips.Get("huskythreshold", double.Parse, 0.9);
+            int N = (int)Math.Round(Math.Sqrt(trajectories.Length));
+            int resolution = clips.Get("resolution", int.Parse, 1);
+
+            modules = CliPlotHelpers.GuessModules(console, clips, N);
+            var mts = modules.ToArray();
+
+            var dtms = Analysis.EnumerateMatricies(trajectories, N, N, 0, trajectories[0].Length, true);
+            var huskynesses = dtms.Select(dtm => Analysis.ComputeModuleHuskyness(dtm, mts)).ToArray();
+            bool complete = huskynesses.FirstIndex(hs => hs.All(h => h >= completeThreshold && h <= 1), out int completeIndex, out _);
+
+            return huskynesses;
+        }
+
+        public static double[][] ComputeModuleIndependenceSamples(TextWriter console, CliParams clips, double[][] trajectories, int samplePeriod, long startTime, out Modular.Modules modules)
+        {
+            double completeThreshold = clips.Get("ModuleIndependencethreshold", double.Parse, 0.9);
+            int N = (int)Math.Round(Math.Sqrt(trajectories.Length));
+            int resolution = clips.Get("resolution", int.Parse, 1);
+
+            modules = CliPlotHelpers.GuessModules(console, clips, N);
+            var mts = modules.ToArray();
+
+            var dtms = Analysis.EnumerateMatricies(trajectories, N, N, 0, trajectories[0].Length, true);
+            var huskynesses = dtms.Select(dtm => Analysis.ComputeModuleHuskyness(dtm, mts)).ToArray();
+            bool complete = huskynesses.FirstIndex(hs => hs.All(h => h >= completeThreshold && h <= 1), out int completeIndex, out _);
+
+            return huskynesses;
+        }
+
+        /// <summary>
+        /// returns -1 if it never qualified
+        /// </summary>
+        public static int DetectThreshold(double[][] samples, double threshold, int hnum)
+        {
+            bool complete = samples.HystericalFirstIndex(hs => hs.All(h => h >= threshold && h <= 1), hnum, out int completeIndex, out _);
+
+            if (complete)
+            {
+                return completeIndex;
+            }
+            else
+            {
+                return -1;
+            }
+        }
+    }
+
+    public class CliExpAnalyser : ICliAnalyser
+    {
+        public string Prefix => "epoch";
+
+        public string Name => "Experiment";
+
+        public void Run(TextWriter console, CliParams clips, string filename)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Try one of: signal");
+            }
+
+            if (clips.IsSet("signal"))
+            {
+                AnalyseDaveSignal(console, clips, filename);
+            }
+            else
+            {
+                console.WriteLine("Try one of: signal");
+            }
+        }
+
+        public static void AnalyseDistribution(TextWriter console, CliParams clips, string filename)
+        {
+        }
+
+        public static void AnalyseDaveSignal(TextWriter console, CliParams clips, string filename)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Measures the 'Dave' signal for a given genome in each target of the environment");
+                console.WriteLine(" - exposureepoch (default 1)");
+                console.WriteLine(" - seed (default 1)");
+                console.WriteLine(" - resettargets");
+                console.WriteLine(" - variability, modules (default is to vary all traits independently)");
+                console.WriteLine(" - closed, modules (default is the variability modules)");
+                console.WriteLine(" - deltamag (default is MB pulled from expfile)");
+                console.WriteLine(" - postfix (default is none)");
+            }
+
+            var exp = PopulationExperiment<DenseIndividual>.Load(filename);
+            var config = exp.PopulationConfig.ExperimentConfiguration;
+
+            int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+            int seed = clips.Get("seed", int.Parse, 1);
+            var resetTargets = clips.IsSet("resettargets");
+            var variability = clips.GetOrCreate("variability", MultiModulesStringParser.Instance.Parse, () => Modules.CreateBlockModules(config.Size, 1));
+            var closed = clips.Get("closed", MultiModulesStringParser.Instance.Parse, variability);
+            console.WriteLine($"Variability Modules: {variability.ToString()}");
+            console.WriteLine($"Closed Modules: {closed?.ToString() ?? "(null)"}");
+
+            var rand = new CustomMersenneTwister(seed);
+            var context = new ModelExecutionContext(rand);
+
+            var template = exp.Population.ExtractAll()[0].Genome;
+            var deltaMag = clips.Get("deltamag", double.Parse, config.ReproductionRules.DevelopmentalTransformationMatrixMutationSize);
+            var deltaMode = new CellDeltaMode(); // TODO: make this configurable
+
+            var postfix = clips.Get("postfix", "");
+
+            foreach (var target in config.Targets)
+            {
+                console.WriteLine($"Target {target.FullName}");
+
+                if (target is IPerfectPhenotypeTarget ppt)
+                {
+                    if (resetTargets)
+                    {
+                        ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                        target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+                    }
+
+                    target.NextGeneration(rand, config.JudgementRules);
+
+                    var signal = MeasureDaveSignal(context, ppt, config.DevelopmentRules, config.JudgementRules, template, deltaMag, deltaMode, variability, closed, out var bothSignals, out var correctSignals, out var incorrectSignals, out var neitherSignals, out var integral, out var sampleCount);
+                    console.WriteLine($" Signal: {signal}    = (c-i)/(c+i+2b)");
+                    console.WriteLine($"  Both:      {bothSignals}");
+                    console.WriteLine($"  Correct:   {correctSignals}");
+                    console.WriteLine($"  Incorrect: {incorrectSignals}");
+                    console.WriteLine($"  Neither:   {neitherSignals}");
+
+                    console.WriteLine($" IntegralSignal: {RegularisationHelpers.L1Sum(integral)}    = L1(integral)");
+
+                    var signalGenome = template.Clone(context, null, integral);
+                    var signalAverageGenome = template.Clone(context, null, integral / sampleCount);
+                    var updatedGenome = template.Clone(context, null, integral + template.TransMat);
+
+                    Analysis.SaveGenome($"genomeSignal{postfix}.dat", signalGenome);
+                    Analysis.SaveGenome($"genomeSignalAverage{postfix}.dat", signalAverageGenome);
+                    Analysis.SaveGenome($"genomeUpdated{postfix}.dat", updatedGenome);
+                }
+                else
+                {
+                    console.WriteLine($" Target {target.FullName} is not a PerfectPhenotypeTarget.");
+                }
+            }
+        }
+
+        public static bool[,] PrepareOpen(int size, Modules closedModules)
+        {
+            int n = closedModules.ElementCount;
+
+            var open = new bool[size, size];
+
+            // open everything
+            for (int r = 0; r < size; r++)
+            {
+                for (int c = 0; c < size; c++)
+                {
+                    open[r, c] = true;
+                }
+            }
+
+            if (closedModules != null)
+            {
+                // close everything within a module
+                foreach (var m in closedModules.ModuleAssignments)
+                {
+                    foreach (var r in m)
+                    {
+                        foreach (var c in m)
+                        {
+                            open[r, c] = false;
+                        }
+                    }
+                }
+            }
+
+            return open;
+        }
+
+        public static double MeasureDaveSignal(ModelExecutionContext context, IPerfectPhenotypeTarget target, DevelopmentRules drules, JudgementRules jrules, DenseGenome template, double deltaMag, IDeltaMode deltaMode, Modules varitationalModules, Modules closedModules, out int bothSignals, out int correctSignals, out int incorrectSignals, out int neitherSignals, out Linear.Matrix<double> integral, out int sampleCount)
+        {
+            var perfect = target.PreparePerfectP();
+
+            var correctness = perfect.Correlate();
+
+            var n = target.Size;
+
+            var genome = template.Clone(context);
+
+            var open = PrepareOpen(n, closedModules);
+
+            // zero signals
+            bothSignals = 0;
+            correctSignals = 0;
+            incorrectSignals = 0;
+            neitherSignals = 0;
+
+            integral = Linear.CreateMatrix.Dense(n, n, 0.0);
+            sampleCount = 0;
+
+            // enumerate combinations of modules
+            foreach (var gbools in Combinatorics.EnumerateBooleanAssignments(varitationalModules.ModuleCount))
+            {
+                sampleCount++;
+
+                // fill genome with modules
+                for (int mi = 0; mi < varitationalModules.ModuleCount; mi++)
+                {
+                    var v = gbools[mi] ? 1.0 : -1.0;
+
+                    foreach (var i in varitationalModules.ModuleAssignments[mi])
+                    {
+                        genome.InitialState[i] = v;
+                    }
+                }
+
+                // perform DeltaTest no open entries
+                var deltaTest = DeltaTest.Test(genome, drules, jrules, target, deltaMag, deltaMode, open);
+
+                // record signals
+                for (int r = 0; r < n; r++)
+                {
+                    for (int c = 0; c < n; c++)
+                    {
+                        if (!open[r, c])
+                            continue;
+
+                        switch (deltaTest.GetBeneficial(r, c))
+                        {
+                            case DeltaResult.Both:
+                                bothSignals++;
+                                break;
+                            case DeltaResult.Plus:
+                                integral[r, c]++;
+                                if (correctness[r, c] > 0)
+                                    correctSignals++;
+                                else if (correctness[r, c] < 0)
+                                    incorrectSignals++;
+                                break;
+                            case DeltaResult.Minus:
+                                integral[r, c]--;
+                                if (correctness[r, c] > 0)
+                                    incorrectSignals++;
+                                else if (correctness[r, c] < 0)
+                                    correctSignals++;
+                                break;
+                            case DeltaResult.Neither:
+                                neitherSignals++;
+                                break;
+                        }
+                    }
+                }
+            }
+
+            return (correctSignals - incorrectSignals) / (double)(correctSignals + incorrectSignals + bothSignals * 2);
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliBestiary.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliBestiary.cs
new file mode 100644
index 0000000..dec59e8
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliBestiary.cs
@@ -0,0 +1,735 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MathNet.Numerics.Random;
+using SixLabors.Fonts;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.Primitives;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+
+namespace M4M
+{
+    public class CustomComparer : IComparer<string>
+    {
+        public int Compare(string x, string y)
+        {
+            if (double.TryParse(x, out var nx) && double.TryParse(y, out var ny))
+            {
+                return nx.CompareTo(ny);
+            }
+            else
+            {
+                return StringComparer.InvariantCultureIgnoreCase.Compare(x, y);
+            }
+        }
+    }
+
+    public interface IRandomVectorProvider
+    {
+        string Name { get; }
+        Linear.Vector<double> RandomVector(Misc.Range range, int size, RandomSource rand);
+    }
+
+    public class BinaryRandomVectorProvider : IRandomVectorProvider
+    {
+        public static BinaryRandomVectorProvider Instance = new BinaryRandomVectorProvider();
+
+        private BinaryRandomVectorProvider()
+        {
+            // nix
+        }
+
+        public string Name => "Binary";
+
+        public Linear.Vector<double> RandomVector(Misc.Range range, int length, RandomSource rand)
+        {
+            return Linear.CreateVector.Dense(length, i => rand.NextBoolean() ? range.Min : range.Max);
+        }
+    }
+    public class RandomVectorProviderPopulationResetOperation : IPopulationResetOperation<DenseIndividual>
+    {
+        public RandomVectorProviderPopulationResetOperation(IRandomVectorProvider randomVectorProvider)
+        {
+            RandomVectorProvider = randomVectorProvider ?? throw new ArgumentNullException(nameof(randomVectorProvider));
+        }
+
+        public IRandomVectorProvider RandomVectorProvider { get; }
+
+        public string Name => $"RandomVectorProviderResetOperation {RandomVectorProvider.Name}";
+
+        public void Reset(ModelExecutionContext context, Population<DenseIndividual> population, Misc.Range gResetRange, DevelopmentRules drules, ReproductionRules rrules, ITarget target)
+        {
+            population.Process(i =>
+            {
+                var clone = i.Clone(context);
+                clone.Genome.CopyOverInitialState(RandomVectorProvider.RandomVector(gResetRange, clone.Genome.Size, context.Rand));
+                return clone;
+            });
+        }
+    }
+
+    public class UniformRandomVectorProvider : IRandomVectorProvider
+    {
+        public static UniformRandomVectorProvider Instance = new UniformRandomVectorProvider();
+
+        private UniformRandomVectorProvider()
+        {
+            // nix
+        }
+
+        public string Name => "Uniform";
+
+        public Linear.Vector<double> RandomVector(Misc.Range range, int length, RandomSource rand)
+        {
+            return Linear.CreateVector.Dense(length, i => range.Sample(rand));
+        }
+    }
+
+    public class ZeroVectorProvider : IRandomVectorProvider
+    {
+        public static ZeroVectorProvider Instance = new ZeroVectorProvider();
+
+        private ZeroVectorProvider()
+        {
+            // nix
+        }
+
+        public string Name => "Zero";
+
+        public Linear.Vector<double> RandomVector(Misc.Range range, int length, RandomSource rand)
+        {
+            return Linear.CreateVector.Dense(length, 0.0);
+        }
+    }
+
+    public class CliBestiary : ICliProvider
+    {
+        public string Key => "bestiary";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            console.WriteLine("Note: Bestiary depends on shed loads of stuff (because ImageSharp depends on shed loads of stuff) so if it isn't working, try shuffling DLLs around (never tested under .NET Framework)");
+
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Bestiary, generates many phenotypes from an experiment's population and configuration. Takes the following parameters:\n" +
+                    " - bestiary (filename of expfile)\n" +
+                    " - gmode (vector random mode, either Mutate, Binary, or Uniform\n" +
+                    " - out (output file name; .png may be appended by default if you forgot the extension\n" +
+                    " - min (default -1)\n" +
+                    " - max (default +1)\n" +
+                    " - saturate (default false)\n" +
+                    " - count (default 100)\n" +
+                    " - seed (default 42)");
+                console.WriteLine("Alternatively, specify a 'dir' and it will tabulate it for you");
+            }
+
+            if (clips.IsSet("dir"))
+            {
+                TabulateGenomes(console, clips, clips.Get("dir"));
+            }
+            else if (clips.IsSet("net"))
+            {
+                DrawNet(console, clips, clips.Get("net"));
+            }
+            else
+            {
+                GenerateBestiary(console, clips);
+            }
+        }
+
+        // deficient for most purposes
+        private struct Pair<TL, TR>
+        {
+            public readonly TL Left;
+            public readonly TR Right;
+
+            public Pair(TL left, TR right)
+            {
+                Left = left;
+                Right = right;
+            }
+        }
+
+        /// <summary>
+        /// Draws 
+        /// </summary>
+        /// <param name="clips">CliParams</param>
+        /// <param name="filename">Genome or EpochSaveFile</param>
+        public static void DrawNet(TextWriter console, CliParams clips, string filename)
+        {
+            var outfile = clips.Get("out", filename + ".png");
+
+            DenseGenome genome;
+            if (System.IO.Path.GetFileName(filename).StartsWith("genome", StringComparison.InvariantCultureIgnoreCase))
+            {
+                genome = Analysis.LoadGenome(filename);
+            }
+            else
+            {
+                var exp = PopulationExperiment<DenseIndividual>.Load(filename);
+                genome = exp.Population.ExtractAll()[0].Genome;
+            }
+
+            float r = clips.Get("radius", float.Parse, 10f);
+            float s = clips.Get("spacing", float.Parse, 10f);
+
+            string arrangementString = clips.Get("arrangement");
+
+            if (arrangementString == "4x4std")
+            {
+                arrangementString = "0;1;4;5&2;3;6;7&8;9;12;13&10;11;14;15";
+            }
+
+            var nodeLines = arrangementString.Split('&').Select(l => l.Split(';').Select(int.Parse).ToArray()).ToArray();
+            int w = nodeLines.Max(l => l.Length);
+            int h = nodeLines.Length;
+
+            float g = r * 2f + s;
+
+            int imgWidth = (int)Math.Ceiling(w * g - s);
+            int imgHeight = (int)Math.Ceiling(h * g - s);
+
+            PointF[] nodeLocs = new PointF[genome.Size];
+            float y = r;
+            for (int j = 0; j < nodeLines.Length; j++)
+            {
+                var line = nodeLines[j];
+                float x = r;
+
+                for (int i = 0; i < line.Length; i++)
+                {
+                    var n = line[i];
+                    if (n >= 0)
+                    {
+                        nodeLocs[n] = new PointF(x, y);
+                    }
+
+                    x += g;
+                }
+
+                y += g;
+            }
+
+            var dtm = genome.TransMat;
+            double threshold = clips.Get("threshold", double.Parse, dtm.Enumerate().Max<double>(d => Math.Abs(d)) * 0.1);
+            
+            using (var img = new Image<Rgba32>(imgWidth, imgHeight))
+            {
+                DrawNetwork(img, dtm, r, 1.0f, Rgba32.Black, Rgba32.Red, Rgba32.Blue, threshold, nodeLocs);
+
+                console.WriteLine("Saving to " + outfile);
+                img.Save(outfile);
+            }
+        }
+
+        /// <summary>
+        /// Reads aliases from a file.
+        /// </summary>
+        /// <param name="filename"></param>
+        /// <returns></returns>
+        public static Dictionary<string, string> ReadAliases(string filename)
+        {
+            var aliases = new Dictionary<string, string>();
+            if (System.IO.File.Exists(filename))
+            {
+                foreach (var line in System.IO.File.ReadLines("xAliases.txt"))
+                {
+                    if (string.IsNullOrWhiteSpace(line))
+                        continue;
+                    var data = StringHelpers.Split(line).ToArray();
+                    aliases.Add(data[0], data[1]);
+                }
+            }
+            return aliases;
+        }
+
+        /// <summary>
+        /// Returns a function that takes a key and returns the value found in the dictionary or the key itself if it is not present in the dictionary.
+        /// </summary>
+        /// <param name="aliases"></param>
+        /// <returns></returns>
+        public static Func<string, string> Defaulting(Dictionary<string, string> aliases)
+        {
+            return (string key) => aliases.TryGetValue(key, out var found) ? found : key;
+        }
+
+        /// <summary>
+        /// Plots DTMs
+        /// </summary>
+        /// <param name="clips"></param>
+        /// <param name="dir"></param>
+        public static void TabulateGenomes(TextWriter console, CliParams clips, string dir)
+        {
+            var genomeFilename = clips.Get("gname", "genome_t.dat");
+            var outfile = clips.Get("out");
+            var min = clips.Get("min", double.Parse, -1.0);
+            var max = clips.Get("max", double.Parse, +1.0);
+            
+            Dictionary<Pair<string, string>, DenseGenome> Table = new Dictionary<Pair<string, string>, DenseGenome>();
+            SortedSet<string> xs = new SortedSet<string>(new CustomComparer());
+            SortedSet<string> ys = new SortedSet<string>(new CustomComparer());
+
+            DenseGenome example = null;
+
+            var xAliases = ReadAliases("xAliases.txt");
+            var aliasX = Defaulting(xAliases);
+
+            var yAliases = ReadAliases("yAliases.txt");
+            var aliasY = Defaulting(yAliases);
+
+            var xdirs = System.IO.Directory.GetDirectories(dir);
+            foreach (var xdir in xdirs)
+            {
+                var xaliasfile = System.IO.Path.Combine(xdir, "alias.txt");
+                var x = System.IO.File.Exists(xaliasfile)
+                    ? System.IO.File.ReadAllText(xaliasfile)
+                    : aliasX(System.IO.Path.GetFileName(xdir));
+                console.WriteLine(x);
+
+                var ydirs = System.IO.Directory.GetDirectories(xdir);
+                foreach (var ydir in ydirs)
+                {
+                    var yaliasfile = System.IO.Path.Combine(ydir, "alias.txt");
+                    var y = System.IO.File.Exists(yaliasfile)
+                        ? System.IO.File.ReadAllText(yaliasfile)
+                        : aliasY(System.IO.Path.GetFileName(ydir));
+                    console.WriteLine(y);
+
+                    string gpath = System.IO.Path.Combine(ydir, genomeFilename);
+
+                    console.WriteLine(gpath);
+                    if (System.IO.File.Exists(gpath))
+                    {
+                        DenseGenome g;
+
+                        if (genomeFilename.StartsWith("genome"))
+                        {
+                            var temp = Analysis.LoadGenome(gpath);
+                            g = temp;
+                        }
+                        else if (genomeFilename.StartsWith("epoch"))
+                        {
+                            var temp = PopulationExperiment<DenseIndividual>.Load(gpath);
+                            g = temp.Population.ExtractAll()[0].Genome;
+                        }
+                        else
+                        {
+                            throw new NotSupportedException("Can't extract a dense genome from " + gpath);
+                        }
+
+                        Table.Add(new Pair<string, string>(x, y), g);
+                        xs.Add(x);
+                        ys.Add(y);
+
+                        if (example == null || example.Size < g.Size)
+                            example = g;
+                    }
+                }
+            }
+
+            if (example == null)
+            {
+                console.WriteLine("No genomes found; try qualifying gname");
+                return;
+            }
+
+            bool axes = clips.Get("axes", bool.Parse, false);
+
+            // cell dimensions
+            int cw = clips.Get("cw", int.Parse, 1);
+            int ch = clips.Get("ch", int.Parse, 1);
+
+            // spacing dimensions
+            int sw = clips.Get("sw", int.Parse, 1);
+            int sh = clips.Get("sh", int.Parse, 1);
+
+            bool labels = clips.Get("labels", bool.Parse, true);
+            
+            string fontname = clips.Get("font", "Consolas");
+            bool whot = clips.IsSet("whot") || !clips.IsSet("bhot");
+
+            // matrix width and height
+            int mw = example.TransMat.ColumnCount * cw;
+            int mh = example.TransMat.RowCount * ch;
+
+            // column and row counts
+            int c = ys.Count;
+            int r = xs.Count;
+
+            int fontSize = clips.Get("fontSize", int.Parse, Math.Max(mw, mh));
+            Font font = labels ? SystemFonts.CreateFont(fontname, fontSize, FontStyle.Regular) : null;
+            
+            int tp = 4;
+            var xTitle = clips.Get("xtitle", null);
+            var yTitle = clips.Get("ytitle", null);
+            int tw = xTitle == null ? 0 : tp + (int)TextMeasurer.Measure(xTitle, new RendererOptions(font)).Height;
+            int th = yTitle == null ? 0 : tp + (int)TextMeasurer.Measure(yTitle, new RendererOptions(font)).Height;
+
+            int lp = 4; // padding
+            int lw = labels ? (int)xs.Max(x => TextMeasurer.Measure(x, new RendererOptions(font)).Width) + lp : 0;
+            int lh = labels ? (int)ys.Max(y => TextMeasurer.Measure(y, new RendererOptions(font)).Width) + lp : 0;
+
+            int ap = axes ? 8 : 0;
+            int aw = ap;
+            int ah = ap;
+
+            using (var img = new Image<Rgba32>(tw + lw + aw + (mw + sw) * c - sw, th + lh + ah + (mh + sh) * r - sh))
+            {
+                // draw titles
+                if (xTitle != null)
+                {
+                    var m = TextMeasurer.Measure(xTitle, new RendererOptions(font));
+                    var pen = new Pen(Color.Black, 1f);
+                    var brush = new SolidBrush(Color.Black);
+
+                    img.Mutate(ctx =>
+                    {
+                        ctx.RotateFlip(RotateMode.Rotate90, FlipMode.None);
+                        ctx.DrawText(xTitle, font, brush, pen, new PointF(img.Width / 2 - m.Width / 2, 0));
+                        ctx.RotateFlip(RotateMode.Rotate270, FlipMode.None);
+                    });
+                }
+                if (yTitle != null)
+                {
+                    var m = TextMeasurer.Measure(yTitle, new RendererOptions(font));
+                    var pen = new Pen(Color.Black, 1f);
+                    var brush = new SolidBrush(Color.Black);
+
+                    img.Mutate(ctx =>
+                    {
+                        ctx.DrawText(yTitle, font, brush, pen, new PointF(img.Width / 2 - m.Width / 2, 0));
+                    });
+                }
+
+                // draw labels
+                if (labels)
+                {
+                    var pen = new Pen(Color.Black, 1f);
+                    var brush = new SolidBrush(Color.Black);
+
+                    img.Mutate(ctx =>
+                    {
+                        int j = th + lh + ah;
+                        foreach (var x in xs)
+                        {
+                            ctx.DrawText(x, font, brush, pen, new PointF(tw, j));
+                            j += mh + sh;
+                        }
+                    });
+                    
+                    img.Mutate(ctx =>
+                    {
+                        ctx.RotateFlip(RotateMode.Rotate270, FlipMode.None);
+                        int i = 0;
+                        foreach (var y in ys.Reverse())
+                        {
+                            ctx.DrawText(y, font, brush, pen, new PointF(th, i));
+                            i += mw + sw;
+                        }
+                        ctx.RotateFlip(RotateMode.Rotate90, FlipMode.None);
+                    });
+                }
+
+                // draw arrows
+                if (axes)
+                {
+                    var pen = new Pen(Color.Black, 2f);
+                    img.Mutate(ctx =>
+                    {
+                        var yend = new PointF(tw + lw + aw / 2, img.Height);
+                        ctx.DrawLines(pen, new[] { new PointF(tw + lw + aw / 2, th + lh + ah), yend });
+                        ctx.DrawLines(pen, new[] { new PointF(tw + lw + 1, img.Height - 8), yend, new PointF(tw + lw + aw - 1, img.Height - 8) });
+
+                        var xend = new PointF(img.Width, th + lh + ah / 2);
+                        ctx.DrawLines(pen, new[] { new PointF(tw + lw + aw, th + lh + ah / 2), xend });
+                        ctx.DrawLines(pen, new[] { new PointF(img.Width - 8, th + lh + 1), xend, new PointF(img.Width - 8, th + lh + ah - 1) });
+                    });
+                }
+
+                // draw dtms
+                if (true)
+                {
+                    int j = th + lh + ah;
+                    foreach (var x in xs)
+                    {
+                        int i = tw + lw + aw;
+                        foreach (var y in ys)
+                        {
+                            if (Table.TryGetValue(new Pair<string, string>(x, y), out var g))
+                            {
+                                DrawMatrix(img, g.TransMat, i, j, min, max, whot, cw, ch);
+                            }
+
+                            i += mw + sw;
+                        }
+
+                        j += mh + sh;
+                    }
+                }
+
+                console.WriteLine("Saving to " + outfile);
+                img.Save(outfile);
+            }
+        }
+
+        /// <summary>
+        /// Plots generated phenotypes
+        /// </summary>
+        public static void GenerateBestiary(TextWriter console, CliParams clips)
+        {
+            var filename = clips.Get("bestiary");
+            var seed = clips.Get("seed", int.Parse, 42);
+            var gmode = clips.Get("gmode");
+            var outfile = clips.Get("out");
+            var min = clips.Get("min", double.Parse, -1.0);
+            var max = clips.Get("max", double.Parse, +1.0);
+            var saturate = clips.Get("saturate", bool.Parse, false);
+            bool whot = clips.IsSet("whot") || !clips.IsSet("bhot");
+            int moduleCount = clips.Get("moduleCount", int.Parse, -1);
+            int generations = clips.Get("generations", int.Parse, 0); // the number of generations to run each individual for after variation
+
+            if (!System.IO.Path.GetFileName(outfile).Contains("."))
+                outfile += ".png";
+            
+            bool mutate = gmode.Equals("mutate", StringComparison.InvariantCultureIgnoreCase);
+            bool cycle = gmode.Equals("cycle", StringComparison.InvariantCultureIgnoreCase);
+            var vp = mutate || cycle ? null : ParseVectorProvider(gmode);
+
+            var exp = PopulationExperiment<DenseIndividual>.Load(filename);
+
+            var count = clips.Get("count", int.Parse, cycle ? exp.PopulationConfig.ExperimentConfiguration.Size : 100);
+
+            int exposureEpoch = clips.Get("exposureepoch", int.Parse, exp.Epoch);
+            int targetIndex = clips.Get("target", int.Parse, exp.CurrentTargetIndex);
+
+            var popConfig = exp.PopulationConfig;
+            var config = popConfig.ExperimentConfiguration;
+            var rrules = config.ReproductionRules;
+            var drules = config.DevelopmentRules;
+            var jrules = config.JudgementRules;
+            var population = exp.Population;
+            var range = rrules.InitialStateClamping;
+
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed);
+
+            List<Phenotype> phenotypes = new List<Phenotype>();
+
+            var context = new ModelExecutionContext(rand);
+            int n = exp.PopulationConfig.ExperimentConfiguration.Size;
+            console.WriteLine($"Population size {population.Count}\nGnome size {n}");
+
+            int popCount = population.Count;
+
+            foreach (var template in population.ExtractAll())
+            {
+                var g = template.Genome;
+
+                for (int i = 0; i < count; i++)
+                {
+                    DenseGenome g2 = mutate
+                        ? g.Mutate(context, rrules)
+                        : cycle
+                        ? BinaryCycle(context, g, i)
+                        : g.Clone(context, newInitialState: vp.RandomVector(range, n, rand));
+                    var individual = DenseIndividual.Develop(g2, context, drules, template.Epigenetic);
+
+                    if (generations > 0)
+                    {
+                        var target = config.Targets[0];
+
+                        ExposureInformation exposureInformation = new ExposureInformation(generations);
+                        target.NextExposure(rand, jrules, exp.Epoch, ref exposureInformation);
+                        exposureInformation.ExposureDuration = generations; // ignore what the target says
+
+                        var pop = new Population<DenseIndividual>(individual, popCount);
+                        popConfig.CustomPopulationSpinner.SpinPopulation(pop, context, rrules, drules, jrules, target, popConfig.SelectorPreparer, generations, null, popConfig.EliteCount, popConfig.HillclimberMode);
+                        individual = pop.PeekAll()[0];
+                    }
+
+                    var p = individual.Phenotype;
+                    if (saturate)
+                        p = p.Saturate(min, 0.0, max);
+                    phenotypes.Add(p);
+                }
+            }
+
+            if (clips.IsSet("distinct"))
+            {
+                phenotypes = phenotypes.Distinct(new SaturatedPhenotypeComparer()).ToList();
+            }
+            
+            int w = moduleCount > 1 ? moduleCount : (int)Math.Sqrt(n);
+            int h = (int)Math.Ceiling((double)n / w);
+            int c = (int)Math.Sqrt(phenotypes.Count);
+            int r = (int)Math.Ceiling((double)phenotypes.Count / c);
+            using (var img = new Image<Rgba32>((w + 1) * c - 1, (h + 1) * r - 1))
+            {
+                for (int pi = 0; pi < phenotypes.Count; pi++)
+                {
+                    var p = phenotypes[pi];
+
+                    int x = (w + 1) * (pi % c);
+                    int y = (h + 1) * (pi / c);
+
+                    DrawVector(img, p.Vector, x, y, w, min, max, whot);
+                }
+
+                console.WriteLine("Saving to " + outfile);
+                img.Save(outfile);
+            }
+
+            Console.WriteLine(phenotypes.Distinct(new SaturatedPhenotypeComparer()).Count());
+        }
+
+        private class SaturatedPhenotypeComparer : IEqualityComparer<Phenotype>
+        {
+            public bool Equals(Phenotype x, Phenotype y)
+            {
+                if (Analysis.Matches(x.Vector, y.Vector))
+                    return true;
+                return x.Vector.SequenceEqual(y.Vector);
+            }
+
+            public int GetHashCode(Phenotype obj)
+            {
+                int i = 0;
+                foreach (var v in obj.Vector)
+                {
+                    unchecked
+                    {
+                        i *= 7;
+                        i += Math.Sign(v);
+                    }
+                }
+                return i;
+            }
+        }
+
+        private static DenseGenome BinaryCycle(ModelExecutionContext context, DenseGenome g, int i)
+        {
+            i = i % g.Size;
+            var newg = g.InitialState.Clone();
+            newg[i] *= -1;
+            return g.Clone(context, newInitialState:newg);
+        }
+
+        public static void DrawNetwork(Image<Rgba32> img, Linear.Matrix<double> dtm, float r, float thickness, Rgba32 nodeCol, Rgba32 hotCol, Rgba32 coldCol, double threshold, SixLabors.Primitives.PointF[] nodeLocs)
+        {
+            int size = dtm.RowCount;
+
+            img.Mutate(ctx => 
+            {
+                // draw connectors
+                for (int i = 0; i < size; i++)
+                {        
+                    for (int j = 0; j < size; j++)
+                    {
+                        double mag = Math.Abs(dtm[i, j]);
+                        if (mag < threshold)
+                            continue;
+
+                        // effect of i on j
+                        var pen = new Pen(dtm[i, j] > 0 ? hotCol : coldCol, (float)(mag / threshold));
+
+                        var ni = nodeLocs[i];
+                        var nj = nodeLocs[j];
+                        var mid = (ni + nj) / 2f;
+                        var diff = nj - ni;
+                        diff = new PointF(-diff.Y, diff.X);
+                        var side = mid + diff * 0.2f;
+
+                        var pts = new[] { ni, side, side, nj };
+
+                        ctx.DrawBeziers(pen, pts);
+                    }
+                }
+
+                // draw noes
+                var nodePen = new Pen(nodeCol, 1);
+                var nodeFill = new SolidBrush(Rgba32.White);
+                foreach (var nl in nodeLocs)
+                {
+                    var ellipse = new SixLabors.Shapes.EllipsePolygon(nl.X, nl.Y, r, r);
+                    ctx.Draw(nodePen, ellipse);
+                    ctx.Fill(nodeFill, ellipse);
+                }
+            });
+        }
+
+        public static void DrawMatrix(Image<Rgba32> img, Linear.Matrix<double> m, int x, int y, double min, double max, bool whot, int cw, int ch)
+        {
+            int w = m.ColumnCount;
+            int h = m.RowCount;
+
+            for (int i = 0; i < w * cw; i++)
+            {
+                for (int j = 0; j < h * ch; j++)
+                {
+                    img[x + i, y + j] = Colour(m[j / ch, i / cw], min, max, whot);
+                }
+            }
+        }
+
+        public static void DrawVector(Image<Rgba32> img, IEnumerable<double> v, int x, int y, int w, double min, double max, bool whot)
+        {
+            int i = 0;
+            int j = 0;
+            foreach (double e in v)
+            {
+                img[x + i, y + j] = Colour(e, min, max, whot);
+
+                if (++i >= w)
+                {
+                    i = 0;
+                    j++;
+                }
+            }
+        }
+
+        public static Rgba32 Colour(double e, double min, double max, bool whot)
+        {
+            double d = (e - min) / (max - min);
+            if (!whot)
+                d = 1.0 - d;
+            Rgba32 col;
+
+            if (d < 0)
+            {
+                col = Color.Blue;
+            }
+            else if (d > 1)
+            {
+                col = Color.Red;
+            }
+            else
+            {
+                byte v = (byte)(d * 255);
+                col = new Rgba32(v, v, v);
+            }
+
+            return col;
+        }
+
+        public static IRandomVectorProvider ParseVectorProvider(string name)
+        {
+            if (name.Equals("Binary", StringComparison.InvariantCultureIgnoreCase))
+                return BinaryRandomVectorProvider.Instance;
+            if (name.Equals("Uniform", StringComparison.InvariantCultureIgnoreCase))
+                return UniformRandomVectorProvider.Instance;
+            if (name.Equals("Zero", StringComparison.InvariantCultureIgnoreCase))
+                return ZeroVectorProvider.Instance;
+
+            throw new Exception("Unrecognised VectorProvider name, consider one of:\n" +
+                " - Binary\n" +
+                " - Uniform\n" +
+                " - Zero");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliConcat.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliConcat.cs
new file mode 100644
index 0000000..f3f6468
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliConcat.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public class CliConcat : ICliProvider
+    {
+        public string Key => "concat";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Concat");
+                console.WriteLine(" - Files should be separated by semi-colons");
+                console.WriteLine(" - Indicate output file with out=path/filename");
+                console.WriteLine(" - You must qualify the type of files being processed and how to process them with the mode parameters:");
+                console.WriteLine("   - mode=traj -> concatenate trajectories (e.g. rcs, ist, pst, fitness)");
+                console.WriteLine("   - mode=ws -> concatenate wholesamples");
+                console.WriteLine(" - You can set sampleperiod where it is unknown, but if any known sample periods do not match the process will fail");
+                console.WriteLine(" - Set trimEnds flag if you want to trim the last entry in trajectories (except for the last one) so that they don't overlap");
+            }
+
+            string[] files = clips.Get("files", s => s.Split(';'));
+            string ofname = clips.Get("out");
+            string mode = clips.Get("mode");
+
+            if (mode.StartsWith("traj"))
+            {
+                int trueSamplePeriod = clips.Get("sampleperiod", int.Parse, -1);
+                bool trimEnds = clips.IsSet("trimEnds");
+
+                double[][][] trajectoriesies = new double[files.Length][][];
+
+                int totalLength = 0;
+                int fileSamplePeriod = -1;
+                for (int i = 0; i < files.Length; i++)
+                {
+                    trajectoriesies[i] = Analysis.LoadTrajectories(files[i], out fileSamplePeriod);
+                    totalLength += trajectoriesies[i][0].Length;
+
+                    if (trueSamplePeriod == -1)
+                        trueSamplePeriod = fileSamplePeriod;
+                    else
+                    {
+                        if (fileSamplePeriod != -1 && fileSamplePeriod != trueSamplePeriod)
+                            throw new Exception("Sample periods do not match");
+                    }
+                }
+
+                if (trimEnds)
+                    totalLength -= (trajectoriesies.Length - 1);
+
+                double[][] trajectories = new double[trajectoriesies[0].Length][];
+
+                for (int i = 0; i < trajectories.Length; i++)
+                {
+                    trajectories[i] = new double[totalLength];
+
+                    int pos = 0;
+                    for (int j = 0; j < trajectoriesies.Length; j++)
+                    {
+                        int l = trajectoriesies[j][i].Length - (trimEnds && j < trajectories.Length - 1 ? 1 : 0);
+                        Array.Copy(trajectoriesies[j][i], 0, trajectories[i], pos, l);
+                        pos += l;
+                    }
+                }
+
+                Analysis.SaveTrajectories(ofname, trajectories, trueSamplePeriod);
+            }
+            else if (mode.StartsWith("wholesamples") || mode == "ws")
+            {
+                if (clips.IsSet("help"))
+                {
+                    console.WriteLine("Wholesample Concat\n" +
+                     " - resequence: resequence wholesamples in order supplied\n" +
+                     " - skipfirst: skip the first entry of successive files");
+                }
+
+                bool resequence = clips.IsSet("resequence");
+                bool skipfirst = clips.IsSet("skipfirst");
+
+                var wsm = WholeSamplesHelpers.LoadUnknownType<IWholeSamplesManipulator>(files[0], new WholeSamplesManipulatorReceiver());
+                wsm.AppendSamples(files.Skip(1), resequence, skipfirst);
+                wsm.SaveSamples(ofname);
+            }
+
+            // TODO: concat tracees
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliExp.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliExp.cs
new file mode 100644
index 0000000..a4eeab2
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliExp.cs
@@ -0,0 +1,495 @@
+using M4M.Epistatics;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4M
+{
+    public interface IDensePopulationExperimentFactoryCliFactory
+    {
+        string Name { get; }
+        DensePopulationExperimentFeedbackFactory Prepare(TextWriter console, CliParams clips);
+    }
+
+    public class CliRun : ICliProvider
+    {
+        public CliRun(CliExp cliExp)
+        {
+            CliExp = cliExp;
+        }
+
+        public string Key => "run";
+
+        public CliExp CliExp { get; }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            string defSuperDir = clips.Get("run");
+
+            if (defSuperDir == null)
+            {
+                console.WriteLine("Supply, as a minimum, a directory containing directories containing 'epoch0savestarter.dat' population experiment definitions.\n" +
+                    "Optionally, you may provide a postfix and alternative topdir, and whatever stuff your feedback provider will take.");
+
+                console.WriteLine("Your feedback provider is " + CliExp.FeedbackFactoryFactory.Name);
+
+                return;
+            }
+
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("CliRun runs a directory full of directories containing experiments.");
+                console.WriteLine("Parameters include:\n" + 
+                    " - expfilename, the name of the experiment files to run\n" +
+                    " - postfix, the postfix to append\n" +
+                    " - continue, whether to continue an interrupted run\n" +
+                    " - stl, when continuing, use the second-to-last expfile\n" +
+                    " - seed, the base seed, which is incremented for each sub-experiment\n" +
+                    " - repeats, the number of repeats to run\n");
+
+                CliExp.PrintFeedbackHelp(console);
+            }
+
+            string expfilename = clips.Get("expfilename", "epoch0savestarter.dat");
+            string postfix = clips.Get("postfix", "");
+            
+            bool @continue = clips.IsSet("continue");
+            bool stl = clips.IsSet("stl");
+
+            string superDir = clips.Get("topdir", defSuperDir + "runs" + postfix + "/");
+
+            List<Action> actions = new List<Action>();
+            
+            int? seed = clips.Get("seed", s => (int?)int.Parse(s), null);
+            int repeats = clips.Get("repeats", int.Parse, 1);
+            int r0 = clips.Get("r0", int.Parse, 0);
+
+            if (seed != null)
+                seed += r0;
+
+            string[] dirs = System.IO.Directory.GetDirectories(defSuperDir);
+            Array.Sort(dirs, StringComparer.Ordinal);
+
+            foreach (var d in dirs)
+            {
+                var expAddress = @continue
+                    ? (stl ? FindNewestSicherSave(d) : FindNewestSave(d))
+                    : System.IO.Path.Combine(d, expfilename);
+                var topdir = d.Replace(defSuperDir, superDir); // soooo dodgy
+                if (string.IsNullOrEmpty(topdir))
+                    topdir = ".";
+
+                if (System.IO.File.Exists(expAddress))
+                {
+                    for (int r = r0; r < r0 + repeats; r++)
+                    {
+                        string cliPrefix = "R" + actions.Count + " ";
+                        
+                        string repeatPostfix = repeats <= 1 ? "" : "r" + r;
+                        int dseed = seed ?? CliExp.SeedSource.Next();
+
+                        actions.Add(() =>
+                        {
+                            try
+                            {
+                                CliExp.RunExperiment(console, clips, cliPrefix, expAddress, topdir + repeatPostfix, dseed);
+                            }
+                            catch (Exception ex)
+                            {
+                                console.WriteLine(cliPrefix + "Crashed: " + ex.Message);
+                                console.WriteLine(cliPrefix + "StackTrace: " + ex.StackTrace);
+                            }
+                        });
+                        
+                        if (seed != null)
+                            seed = seed + 1;
+                    }
+                }
+            }
+
+            console.WriteLine("Running " + actions.Count + " experiments");
+            Parallel.Invoke(actions.ToArray());
+        }
+
+        private static string FindNewestSave(string dir)
+        {
+            return System.IO.Directory.GetFiles(dir).Where(f => f.EndsWith("save.dat")).ArgMax(System.IO.File.GetLastWriteTimeUtc);
+        }
+
+        private static string FindNewestSicherSave(string dir)
+        {
+            return System.IO.Directory.GetFiles(dir).Where(f => f.EndsWith("save.dat")).OrderByDescending(System.IO.File.GetLastWriteTimeUtc).Take(2).Last();
+        }
+    }
+
+    public class CliExp : ICliProvider
+    {
+        public static readonly MathNet.Numerics.Random.RandomSource SeedSource = new MathNet.Numerics.Random.MersenneTwister(true);
+        
+        public CliExp(IDensePopulationExperimentFactoryCliFactory feedbackFactoryFactory)
+        {
+            FeedbackFactoryFactory = feedbackFactoryFactory;
+        }
+
+        public string Key => "exp";
+
+        public IDensePopulationExperimentFactoryCliFactory FeedbackFactoryFactory { get; }
+
+        public void PrintFeedbackHelp(TextWriter console)
+        {
+            console.WriteLine("Feedback Parameters include:\n" + 
+                " - savePeriodEpochs, the interval between writing an experiment file\n" +
+                " - savePeriodSeconds, the maximum number of seconds to wait before writing an experiment file\n" +
+                " - sampleMax, controls the number of samples in trajectorty (rcs, fitness, ist, and pst) files\n" +
+                " - wholeSamplePeriod, interval between wholesamples\n" +
+                " - wholeSampleWritePeriod, interval between writing wholesamples\n" +
+                " - tracePeriod, interval between tracees\n" +
+                " - traceDuration, number of epochs to trace for each tracee\n" +
+                " - traceSamplePeriod, within-tracee sample period\n" +
+                " - plotperiod, interval in epochs between producing trajectory files\n" +
+                " - saveperiod, interval between writing out a genome file\n" +
+                " - configurators, list of additional feedback configurators to use\n");
+        }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("CliExp runs a single experiment.");
+                console.WriteLine("Parameters include:\n" + 
+                    " - exp, the name of the experiment files to run\n" +
+                    " - topdir, the directory wherein to run\n" +
+                    " - repeats, the number of repeats to run\n" +
+                    " - seed, the seed\n");
+
+                PrintFeedbackHelp(console);
+            }
+
+            string expAddress = clips.Get("exp");
+            string topdir = clips.Get("topdir", System.IO.Path.GetDirectoryName(expAddress));
+            string cliPrefix = clips.Get("cliPrefix", "");
+            if (cliPrefix != "")
+                cliPrefix += " ";
+
+            int seed = clips.Get("seed", s => (int?)int.Parse(s), null) ?? SeedSource.Next();
+
+            int repeats = clips.Get("repeats", int.Parse, 0);
+            int r0 = clips.Get("r0", int.Parse, 0);
+
+            seed += r0;
+
+            if (repeats > 0)
+            {
+                string innerDir = clips.Get("innerdir", "");
+                string innerExp = clips.Get("innerexp", null);
+
+                List<Action> actions = new List<Action>();
+
+                if (string.IsNullOrEmpty(topdir))
+                    topdir = ".";
+
+                for (int r = r0; r < r0 + repeats; r++)
+                {
+                    string rCliPrefix = repeats > 1 ? "R" + actions.Count + cliPrefix : cliPrefix;
+                    int rSeed = seed;
+
+                    string repeatPostfix = repeats < 1 ? "" : "r" + r;
+                    string rTopDir = System.IO.Path.Combine(topdir, repeatPostfix);
+                    string rInnerDir = System.IO.Path.Combine(topdir, repeatPostfix, innerExp ?? "");
+                    string rExpAdr = innerExp == null ? expAddress : System.IO.Path.Combine(rTopDir, innerExp);
+
+                    actions.Add(() =>
+                    {
+                        try
+                        {
+                            RunExperiment(console, clips, rCliPrefix, rExpAdr, rInnerDir, rSeed);
+                        }
+                        catch (Exception ex)
+                        {
+                            console.WriteLine(rCliPrefix + "Crashed: " + ex.Message);
+                            console.WriteLine(rCliPrefix + "StackTrace: " + ex.StackTrace);
+                        }
+                    });
+
+                    seed++;
+                }
+
+                console.WriteLine("Running " + actions.Count + " experiments");
+                Parallel.Invoke(actions.ToArray());
+            }
+            else
+            {
+                RunExperiment(console, clips, cliPrefix, expAddress, topdir, seed);
+            }
+        }
+
+        public void RunExperiment(TextWriter console, CliParams clips, string cliPrefix, string expAddress, string topdir, int? seed)
+        {
+            string postfix2 = clips.Get("postfix2", "");
+            bool appendTimestamp = clips.IsSet("appendTimestamp");
+
+            bool @continue = clips.IsSet("continue");
+
+            int savePeriodEpochs = clips.Get("savePeriodEpochs", Misc.ParseInt, 500);
+            int savePeriodSeconds = clips.Get("savePeriodSeconds", Misc.ParseInt, 60*60);
+
+            bool enableMatrixPools = clips.Get("enableMatrixPools", bool.Parse, false);
+            MatrixPool.EnableMatrixPools = enableMatrixPools;
+            if (enableMatrixPools)
+            {
+                console.WriteLine("Warning: Matrix Pools are enabled!");
+            }
+
+            var feedbackFactory = FeedbackFactoryFactory.Prepare(console, clips);
+
+            if (@continue)
+                ContinueExperiment(console, cliPrefix, expAddress, topdir, postfix2, seed, feedbackFactory, appendTimestamp, savePeriodEpochs, savePeriodSeconds);
+            else
+                RunExperiment(console, cliPrefix, expAddress, topdir, postfix2, seed, feedbackFactory, appendTimestamp, savePeriodEpochs, savePeriodSeconds);
+        }
+
+        public void RunExperiment(TextWriter console, string cliPrefix, string expAddress, string topdir, string postfix2, int? seed, DensePopulationExperimentFeedbackFactory feedbackFactory, bool appendTimestamp, int savePeriodEpochs, int savePeriodSeconds)
+        {
+            topdir += postfix2;
+
+            if (string.IsNullOrEmpty(topdir))
+                topdir = ".";
+
+            console.WriteLine(cliPrefix + "Running prepackaged DenseIndividual experiment");
+            console.WriteLine(cliPrefix + "Definition at " + expAddress);
+            console.WriteLine(cliPrefix + "Running into  " + topdir);
+            if (seed != null)
+                console.WriteLine(cliPrefix + "Seed " + seed);
+
+            var popExperiment = PopulationExperiment<DenseIndividual>.Load(expAddress);
+
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+            sw.Restart();
+
+            console.WriteLine(cliPrefix + popExperiment.PopulationConfig.ExperimentConfiguration.Epochs + " epochs of " + popExperiment.PopulationConfig.ExperimentConfiguration.GenerationsPerTargetPerEpoch + " generations");
+            LowRunners.RunExperiment(console, cliPrefix, topdir, popExperiment, seed, feedbackFactory, null, appendTimestamp, savePeriodEpochs, savePeriodSeconds);
+
+            sw.Stop();
+            console.WriteLine(cliPrefix + "Took " + sw.ElapsedMilliseconds + "ms");
+        }
+
+        public void ContinueExperiment(TextWriter console, string cliPrefix, string expAddress, string topdir, string postfix2, int? seed, DensePopulationExperimentFeedbackFactory feedbackFactory, bool appendTimestamp, int savePeriodEpochs, int savePeriodSeconds)
+        {
+            topdir += postfix2;
+            
+            if (string.IsNullOrEmpty(topdir))
+                topdir = ".";
+
+            console.WriteLine(cliPrefix + "Continuing prepackaged DenseIndividual experiment");
+            console.WriteLine(cliPrefix + "Definition at " + expAddress);
+            console.WriteLine(cliPrefix + "Running into  " + topdir);
+            if (seed != null)
+                console.WriteLine(cliPrefix + "Seed " + seed);
+
+            var popExperiment = PopulationExperiment<DenseIndividual>.Load(expAddress);
+
+            int epochsLeft = popExperiment.PopulationConfig.ExperimentConfiguration.Epochs - popExperiment.Epoch;
+            if (epochsLeft < 0)
+            {
+                console.WriteLine(cliPrefix + " Experiment already finished");
+                return;
+            }
+            
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+            sw.Restart();
+
+            console.WriteLine(cliPrefix + epochsLeft + " epochs of " + popExperiment.PopulationConfig.ExperimentConfiguration.GenerationsPerTargetPerEpoch + " generations");
+            LowRunners.ContinueExperiment(console, cliPrefix, topdir, popExperiment, seed, feedbackFactory, null, appendTimestamp, savePeriodEpochs, savePeriodSeconds);
+
+            sw.Stop();
+            console.WriteLine(cliPrefix + "Took " + sw.ElapsedMilliseconds + "ms");
+        }
+    }
+    
+    public class CommonFeedbackFactoryCliFactory : IDensePopulationExperimentFactoryCliFactory
+    {
+        public CommonFeedbackFactoryCliFactory(IEnumerable<IDensePoulationExperimentFeedbackConfiguratorCliFactory> configuratorFactories)
+        {
+            ConfiguratorFactories = configuratorFactories;
+        }
+
+        public string Name => "CommonFeedbackFactoryCliFactory";
+
+        public IEnumerable<IDensePoulationExperimentFeedbackConfiguratorCliFactory> ConfiguratorFactories { get; }
+
+        public DensePopulationExperimentFeedbackFactory Prepare(TextWriter console, CliParams clips)
+        {
+            int sampleMax = clips.Get("sampleMax", Misc.ParseInt, 2000); // not so much expensive, as we just don't want too many because RCSViewer will become unhappy
+            int wholeSamplePeriod = clips.Get("wholeSamplePeriod", Misc.ParseInt, 10); // expensive
+            int wholeSampleWritePeriod = clips.Get("wholeSampleWritePeriod", Misc.ParseInt, Math.Max(2000, wholeSamplePeriod * 200));
+            int tracePeriod = clips.Get("tracePeriod", Misc.ParseInt, 0); // very expensive
+            int traceDuration = clips.Get("traceDuration", Misc.ParseInt, 1); // very expensive
+            int traceSamplePeriod = clips.Get("traceSamplePeriod", Misc.ParseInt, 1); // very expensive
+            int plotPeriod = clips.Get("plotperiod", Misc.ParseInt, 5000); // not inexpensive
+            int savePeriod = clips.Get("saveperiod", Misc.ParseInt, plotPeriod); // basically pointless, but not expensive
+            var rememberTraces = clips.Get("rememberTraces", bool.Parse, false); // completely pointless, very expensive
+            var noRcs = clips.Get("norcs", bool.Parse, false); // reduces output, not memory usage
+
+            var feedbackFactory = CommonDensePopulationExperimentFeedback.SimpleCommonDensePopulationExperimentFeedback(
+                console: console,
+                sampleMax: sampleMax,
+                plotPeriod: plotPeriod,
+                savePeriod: savePeriod,
+                wholeSamplePeriod: wholeSamplePeriod,
+                wholeSampleWritePeriod: wholeSampleWritePeriod,
+                tracePeriod: tracePeriod,
+                traceDuration: traceDuration,
+                traceSamplePeriod: traceSamplePeriod,
+                rememberTraces: rememberTraces,
+                noRcs: noRcs
+                );
+
+            var configuratorFactoryNames = clips.Get("configurators", s => new HashSet<string>(s.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries), StringComparer.InvariantCultureIgnoreCase), new HashSet<string>());
+            var configuratorFactories = ConfiguratorFactories.Where(cf => configuratorFactoryNames.Contains(cf.Name)).ToArray();
+            var configurators = configuratorFactories.Select(cf => cf.Prepare(console, clips)).ToArray();
+            console.WriteLine("Configurators: " + string.Join(", ", configuratorFactories.Select(cf => cf.Name)));
+
+            return (RandomSource rand, PopulationExperimentConfig<DenseIndividual> populationExperimentConfig, PopulationExperimentFeedback<DenseIndividual> feedback)
+                =>
+            {
+                var denseFeedback = feedbackFactory(rand, populationExperimentConfig, feedback);
+
+                foreach (var configurator in configurators)
+                {
+                    configurator(denseFeedback);
+                }
+
+                return denseFeedback;
+            };
+        }
+        
+        public static readonly List<IDensePoulationExperimentFeedbackConfiguratorCliFactory> DefaultConfiguratorFactories = new List<IDensePoulationExperimentFeedbackConfiguratorCliFactory>
+        {
+            new IvmcDensePoulationExperimentFeedbackConfiguratorCliFactory(),
+            new SatFitnessExperimentFeedbackConfiguratorCliFactory(),
+            new SudokuPoulationExperimentFeedbackConfiguratorCliFactory(),
+            new NQueensPoulationExperimentFeedbackConfiguratorCliFactory(),
+        };
+    }
+
+    public interface IDensePoulationExperimentFeedbackConfiguratorCliFactory
+    {
+        string Name { get; }
+        DensePopulationExperimentFeedbackConfigurator Prepare(TextWriter console, CliParams clips);
+    }
+
+    public class IvmcDensePoulationExperimentFeedbackConfiguratorCliFactory : IDensePoulationExperimentFeedbackConfiguratorCliFactory
+    {
+        public string Name => "IvmcDenseFeedback";
+
+        public DensePopulationExperimentFeedbackConfigurator Prepare(TextWriter console, CliParams clips)
+        {
+            int plotPeriod = clips.Get("ivmcSwitchPlotperiod", Misc.ParseInt, clips.Get("plotperiod", Misc.ParseInt, 5000)); // not inexpensive
+            int resolution = clips.Get("ivmcSwitchResolution", Misc.ParseInt, 1); // not inexpensive
+            bool recordProper = clips.Get("ivmcRecordPropers", bool.Parse, false);
+
+            return (IDensePopulationExperimentFeedback densePopulationExperimentFeedback) =>
+            {
+                var target = densePopulationExperimentFeedback.PopulationConfig.ExperimentConfiguration.Targets[0];
+
+                if (target is Epistatics.IIvmcProperTarget ivmcProperTarget)
+                {
+                    var proper = ivmcProperTarget.Proper;
+                    int moduleCount = ivmcProperTarget.Proper.ModuleCount;
+
+                    Epistatics.IvmcProperFeedback ivmcProperFeedback = new Epistatics.IvmcProperFeedback(resolution, plotPeriod, recordProper, moduleCount);
+                    ivmcProperFeedback.Attach(densePopulationExperimentFeedback);
+                    console.WriteLine("IvmcProperFeedback attached");
+                }
+                else
+                {
+                    console.WriteLine("IvmcProperFeedback NOT attached: target is not an IIvmcProperTarget");
+                }
+            };
+        }
+    }
+
+    public class SudokuPoulationExperimentFeedbackConfiguratorCliFactory : IDensePoulationExperimentFeedbackConfiguratorCliFactory
+    {
+        public string Name => "SudokuFeedback";
+
+        public DensePopulationExperimentFeedbackConfigurator Prepare(TextWriter console, CliParams clips)
+        {
+            int sudokuResolution = clips.Get("sudokuresolution", Misc.ParseInt, 1);
+            int plotPeriod = clips.Get("plotperiod", Misc.ParseInt, 5000);
+
+            return (IDensePopulationExperimentFeedback densePopulationExperimentFeedback) =>
+            {
+                var target = densePopulationExperimentFeedback.PopulationConfig.ExperimentConfiguration.Targets[0];
+
+                if (target is Epistatics.CorrelationMatrixTarget cmt)
+                {
+                    Epistatics.SudokuFeedback sudokuFeedback = new Epistatics.SudokuFeedback(sudokuResolution, plotPeriod);
+                    sudokuFeedback.Attach(densePopulationExperimentFeedback);
+                    console.WriteLine("SudokuFeedback attached");
+                }
+                else
+                {
+                    console.WriteLine("SudokuFeedback NOT attached: target is not a CorrelationMatrixTarget");
+                }
+            };
+        }
+    }
+
+    public class NQueensPoulationExperimentFeedbackConfiguratorCliFactory : IDensePoulationExperimentFeedbackConfiguratorCliFactory
+    {
+        public string Name => "NQueensFeedback";
+
+        public DensePopulationExperimentFeedbackConfigurator Prepare(TextWriter console, CliParams clips)
+        {
+            int NQueensResolution = clips.Get("nqueensresolution", Misc.ParseInt, 1);
+            int plotPeriod = clips.Get("plotperiod", Misc.ParseInt, 5000);
+            var threshold = clips.Get("nqueensthreshold", double.Parse, 0.5);
+
+            return (IDensePopulationExperimentFeedback densePopulationExperimentFeedback) =>
+            {
+                var target = densePopulationExperimentFeedback.PopulationConfig.ExperimentConfiguration.Targets[0];
+
+                if (target is Epistatics.CorrelationMatrixTarget cmt)
+                {
+                    Epistatics.NQueensFeedback NQueensFeedback = new Epistatics.NQueensFeedback(NQueensResolution, plotPeriod, threshold);
+                    NQueensFeedback.Attach(densePopulationExperimentFeedback);
+                    console.WriteLine("NQueensFeedback attached");
+                }
+                else
+                {
+                    console.WriteLine("NQueensFeedback NOT attached: target is not a CorrelationMatrixTarget");
+                }
+            };
+        }
+    }
+
+    public class SatFitnessExperimentFeedbackConfiguratorCliFactory : IDensePoulationExperimentFeedbackConfiguratorCliFactory
+    {
+        public string Name => "SatFitnessFeedback";
+
+        public DensePopulationExperimentFeedbackConfigurator Prepare(TextWriter console, CliParams clips)
+        {
+            int samplePeriod = clips.Get("SatFitnessSampleRate", Misc.ParseInt, 1);
+            int plotPeriod = clips.Get("SatFitnessPlotPeriod", Misc.ParseInt, clips.Get("PlotPeriod", Misc.ParseInt, -1));
+
+            var satMin = clips.Get("satMin", double.Parse, -1.0);
+            var satThreshold = clips.Get("satThreshold", double.Parse, 0.0);
+            var satMax = clips.Get("satMax", double.Parse, +1.0);
+
+            return (IDensePopulationExperimentFeedback densePopulationExperimentFeedback) =>
+            {
+                var feedback = densePopulationExperimentFeedback.Feedback;
+                var jrules = densePopulationExperimentFeedback.PopulationConfig.ExperimentConfiguration.JudgementRules;
+                var targets = densePopulationExperimentFeedback.PopulationConfig.ExperimentConfiguration.Targets.Select(t => t is SaturationTarget ? t : new SaturationTarget(t, satMin, satThreshold, satMax)).ToArray();
+                
+                var satFitnessFeedback = new ProjectedFitnessFeedback<DenseIndividual>(feedback, targets, jrules, "Sat", samplePeriod, plotPeriod);
+                console.WriteLine("SatFitnessFeedback attached");
+            };
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliExtract.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliExtract.cs
new file mode 100644
index 0000000..2c039b4
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliExtract.cs
@@ -0,0 +1,543 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public class CliExtract : ICliProvider
+    {
+        public string Key => "extract";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Extracts stuff from other stuff");
+                console.WriteLine(" - extract, the source file to extract from (e.g. wholesamples)");
+            }
+            
+            string source = clips.Get("extract");
+            string fname = System.IO.Path.GetFileName(source);
+
+            if (fname.StartsWith("wholesamples"))
+            {
+                ExtractFromWholeSamples(console, clips);
+            }
+            else if (fname.StartsWith("tracee"))
+            {
+                ExtractFromTracee(console, clips);
+            }
+            else if (fname.StartsWith("epoch"))
+            {
+                ExtractFromPopulationExperiment(console, clips);
+            }
+            else if (fname.StartsWith("rcs"))
+            {
+                ExtractDenseFromRcs(console, clips);
+            }
+            else
+            {
+                console.WriteLine("Can only extract from wholesamples and tracees");
+            }
+        }
+
+        // will assume the experiment type for the experiment type
+        public void ExtractFromWholeSamples(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Must provide the following to extract from a wholesamples:");
+                console.WriteLine(" - extract, the wholesamples source file to extract from");
+                console.WriteLine(" - expfile, the experiment save file to reconfigure");
+                console.WriteLine(" - epoch, the epoch to extract");
+                console.WriteLine(" - outdir, the experiment output directory");
+                console.WriteLine(" - postfix, the experiment savefile postfix");
+                console.WriteLine(" - zeroStart, whether to zero the start epochs and generations (default is false: keep)");
+                console.WriteLine(" - firstOfEpoch, whether to take the first entry for the given epoch (default is false: takes the last)");
+            }
+
+            string wholeSamplesSource = clips.Get("extract");
+            int[] epochs = clips.Get("epoch", Misc.ParseIntList);
+            string expFile = clips.Get("expfile");
+            string outdir = clips.Get("outdir");
+            string postfix = clips.Get("postfix", "extract");
+            bool firstOfEpoch = clips.Get("firstOfEpoch", bool.Parse, false);
+            bool zeroStart = clips.Get("zeroStart", bool.Parse, false);
+            bool appendEpoch = clips.Get("appendEpoch", bool.Parse, true);
+
+            // load the experiment - inferring its type - and give us an IExperimentExtractor with which to work
+            var expExtractor = PopulationExperimentHelpers.LoadUnknownType(expFile, new ExperimentExtractorExperimentReceiver());
+
+            expExtractor.ExtractFromWholeSamples(wholeSamplesSource, epochs, firstOfEpoch, postfix, outdir, zeroStart, appendEpoch);
+        }
+
+        // will assume the experiment type for the experiment type
+        public void ExtractFromTracee(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Must provide the following to extract from a tracee:");
+                console.WriteLine(" - extract, the tracee source file to extract from");
+                console.WriteLine(" - expfile, the experiment save file to reconfigure");
+                console.WriteLine(" - generation, the generation to extract");
+                console.WriteLine(" - outdir, the experiment output directory");
+                console.WriteLine(" - postfix, the experiment savefile postfix");
+                console.WriteLine(" - zeroStart, whether to zero the start epochs and generations (default is false: keep)");
+                console.WriteLine(" - firstOfEpoch, whether to take the first entry for the given epoch (default is false: takes the last)");
+            }
+
+            string wholeSamplesSource = clips.Get("extract");
+            int[] generations = clips.Get("generation", Misc.ParseIntList);
+            string expFile = clips.Get("expfile");
+            string outdir = clips.Get("outdir");
+            string postfix = clips.Get("postfix", "extract");
+            bool firstOfEpoch = clips.Get("firstOfEpoch", bool.Parse, false);
+            bool zeroStart = clips.Get("zeroStart", bool.Parse, false);
+            bool appendGeneration = clips.Get("appendGeneration", bool.Parse, true);
+
+            // load the experiment - inferring its type - and give us an IExperimentExtractor with which to work
+            var expExtractor = ExperimentExtractorExperimentReceiver.LoadExtractorFromFile(expFile);
+
+            expExtractor.ExtractFromTraceInfo(wholeSamplesSource, generations, firstOfEpoch, postfix, outdir, zeroStart, appendGeneration);
+        }
+
+        // will assume the experiment type for the experiment type
+        public void ExtractFromPopulationExperiment(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Must provide the following to extract from a tracee:");
+                console.WriteLine(" - extract, the population experiment source file to extract from");
+                console.WriteLine(" - expfile, the experiment save file to reconfigure");
+                console.WriteLine(" - outdir, the output directory");
+            }
+
+            string expSoure = clips.Get("extract");
+            string prefix = clips.Get("prefix", "");
+            string outdir = clips.Get("outdir");
+
+            // load the experiment - inferring its type - and give us an IExperimentExtractor with which to work
+            var expExtractor = ExperimentExtractorExperimentReceiver.LoadExtractorFromFile(expSoure);
+            expExtractor.ExtractGenomesFromWithin(prefix, outdir);
+        }
+
+        // will assume the experiment type for the experiment type
+        public void ExtractDenseFromRcs(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Must provide the following to extract a dense experiment from an rcs and ist combination:");
+                console.WriteLine(" - extract, the rcs source file to extract from");
+                console.WriteLine(" - ist, the ist source file to extract from");
+                console.WriteLine(" - epoch, the epoch to sample");
+                console.WriteLine(" - expfile, the experiment save file to reconfigure");
+                console.WriteLine(" - outdir, the experiment output directory");
+                console.WriteLine(" - postfix, the experiment savefile postfix");
+                console.WriteLine(" - zeroStart, whether to zero the start epochs and generations (default is false: keep)");
+                console.WriteLine(" - seed, the random seed");
+            }
+
+            string rcsSource = clips.Get("extract");
+            string istSource = clips.Get("ist");
+            int epoch = clips.Get("epoch", Misc.ParseInt);
+            string expFile = clips.Get("expfile");
+            string outdir = clips.Get("outdir");
+            int seed = clips.GetOrCreate("seed", s => int.Parse(s), () => CliExp.SeedSource.Next());
+            string postfix = clips.Get("postfix", "extract");
+            bool zeroStart = clips.Get("zeroStart", bool.Parse, false);
+            int nominalSamplePeriod = clips.Get("nominalSamplePeriod", int.Parse, -1);
+
+            var rcs = Analysis.LoadTrajectories(rcsSource, out int rcsSamplePeriod);
+            var ist = Analysis.LoadTrajectories(istSource, out int istSamplePeriod);
+
+            // all this is just handling the sample periods
+            if (nominalSamplePeriod != -1)
+            {
+                if (rcsSamplePeriod != -1 && nominalSamplePeriod != rcsSamplePeriod)
+                {
+                    console.Write("Nominal sample period did not match sample period reported by rcs; ignoring nominal");
+                }
+                if (istSamplePeriod != -1 && nominalSamplePeriod != rcsSamplePeriod)
+                {
+                    console.WriteLine("Nominal sample period did not match sample period reported by rcs; ignoring nominal");
+                }
+            }
+
+            if (rcsSamplePeriod == -1)
+            {
+                rcsSamplePeriod = nominalSamplePeriod;
+            }
+            if (istSamplePeriod == -1)
+            {
+                istSamplePeriod = nominalSamplePeriod;
+            }
+
+            if (istSamplePeriod < 0)
+            {
+                throw new Exception("Invalid ist sample periods");
+            }
+            if (rcsSamplePeriod < 0)
+            {
+                throw new Exception("Invalid rcs sample periods");
+            }
+
+            if (istSamplePeriod != rcsSamplePeriod)
+            {
+                // this is not a problem itself, but probably implies the user messed up
+                console.WriteLine("rcs and ist sample periods are different");
+            }
+            //
+
+            int rcsIndex = epoch / rcsSamplePeriod;
+            int istIndex = epoch / rcsSamplePeriod;
+            console.WriteLine("rcs epoch: " + rcsIndex * rcsSamplePeriod);
+            console.WriteLine("ist epoch: " + istIndex * istSamplePeriod);
+
+            var rand = new CustomMersenneTwister(seed);
+            var context = new ModelExecutionContext(rand);
+            var exp = PopulationExperiment<DenseIndividual>.Load(expFile);
+
+            var expExtractor = new ExperimentExtractor<DenseIndividual>(exp);
+            var config = expExtractor.ExperimentConfiguration;
+            var drules = config.DevelopmentRules;
+            int generationsPerEpoch = config.Targets.Count * config.GenerationsPerTargetPerEpoch;
+
+            var example = exp.Population.PeekAll()[0];
+            var genome = ExtractDenseGenomeFromRcsAndIst(rcs, ist, rcsIndex, istIndex, example.Genome);
+
+            // popConfig
+            var popConfig = exp.PopulationConfig;
+            var template = DenseIndividual.Develop(genome, context, drules, example.Epigenetic);
+            var population = new Population<DenseIndividual>(template, exp.Population.Count);
+
+            // population experiment
+            var fileStuff = FileStuff.CreateNow(outdir, "", "", true, false);
+            var popExp = new PopulationExperiment<DenseIndividual>(population, popConfig, fileStuff, zeroStart ? 0 : epoch, zeroStart ? 0L : epoch * generationsPerEpoch);
+            popExp.Save(postfix, false);
+            Analysis.SaveGenome(System.IO.Path.Combine(outdir, "genome" + postfix + ".dat"), genome);
+        }
+
+        public static DenseGenome ExtractDenseGenomeFromRcsAndIst(double[][] rcs, double[][] ist, int rcsIndex, int istIndex, DenseGenome template)
+        {
+            int size = ist.Length;
+            var dtm = Analysis.ExtractMatrix(rcs, rcsIndex, size, size);
+            var initialState = Analysis.ExtractVector(ist, istIndex);
+            return new DenseGenome(initialState, dtm, null, template.TransMatIndexOpenEntries, template.CustomTransMatMutator, template.CustomInitialStateMutator, template.CustomTransMatCombiner, template.CustomInitialStateCombiner, template.CustomDevelopmentStep, null, false);
+        }
+    }
+
+    // this stuff is all so that we can use it for any type of individual
+    public class ExperimentExtractorExperimentReceiver : IUnknownPopulationExperimentTypeReceiver<IExperimentExtractor>
+    {
+        public IExperimentExtractor Receive<T>(PopulationExperiment<T> populationExperiment) where T : IIndividual<T>
+        {
+            return new ExperimentExtractor<T>(populationExperiment);
+        }
+
+        /// <summary>
+        /// Loads an IExperimentExtractor from the file, which provides helper methods which don't require knowledge of the individual type
+        /// </summary>
+        /// <param name="fileName"></param>
+        /// <returns></returns>
+        public static IExperimentExtractor LoadExtractorFromFile(string fileName)
+        {
+            var expExtractor = PopulationExperimentHelpers.LoadUnknownType(fileName, new ExperimentExtractorExperimentReceiver());
+            return expExtractor;
+        }
+    }
+    
+    public interface IExperimentExtractor
+    {
+        /// <summary>
+        /// Retrieves the ExperimentConfiguration for the experiment
+        /// </summary>
+        ExperimentConfiguration ExperimentConfiguration { get; }
+
+        /// <summary>
+        /// Extracts an experiment from the WholeSamples
+        /// </summary>
+        /// <param name="sourceFile">wholesamples source file</param>
+        /// <param name="epochs">The epochs to extract</param>
+        /// <param name="firstOfEpoch">whether to take the first entry for the given epoch, or the last</param>
+        /// <param name="postfix">a postfix? I guess</param>
+        /// <param name="outdir">experiment output directory</param>
+        /// <param name="zeroStart">whether to zero the start epochs and generations</param>
+        void ExtractFromWholeSamples(string sourceFile, IEnumerable<int> epochs, bool firstOfEpoch, string postfix, string outdir, bool zeroStart, bool appendEpoch);
+        
+        /// <summary>
+        /// Extracts an experiment from the TraceInfo
+        /// </summary>
+        /// <param name="sourceFile">wholesamples source file</param>
+        /// <param name="generations">The generations to extract</param>
+        /// <param name="firstOfEpoch">whether to take the first entry for the given epoch, or the last</param>
+        /// <param name="postfix">a postfix? I guess</param>
+        /// <param name="outdir">experiment output directory</param>
+        /// <param name="zeroStart">whether to zero the start epochs and generations</param>
+        void ExtractFromTraceInfo(string sourceFile, IEnumerable<int> generations, bool firstOfEpoch, string postfix, string outdir, bool zeroStart, bool appendGeneration);
+        
+        /// <summary>
+        /// Projects a WholeSamples
+        /// </summary>
+        /// <param name="sourceFile">wholesamples source file</param>
+        /// <param name="start">start epoch</param>
+        /// <param name="end">end epoch</param
+        /// <param name="outdir">experiment output directory</param>
+        /// <param name="projectorPreparer">The projector preparer</param>
+        void ProjectFromWholeSamples(string sourceFile, int? start, int? end, string outdir, IExtractorWholeSampleProjectorPreparer projectorPreparer);
+        
+        /// <summary>
+        /// Extracts genomes from the experiment
+        /// </summary>
+        /// <param name="postfix">a postfix? I guess</param>
+        /// <param name="outdir">experiment output directory</param>
+        void ExtractGenomesFromWithin(string prefix, string outdir);
+
+        /// <summary>
+        /// Writes out a config.txt for this experiment
+        /// </summary>
+        /// <param name="outFilename">Where to save the config file</param>
+        /// <param name="seed">Seed used for random sampling</param>
+        void WriteOutConfig(string outFilename, int seed = 1);
+    }
+
+    /// <summary>
+    /// The projection mode
+    /// </summary>
+    public enum ProjectionMode
+    {
+        Spin = 0,
+        EvalOnly = 1,
+    }
+
+    /// <summary>
+    /// Extracts experiments from things, basing them on a given experiment definition
+    /// (Individual Agnostic)
+    /// </summary>
+    /// <typeparam name="T">Individual Type</typeparam>
+    public class ExperimentExtractor<T> : IExperimentExtractor where T : IIndividual<T>
+    {
+        public ExperimentExtractor(PopulationExperiment<T> populationExperiment)
+        {
+            PopulationExperiment = populationExperiment;
+        }
+
+        public PopulationExperiment<T> PopulationExperiment { get; }
+        public ExperimentConfiguration ExperimentConfiguration => PopulationExperiment.PopulationConfig.ExperimentConfiguration;
+
+        public void ExtractFromWholeSamples(string sourceFile, IEnumerable<int> epochs, bool firstOfEpoch, string postfix, string outdir, bool zeroStart, bool appendEpoch)
+        {
+            var wholeSamples = WholeSample<T>.LoadWholeSamples(sourceFile);
+            foreach (var epoch in epochs)
+            {
+                var wholeSample = firstOfEpoch
+                    ? wholeSamples.First(ws => ws.Epoch == epoch)
+                    : wholeSamples.Last(ws => ws.Epoch == epoch);
+                ExtractFromWholeSample(wholeSample, appendEpoch ? postfix + "_" + epoch : postfix, outdir, zeroStart);
+            }
+        }
+
+        public void ExtractFromTraceInfo(string sourceFile, IEnumerable<int> generations, bool firstOfEpoch, string postfix, string outdir, bool zeroStart, bool appendGeneration)
+        {
+            var wholeSamples = TraceInfo<T>.Load(sourceFile).TraceWholeSamples;
+            foreach (var generation in generations)
+            {
+                var wholeSample = firstOfEpoch
+                ? wholeSamples.First(ws => ws.Generations == generation)
+                : wholeSamples.Last(ws => ws.Generations == generation);
+                ExtractFromWholeSample(wholeSample, appendGeneration ? postfix + "_" + generation : postfix, outdir, zeroStart);
+            }
+        }
+
+        private void ExtractFromWholeSample(WholeSample<T> wholeSample, string postfix, string outdir, bool zeroStart)
+        {
+            int epoch = zeroStart ? 0 : wholeSample.Epoch;
+            long generationCount = zeroStart ? 0L : wholeSample.Generations;
+            var population = new Population<T>(wholeSample.Judgements.Select(ij => ij.Individual));
+
+            ExtractFromPopulation(epoch, generationCount, population, postfix, outdir);
+        }
+
+        private void ExtractFromPopulation(int epoch, long generationCount, Population<T> population, string postfix, string outdir)
+        {
+            // popConfig
+            var popConfig = PopulationExperiment.PopulationConfig;
+
+            // population experiment
+            var fileStuff = FileStuff.CreateNow(outdir, "", "", true, false);
+            var popExp = new PopulationExperiment<T>(population, popConfig, fileStuff, 0, generationCount);
+            popExp.Save(postfix, false);
+        }
+        
+        public void ProjectFromWholeSamples(string sourceFile, int? start, int? end, string outFile, IExtractorWholeSampleProjectorPreparer projectorPreparer)
+        {
+            List<WholeSample<T>> wholeSamples;
+            if (sourceFile.StartsWith("tracee"))
+            {
+                wholeSamples = TraceInfo<T>.Load(sourceFile).TraceWholeSamples;
+            }
+            else
+            {
+                wholeSamples = WholeSample<T>.EnumerateWholeSamplesSeries2(sourceFile.Split(';')).Where(ws => (start == null || ws.Epoch >= start) && (end == null || ws.Epoch <= end)).ToList();
+            }
+
+            var projector = projectorPreparer.PrepareProjector<T>(PopulationExperiment);
+            var rand = new MathNet.Numerics.Random.MersenneTwister();
+            var context = new ModelExecutionContext(rand);
+
+            List<WholeSample<T>> projected = WholeSampleProjectionHelpers.Project(null, context, wholeSamples, projector, true, false);
+            WholeSample<T>.SaveWholeSamples(outFile, projected);
+        }
+
+        public void ExtractGenomesFromWithin(string prefix, string outdir)
+        {
+            var population = PopulationExperiment.Population;
+            
+            FileStuff stuff = FileStuff.CreateNow(outdir, prefix, prefix, true, false);
+            using (var fs = stuff.CreateBin("initialpopulation.dat"))
+            {
+                M4M.State.GraphSerialisation.Write(population, fs);
+            }
+            
+            int j = 0;
+            foreach (var i in population.PeekAll())
+            {
+                if (i is object o && o is DenseIndividual di)
+                {
+                    Analysis.SaveGenome(stuff.File("genome" + j + ".dat"), di.Genome);
+                }
+                else
+                {
+                    throw new Exception("Cannot save genome of individual of type " + i.GetType().FullName);
+                }
+
+                j++;
+            }
+        }
+
+        public void WriteOutConfig(string outFilename, int seed = 1)
+        {
+            using (var cw = new System.IO.StreamWriter(outFilename))
+            {
+                PopulationExperiment.WriteOutConfig(cw, new CustomMersenneTwister(seed));
+            }
+        }
+    }
+
+    public static class WholeSampleProjectionHelpers
+    {
+        public static List<WholeSample<T>> Project<T>(TextWriter console, ModelExecutionContext context, List<WholeSample<T>> wholeSamples, IWholeSampleProjector<T> projector, bool replaceTarget, bool printProgress) where T : IIndividual<T>
+        {
+            var sw = new System.Diagnostics.Stopwatch();
+            var projected = new List<WholeSample<T>>();
+
+            for (int i = 0; i < wholeSamples.Count; i++)
+            {
+                var ws = wholeSamples[i];
+
+                projected.Add(projector.Project(context, ws, true));
+                if (printProgress && sw.ElapsedMilliseconds > 5000) // keep the user informed
+                {
+                    console.WriteLine($"{i} / {wholeSamples.Count} = {((double)i / wholeSamples.Count) * 100:0.00}%");
+                    sw.Restart();
+                }
+            }
+
+            return projected;
+        }
+    }
+
+    public interface IExtractorWholeSampleProjectorPreparer
+    {
+        IWholeSampleProjector<T> PrepareProjector<T>(PopulationExperiment<T> evalExp) where T : IIndividual<T>;
+    }
+
+    public class BasicExtractorWholeSampleProjectorPreparer : IExtractorWholeSampleProjectorPreparer
+    {
+        public BasicExtractorWholeSampleProjectorPreparer(ProjectionMode projectionMode, bool noNextExposure)
+        {
+            ProjectionMode = projectionMode; // TODO: validate here
+            NoNextExposure = noNextExposure;
+        }
+
+        public ProjectionMode ProjectionMode { get; }
+        public bool NoNextExposure { get; }
+
+        public IWholeSampleProjector<T> PrepareProjector<T>(PopulationExperiment<T> evalExp) where T : IIndividual<T>
+        {
+            var popConfig = evalExp.PopulationConfig;
+            var config = popConfig.ExperimentConfiguration;
+
+            var populationSpinner = popConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<T>.Instance;
+
+            var rrules = config.ReproductionRules;
+            var drules = config.DevelopmentRules;
+            var jrules = config.JudgementRules;
+
+            var selectorPreparer = popConfig.SelectorPreparer;
+            var generations = config.GenerationsPerTargetPerEpoch;
+
+            WholeSample<T> project(ModelExecutionContext context, WholeSample<T> ws, bool replaceTarget)
+            {
+                var rand = context.Rand;
+                var pop = new Population<T>(ws.Judgements.Select(ij => ij.Individual));
+
+                if (ProjectionMode == ProjectionMode.Spin)
+                {
+                    IReadOnlyList<IndividualJudgement<T>> judgements = null;
+
+                    ITarget lastTarget = ws.Target;
+                    // TODO: this should probably be using a target cycler
+                    foreach (var target in config.Targets)
+                    {
+                        if (!NoNextExposure)
+                        {
+                            ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                            target.NextExposure(rand, jrules, ws.Epoch, ref exposureInformation);
+                        }
+
+                        judgements = populationSpinner.SpinPopulation(pop, context, rrules, drules, jrules, target, selectorPreparer, generations, null, popConfig.EliteCount, popConfig.HillclimberMode);
+
+                        lastTarget = target;
+                    }
+
+                    return new WholeSample<T>(ws.Generations, ws.Epoch, replaceTarget ? lastTarget : ws.Target, judgements);
+                }
+                else if (ProjectionMode == ProjectionMode.EvalOnly)
+                {
+                    var target = config.Targets.Last();
+                    target.NextGeneration(rand, jrules);
+                    var judgements = pop.ExtractAndJudgeAll(jrules, target);
+                    return new WholeSample<T>(ws.Generations, ws.Epoch, replaceTarget ? target : ws.Target, judgements);
+                }
+                else
+                {
+                    throw new ArgumentException("Invalid projection mode: " + ProjectionMode.ToString());
+                }
+            }
+
+            return new LambdaWholesampleProjection<T>(project);
+        }
+    }
+
+    public interface IWholeSampleProjector<T> where T : IIndividual<T>
+    {
+        WholeSample<T> Project(ModelExecutionContext context, WholeSample<T> wholeSample, bool replaceTarget);
+    }
+
+    public class LambdaWholesampleProjection<T> : IWholeSampleProjector<T> where T : IIndividual<T>
+    {
+        public LambdaWholesampleProjection(Func<ModelExecutionContext, WholeSample<T>, bool, WholeSample<T>> projector)
+        {
+            Projector = projector;
+        }
+
+        private Func<ModelExecutionContext, WholeSample<T>, bool, WholeSample<T>> Projector { get; }
+
+        public WholeSample<T> Project(ModelExecutionContext context, WholeSample<T> wholeSample, bool replaceTarget)
+        {
+            return Projector(context, wholeSample, replaceTarget);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliGen.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliGen.cs
new file mode 100644
index 0000000..3db587c
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliGen.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace M4M
+{
+    public class CliGen : ICliProvider
+    {
+        public string Key => "gen";
+        
+        public string DefaultTopDir { get; }
+        public Dictionary<string, Action<TextWriter, string, CliParams>> GenSets { get; }
+
+        public void AddGenSet(string name, Action<TextWriter, string, CliParams> genSet)
+        {
+            GenSets.Add(name, genSet);
+        }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            string set = clips.Get("gen", null);
+
+            if (set == null || !GenSets.ContainsKey(set))
+            {
+                console.WriteLine("Available Sets:");
+
+                foreach (var vk in GenSets)
+                {
+                    console.WriteLine(vk.Key);
+                }
+
+                return;
+            }
+            else
+            {
+                string topdir = clips.Get("topdir", DefaultTopDir);
+                console.WriteLine("Generating " + set + " into " + topdir);
+
+                Action<TextWriter, string, CliParams> genset = GenSets[set];
+                genset(console, topdir, clips);
+            }
+        }
+
+        public static readonly Dictionary<string, Action<TextWriter, string, CliParams>> DefaultGenSets = new Dictionary<string, Action<TextWriter, string, CliParams>>(StringComparer.InvariantCultureIgnoreCase)
+        {
+            ["composedense"] = M4M.ExperimentComposition.Typical.DefaultDenseExperimentComposer.Gen,
+        };
+
+        public CliGen(string defaultTopDir, Dictionary<string, Action<TextWriter, string, CliParams>> genSets)
+        {
+            DefaultTopDir = defaultTopDir;
+            GenSets = genSets;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliGroup.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliGroup.cs
new file mode 100644
index 0000000..e0e74fa
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliGroup.cs
@@ -0,0 +1,269 @@
+using M4M;
+using M4M.ExperimentGroups;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace M4M
+{
+    public class CliStackGroup : ICliProvider
+    {
+        public string Key => "StackGroup";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            StackGroup(clips);
+        }
+
+        public static void StackGroup(CliParams clips)
+        {
+            var dir = clips.Get("StackGroup");
+            var groupName = clips.Get("GroupName");
+            var trajFilename = clips.Get("TrajFilename");
+            var trajectory = clips.Get("TrajectoryIndex", int.Parse, 0);
+            var blockName = clips.Get("Block", null);
+            var prefix = clips.Get("Prefix", "trajTraces");
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+
+            if (blockName == null)
+            {
+                var blocks = ExpInfo.EnumerateExps(dir, groupName, trajFilename, blockTerminator).GroupBy(e => e.Block);
+                foreach (var block in blocks)
+                {
+                    var exps = block.AsEnumerable().OrderBy(e => e.Run).ThenBy(e => e.Repeat);
+                    var trajs = StackTrajectories(exps, trajFilename, trajectory, out var samplePeriod);
+                    Analysis.SaveTrajectories(prefix + block.First().Block + ".dat", trajs, samplePeriod);
+                }
+            }
+            else
+            {
+                var trajs = StackTrajectories(dir, groupName, blockName, blockTerminator, trajFilename, trajectory, out var samplePeriod);
+                Analysis.SaveTrajectories(prefix + blockName + ".dat", trajs, samplePeriod);
+            }
+        }
+
+        public static double[][] StackTrajectories(string dir, string groupName, string blockName, string blockTerminator, string trajFilename, int trajectory, out int samplePeriod)
+        {
+            var exps = ExpInfo.EnumerateExps(dir, groupName, trajFilename, blockTerminator)
+                .Where(e => e.Block.Equals(blockName, StringComparison.InvariantCultureIgnoreCase))
+                .OrderBy(e => e.Run).ThenBy(e => e.Repeat);
+            var trajs = StackTrajectories(exps, trajFilename, trajectory, out samplePeriod);
+            return trajs;
+        }
+
+        public static double[][] StackTrajectories(IEnumerable<ExpInfo> exps, string trajFilename, int trajectory, out int samplePeriod)
+        {
+            int _samplePeriod = -1;
+
+            var trajs = exps
+                .Select(e => ExpSamplers.SampleTrajectory(e, trajFilename, trajectory, "", out _samplePeriod))
+                .Select(s => s.Samples.ToArray())
+                .ToArray();
+
+            samplePeriod = _samplePeriod;
+            return trajs;
+        }
+    }
+
+    public class CliSatGroup : ICliProvider
+    {
+        public string Key => "SatGroup";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            SatGroup(console, clips);
+        }
+
+        public static void SatGroup(TextWriter console, CliParams clips)
+        {
+            var dir = clips.Get("SatGroup");
+            var groupName = clips.Get("GroupName");
+            var satExpFile = clips.Get("SatExpFile");
+            var wholesampleFilename = clips.Get("WholesampleFilename");
+            var blockTitle = clips.Get("BlockTitle");
+            var projectionMode = clips.Get("ProjectionMode", CliProject.ParseProjectionMode, ProjectionMode.EvalOnly);
+            var saturateExistingTarget = clips.Get("SaturateExistingTarget", bool.Parse, false);
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+
+            var task = clips.Get("task", "all");
+
+            var dirs = dir.Split(';');
+            var groupNames = groupName.Split(';');
+
+            if (task.Equals("all", StringComparison.InvariantCultureIgnoreCase))
+            {
+                SatGroupAll(console, clips, dirs, groupNames, satExpFile, wholesampleFilename, blockTitle, blockTerminator, projectionMode, saturateExistingTarget);
+            }
+            else if (task.Equals("project", StringComparison.InvariantCultureIgnoreCase))
+            {
+                ProjectSatGroup<DenseIndividual>(console, clips, dirs, groupNames, satExpFile, wholesampleFilename, blockTerminator, projectionMode, saturateExistingTarget);
+            }
+        }
+
+        // TODO: this should really be broken up into one task that produces the saturated wholesamples, and the other tasks which do the work
+        // These could appear as a CliBlockAnalysis class or something (err... 2 of them are plotters...)
+        public static void SatGroupAll(TextWriter console, CliParams clips, IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string satExpFile, string wholesampleFilename, string blockTitle, string blockTerminator, ProjectionMode projectionMode, bool saturateExistingTarget)
+        {
+            if (dirs.Count == 0)
+                throw new ArgumentException("Must provide atleast one Dir and GroupName");
+            if (dirs.Count != groupNames.Count)
+                throw new ArgumentException("Must provide the same number of Dirs and groupNames");
+
+            var sats = ProjectSatGroup<DenseIndividual>(console, clips, dirs, groupNames, satExpFile, wholesampleFilename, blockTerminator, projectionMode, saturateExistingTarget);
+
+            var aliases = GroupPlotHelpers.Aliases(clips);
+
+            var traceesplot = GroupPlotting.PlotBlockTracees(sats, ws => ws.Judgements[0].Judgement.CombinedFitness);
+            SimplePdfPlotExporter.ExportToPdf(traceesplot, "SatTracees.pdf", 300, 300, false, false, null);
+
+            var boxplot = GroupPlotting.PlotBox(sats, "Mean fitness distribution", blockTitle, "Fitness", aliases, GroupPlotting.TerminalWholesampleSelector);
+            SimplePdfPlotExporter.ExportToPdf(boxplot, "SatBoxPlots.pdf", 300, 300, false, false, null);
+        }
+
+        public static List<ExpWholeSamples<TIndividual>> ProjectSatGroup<TIndividual>(TextWriter console, CliParams clips, IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string satExpFile, string wholesampleFilename, string blockTerminator, ProjectionMode projectionMode, bool saturateExistingTarget) where TIndividual : IIndividual<TIndividual>
+        {
+            if (dirs.Count == 0)
+                throw new ArgumentException("Must provide atleast one Dir and GroupName");
+            if (dirs.Count != groupNames.Count)
+                throw new ArgumentException("Must provide the same number of Dirs and groupNames");
+
+            var sats = new List<ExpWholeSamples<TIndividual>>();
+
+            for (int i = 0; i < dirs.Count; i++)
+            {
+                var sed = new SaturatedExperimentData<TIndividual>(console, dirs[i], groupNames[i], satExpFile, wholesampleFilename, blockTerminator, projectionMode, saturateExistingTarget);
+                sats.AddRange(sed.Sats.OrderBy(e => e.ExpInfo.Block));
+                SaturatedExperimentData<TIndividual>.ProcessWholeSamples(console, sats, sed.SatExp, null);
+            }
+
+            return sats;
+        }
+    }
+
+    public class CliRunonGroup : ICliProvider
+    {
+        public string Key => "RunonGroup";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            RunonGroup(console, clips);
+        }
+
+        public static void RunonGroup(TextWriter console, CliParams clips)
+        {
+            var dir = clips.Get("RunonGroup");
+            var groupName = clips.Get("GroupName");
+            var runonExpFile = clips.Get("RunonExpFile");
+            var expFilename = clips.Get("ExpFilename");
+            var blockTitle = clips.Get("BlockTitle");
+            var postfix = clips.Get("Postfix");
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+
+            var width = clips.Get("width", double.Parse, 600);
+            var height = clips.Get("height", double.Parse, 300);
+
+            double[] thresholds = clips.Get("threshold", s => s.Split(';').Select(double.Parse).ToArray(), new double[0]);
+
+            //
+            var runons = RunonGroup<DenseIndividual>(console, dir.Split(';'), groupName.Split(';'), runonExpFile, expFilename, postfix, blockTerminator);
+
+            var boxplot = GroupPlotting.PlotBox(runons, "Mean fitness distribution", blockTitle, "Fitness", null, GroupPlotting.MeanWholesampleSelector);
+            foreach (var threshold in thresholds)
+            {
+                boxplot.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = threshold, Color = OxyPlot.OxyColors.Gray });
+            }
+            SimplePdfPlotExporter.ExportToPdf(boxplot, "RunonBoxPlots.pdf", width, height, false, false, null);
+
+            foreach (var threshold in thresholds)
+            {
+                console.WriteLine($"# Threshold {threshold}");
+
+                foreach (var exp in runons.GroupBy(e => e.ExpInfo.Block).OrderBy(ct => ct.Key))
+                {
+                    var passRate = (double)exp.Count(ws => ws.Samples.Average(s => s.Judgements[0].Judgement.Benefit) >= threshold) / exp.Count();
+                    console.WriteLine(exp.Key + ": " + passRate);
+                }
+
+                console.WriteLine();
+            }
+        }
+
+        public static List<ExpWholeSamples<TIndividual>> RunonGroup<TIndividual>(TextWriter console, IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string runonExpFile, string expFilename, string postfix, string blockTerminator) where TIndividual : IIndividual<TIndividual>
+        {
+            if (dirs.Count == 0)
+                throw new ArgumentException("Must provide atleast one Dir and GroupName");
+            if (dirs.Count != groupNames.Count)
+                throw new ArgumentException("Must provide the same number of Dirs and groupNames");
+
+            var runons = new List<ExpWholeSamples<TIndividual>>();
+
+            for (int i = 0; i < dirs.Count; i++)
+            {
+                var runon = new RunonExperimentData<TIndividual>(console, dirs[i], groupNames[i], runonExpFile, expFilename, postfix, blockTerminator);
+                runons.AddRange(runon.Runons.OrderBy(e => e.ExpInfo.Block));
+            }
+
+            return runons;
+        }
+    }
+
+    public class CliTimeToHierarchyGroup : ICliProvider
+    {
+        public string Key => "TimeToHierarchyGroup";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            StackGroup(clips);
+        }
+
+        public static void StackGroup(CliParams clips)
+        {
+            var dir = clips.Get("StackGroup");
+            var groupName = clips.Get("GroupName");
+            var trajFilename = clips.Get("TrajFilename");
+            var trajectory = clips.Get("TrajectoryIndex", int.Parse, 0);
+            var blockName = clips.Get("Block", null);
+            var prefix = clips.Get("Prefix", "trajTraces");
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+
+            if (blockName == null)
+            {
+                var blocks = ExpInfo.EnumerateExps(dir, groupName, trajFilename, blockTerminator).GroupBy(e => e.Block);
+                foreach (var block in blocks)
+                {
+                    var exps = block.AsEnumerable().OrderBy(e => e.Run).ThenBy(e => e.Repeat);
+                    var trajs = StackTrajectories(exps, trajFilename, trajectory, out var samplePeriod);
+                    Analysis.SaveTrajectories(prefix + block.First().Block + ".dat", trajs, samplePeriod);
+                }
+            }
+            else
+            {
+                var trajs = StackTrajectories(dir, groupName, blockName, blockTerminator, trajFilename, trajectory, out var samplePeriod);
+                Analysis.SaveTrajectories(prefix + blockName + ".dat", trajs, samplePeriod);
+            }
+        }
+
+        public static double[][] StackTrajectories(string dir, string groupName, string blockName, string blockTerminator, string trajFilename, int trajectory, out int samplePeriod)
+        {
+            var exps = ExpInfo.EnumerateExps(dir, groupName, trajFilename, blockTerminator)
+                .Where(e => e.Block.Equals(blockName, StringComparison.InvariantCultureIgnoreCase))
+                .OrderBy(e => e.Run).ThenBy(e => e.Repeat);
+            var trajs = StackTrajectories(exps, trajFilename, trajectory, out samplePeriod);
+            return trajs;
+        }
+
+        public static double[][] StackTrajectories(IEnumerable<ExpInfo> exps, string trajFilename, int trajectory, out int samplePeriod)
+        {
+            int _samplePeriod = -1;
+
+            var trajs = exps
+                .Select(e => ExpSamplers.SampleTrajectory(e, trajFilename, trajectory, "", out _samplePeriod))
+                .Select(s => s.Samples.ToArray())
+                .ToArray();
+
+            samplePeriod = _samplePeriod;
+            return trajs;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliGroupPlot.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliGroupPlot.cs
new file mode 100644
index 0000000..d81190f
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliGroupPlot.cs
@@ -0,0 +1,298 @@
+using M4M;
+using M4M.ExperimentGroups;
+using M4M.Modular;
+using OxyPlot;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace M4M
+{
+    public class CliGroupTraces : ICliPlotter
+    {
+        public string Prefix => "GroupTraces";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var dir = filename;
+            var groupName = clips.Get("GroupName");
+            var wholesampleFilename = clips.Get("WholesampleFilename");
+            var trajectoryFileName = clips.Get("TrajectoryFileName");
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+
+            var dirs = dir.Split(';');
+            var groupNames = groupName.Split(';');
+
+            if (wholesampleFilename != null)
+            {
+                return PlotGroupTracees(dirs, groupNames, wholesampleFilename, blockTerminator);
+            }
+            else if (trajectoryFileName != null)
+            {
+                var trajectoryIndex = clips.Get("trajectoryIndex", int.Parse);
+                return PlotGroupsTracees(dirs, groupNames, trajectoryFileName, trajectoryIndex, blockTerminator);
+            }
+            else
+            {
+                console.WriteLine("Must qualify either the WholesampleFilename or the TrajectoryFileName.");
+                return null;
+            }
+        }
+
+        public static PlotModel PlotGroupTracees(IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string wholesampleFilename, string blockTerminator)
+        {
+            var expSamples = GroupPlotHelpers.EnumerateManyExpInfos(dirs, groupNames, wholesampleFilename, blockTerminator).Select(e => ExpWholeSamples<DenseIndividual>.Sample(e, wholesampleFilename, ""));
+
+            return GroupPlotting.PlotBlockTracees(expSamples, ws => ws.Judgements[0].Judgement.CombinedFitness);
+        }
+
+        public static PlotModel PlotGroupsTracees(IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string trajectoryFilename, int trajectoryIndex, string blockTerminator)
+        {
+            int sampleRate = -1;
+            var expSamples = GroupPlotHelpers.EnumerateManyExpInfos(dirs, groupNames, trajectoryFilename, blockTerminator).Select(e => ExpSamplers.SampleTrajectory(e, trajectoryFilename, trajectoryIndex, ""+trajectoryIndex, out sampleRate));
+
+            return GroupPlotting.PlotBockTracees(expSamples, x => x, sampleRate);
+        }
+    }
+
+    public class CliGroupBox : ICliPlotter
+    {
+        public string Prefix => "GroupBox";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var dir = filename;
+            var groupName = clips.Get("GroupName");
+            var runonExpFile = clips.Get("RunonExpFile");
+            var blockTitle = clips.Get("BlockTitle");
+            var wholesampleFilename = clips.Get("WholesampleFilename");
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+
+            var dirs = dir.Split(';');
+            var groupNames = groupName.Split(';');
+
+            double[] thresholds = clips.Get("threshold", s => s.Split(';').Select(double.Parse).ToArray(), new double[0]);
+
+            var selector = clips.Get("selector", s => GroupPlotHelpers.ParseSelector<DenseIndividual>(s));
+
+            return PlotGroupBox(dirs, groupNames, blockTitle, wholesampleFilename, selector, blockTerminator);
+        }
+
+        public static PlotModel PlotGroupBox<TIndividual>(IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string blockTitle, string wholesampleFilename, Func<IEnumerable<WholeSample<TIndividual>>, double> selector, string blockTerminator) where TIndividual : IIndividual<TIndividual>
+        {
+            var expSamples = GroupPlotHelpers.EnumerateManyExpInfos(dirs, groupNames, wholesampleFilename, blockTerminator).Select(e => ExpWholeSamples<TIndividual>.Sample(e, wholesampleFilename, ""));
+            return GroupPlotting.PlotBox(expSamples, "Mean Terminal Fitness Distributions", blockTitle, "Mean Fitness", null, selector);
+        }
+    }
+
+    public class CliGroupMeanRowSum : ICliPlotter
+    {
+        public string Prefix => "GroupMeanRowSum";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var expFilename = clips.Get("ExpFilename");
+            var sampler = new RowSumExpSamples(expFilename);
+            var aliases = GroupPlotHelpers.Aliases(clips);
+            var expSamples = GroupPlotHelpers.GroupSampling(clips, expFilename, sampler);
+            return GroupPlotting.PlotAverage(expSamples, title ?? "Mean row-sum", "Group", "Weight", aliases, ss => ss.Average());
+        }
+    }
+
+    public class CliGroupPostWinFreq : ICliPlotter
+    {
+        public string Prefix => "GroupPostWinFreq";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var expFilename = clips.Get("ExpFilename");
+            var count = clips.Get("count", int.Parse, 10);
+            var sampler = new RunonWinFreqExpSampler(count, expFilename);
+            var aliases = GroupPlotHelpers.Aliases(clips);
+            var expSamples = GroupPlotHelpers.GroupSampling(clips, expFilename, sampler);
+            return GroupPlotting.PlotAverage(expSamples, title ?? "Mean frequency of most fit phenotype", "Group", "Frequency", aliases, ss => ss.Average(b => b ? 1 : 0));
+        }
+    }
+
+    public class CliGroupTimeToHierarchy : ICliPlotter
+    {
+        public string Prefix => "GroupTimeToHierarchy";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var rcsFilename = clips.Get("RcsFilename");
+            var modules = clips.Get("modules", Modular.MultiModulesStringParser.Instance.Parse);
+            var threshold = clips.Get("huskythreshold", double.Parse, 0.9);
+            var sampler = new TimeToHierarchyExpSampler(modules, rcsFilename, threshold);
+            var aliases = GroupPlotHelpers.Aliases(clips);
+            var expSamples = GroupPlotHelpers.GroupSampling(clips, rcsFilename, sampler);
+
+            if (clips.IsSet("boxplot"))
+            {
+                return GroupPlotting.PlotBox(expSamples, title ?? $"Mean time-to-hierarchy (threshold = {threshold})", "Group", "Epochs", aliases, ss => ss.Average(b => b.HasValue ? (double)b.Value : double.NaN));
+            }
+            else
+            {
+                return GroupPlotting.PlotAverage(expSamples, title ?? $"Time-to-hierarchy (threshold = {threshold})", "Group", "Epochs", aliases, ss => ss.Average(b => b.HasValue ? (double)b.Value : double.NaN));
+            }
+        }
+    }
+
+    public interface IExpSampler<T>
+    {
+        ExpSamples<T> Sample(ModelExecutionContext ctx, ExpInfo xpInfo);
+    }
+
+    public class RowSumExpSamples : IExpSampler<double>
+    {
+        public RowSumExpSamples(string expFileName)
+        {
+            ExpFileName = expFileName ?? throw new ArgumentNullException(nameof(expFileName));
+        }
+
+        public string ExpFileName { get; }
+
+        public ExpSamples<double> Sample(ModelExecutionContext ctx, ExpInfo expInfo)
+        {
+            var expFile = System.IO.Path.Combine(expInfo.Dir, ExpFileName);
+            Console.WriteLine("Exp " + expFile);
+
+            var exp = PopulationExperiment<DenseIndividual>.Load(expFile);
+            var samples = exp.Population.ExtractAll().SelectMany(i => i.Genome.TransMat.RowSums()).ToList();
+
+            return new ExpSamples<double>(expInfo, samples, $"");
+        }
+    }
+
+    public class RunonWinFreqExpSampler : IExpSampler<bool>
+    {
+        public RunonWinFreqExpSampler(int runonCount, string expFileName)
+        {
+            RunonCount = runonCount;
+            ExpFileName = expFileName ?? throw new ArgumentNullException(nameof(expFileName));
+        }
+
+        public int RunonCount { get; }
+        public string ExpFileName { get; }
+
+        public ExpSamples<bool> Sample(ModelExecutionContext ctx, ExpInfo expInfo)
+        {
+            var expFile = System.IO.Path.Combine(expInfo.Dir, ExpFileName);
+            Console.WriteLine("Exp " + expFile);
+
+            var exp = PopulationExperiment<DenseIndividual>.Load(expFile);
+            var targets = exp.PopulationConfig.ExperimentConfiguration.Targets.ToArray();
+
+            var samples = new List<bool>();
+            void feedback(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+            {
+                // test for win
+                var p = oldPopulationJudgements[0].Individual.Phenotype.Vector.Saturate(-1, 0, +1);
+                var t = ((VectorTarget)target).PreparePerfectP().Saturate(-1, 0, +1);
+                samples.Add(t.Equals(p));
+            }
+
+            PopulationTrace.RunTrace(System.Console.Out, ctx, null, exp.Population, exp.PopulationConfig, targets, null, feedback, exp.Epoch, RunonCount);
+            return new ExpSamples<bool>(expInfo, samples, $"Count = {RunonCount}");
+        }
+    }
+
+    public class TimeToHierarchyExpSampler : IExpSampler<int?>
+    {
+        public TimeToHierarchyExpSampler(Modules modules, string rcsFileName, double huskyComputeThreshold)
+        {
+            Modules = modules ?? throw new ArgumentNullException(nameof(modules));
+            RcsFileName = rcsFileName ?? throw new ArgumentNullException(nameof(rcsFileName));
+            HuskyComputeThreshold = huskyComputeThreshold;
+        }
+
+        public Modular.Modules Modules { get; }
+        public string RcsFileName { get; }
+        public double HuskyComputeThreshold { get; }
+
+        public ExpSamples<int?> Sample(ModelExecutionContext ctx, ExpInfo expInfo)
+        {
+            var rcsFile = System.IO.Path.Combine(expInfo.Dir, RcsFileName);
+            var trajectories = Analysis.LoadTrajectories(rcsFile, out var samplePeriod);
+            var N = (int)Math.Round(Math.Sqrt(trajectories.Length));
+            var mts = Modules.ModuleAssignments;
+
+            var dtms = Analysis.EnumerateMatricies(trajectories, N, N, 0, trajectories[0].Length, true);
+            var huskynesses = dtms.Select(dtm => Analysis.ComputeModuleHuskyness(dtm, mts)).ToArray();
+
+            var completions = Enumerable.Range(0, Modules.ModuleCount).Select(mi => huskynesses.FirstIndex(hs => hs[mi] >= HuskyComputeThreshold && hs[mi] <= 1, out var idx, out _) ? (int?)(idx * samplePeriod) : null).ToArray();
+            Console.WriteLine(string.Join(", ", completions));
+            return new ExpSamples<int?>(expInfo, completions, $"Threshold = {HuskyComputeThreshold}");
+        }
+    }
+
+    public class GroupPlotHelpers
+    {
+        public static Func<IEnumerable<WholeSample<TIndividual>>, double> ParseSelector<TIndividual>(string name) where TIndividual : IIndividual<TIndividual>
+        {
+            if (name.Equals("mean", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return GroupPlotting.MeanWholesampleSelector;
+            }
+            else if (name.Equals("terminal", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return GroupPlotting.TerminalWholesampleSelector;
+            }
+            else
+            {
+                throw new ArgumentException("Unrecognised selector name " + name);
+            }
+        }
+
+        public static IEnumerable<ExpInfo> EnumerateManyExpInfos(IReadOnlyList<string> dirs, IReadOnlyList<string> groupNames, string pattern, string blockTerminator)
+        {
+            if (dirs.Count != groupNames.Count)
+                throw new ArgumentException("Must provide the same number of directories and group mames");
+
+            for (int i = 0; i < dirs.Count; i++)
+            {
+                foreach (var e in ExpInfo.EnumerateExps(dirs[i], groupNames[i], pattern, blockTerminator))
+                    yield return e;
+            }
+        }
+
+        public static List<ExpSamples<T>> GroupSampling<T>(CliParams clips, string filepattern, IExpSampler<T> sampler)
+        {
+            var dir = clips.Get("dir", ".");
+            var groupName = clips.Get("GroupName");
+            var blockTerminator = clips.Get("blockTerminator", "runs");
+            var count = clips.Get("count", int.Parse, 10);
+            var seed = clips.Get("seed", int.Parse, 1);
+
+            var exps = ExpInfo.EnumerateExps(dir, groupName, filepattern, blockTerminator);
+            var expSamples = new List<ExpSamples<T>>();
+
+            var ctx = new ModelExecutionContext(new CustomMersenneTwister(seed));
+
+            foreach (var expInfo in exps)
+            {
+                expSamples.Add(sampler.Sample(ctx, expInfo));
+            }
+
+            return expSamples;
+        }
+
+        public static Func<string, string> Aliases(CliParams clips)
+        {
+            var aliasFilename = clips.Get("aliases", "xAliases.txt");
+
+            Func<string, string> aliases;
+            if (File.Exists(aliasFilename))
+            {
+                aliases = CliBestiary.Defaulting(CliBestiary.ReadAliases(aliasFilename));
+            }
+            else
+            {
+                aliases = x => x;
+            }
+
+            return aliases;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliHebbian.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliHebbian.cs
new file mode 100644
index 0000000..2388ffb
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliHebbian.cs
@@ -0,0 +1,309 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Linear = MathNet.Numerics.LinearAlgebra;
+using MathNet.Numerics.Random;
+using MathNet.Numerics.LinearAlgebra;
+using System.IO;
+
+namespace M4M
+{
+    public interface IVectorExtractor<T> where T : IIndividual<T>
+    {
+        string Name { get; }
+        string Description { get; }
+        Linear.Vector<double> Extract(T individual);
+    }
+
+    public class InitialStateExtractor : IVectorExtractor<DenseIndividual>
+    {
+        private InitialStateExtractor()
+        {
+        }
+
+        public static InitialStateExtractor Instance { get; } = new InitialStateExtractor();
+
+        public string Name => nameof(InitialStateExtractor);
+        public string Description => Name;
+
+        public Vector<double> Extract(DenseIndividual individual)
+        {
+            return individual.Genome.InitialState;
+        }
+    }
+
+    public class PhenotypeExtractor : IVectorExtractor<DenseIndividual>
+    {
+        private PhenotypeExtractor()
+        {
+        }
+
+        public static PhenotypeExtractor Instance { get; } = new PhenotypeExtractor();
+
+        public string Name => nameof(PhenotypeExtractor);
+        public string Description => Name;
+
+        public Vector<double> Extract(DenseIndividual individual)
+        {
+            return individual.Phenotype.Vector;
+        }
+    }
+
+    public class CliHebbian : ICliProvider
+    {
+        public string Key => "hebbian";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            var source = clips.Get("hebbian");
+            var filename = System.IO.Path.GetFileName(source);
+
+            if (filename.StartsWith("epoch"))
+            {
+                var exp = PopulationExperiment<DenseIndividual>.Load(source);
+
+                RunOnExperiment(console, clips, exp);
+            }
+            else if (filename.StartsWith("wholesamples"))
+            {
+                List<WholeSample<DenseIndividual>> wholeSamples;
+                if (source.Contains(";"))
+                {
+                    wholeSamples = WholeSample<DenseIndividual>.EnumerateWholeSamplesSeries2(source.Split(';')).ToList();
+                }
+                else
+                {
+                    wholeSamples = WholeSample<DenseIndividual>.LoadWholeSamples(source);
+                }
+
+                TraceInfo<DenseIndividual> traceInfo = new TraceInfo<DenseIndividual>(wholeSamples.ToList());
+                RunOnTracee(console, clips, traceInfo, true);
+            }
+            else if (filename.StartsWith("tracee"))
+            {
+                var traceInfo = TraceInfo<DenseIndividual>.Load(source);
+                RunOnTracee(console, clips, traceInfo, false);
+            }
+            else
+            {
+                throw new Exception("Hebbian must occur on an experiment file, wholesamples, ist, or pst");
+            }
+        }
+
+        public void RunOnExperiment(TextWriter console, CliParams clips, PopulationExperiment<DenseIndividual> exp)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Hebbian (epoch) Options:\n" +
+                    " - seed\n" +
+                    " - generations\n" +
+                    " - extractor\n" +
+                    " - initialReset\n" +
+                    " - fitnesspowerfactor\n");
+            }
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new CustomMersenneTwister(seed);
+            var context = new ModelExecutionContext(rand);
+            var generationsOverride = clips.Get("generations", s => (int?)Misc.ParseInt(s), null);
+
+            var extractor = clips.Get("extractor", ParseDenseExtractor, InitialStateExtractor.Instance);
+
+            var fitnessPowerFactor = clips.GetOrCreate("fitnesspowerfactor", double.Parse, () => clips.IsSet("weighted") ? 1.0 : 0.0);
+            var initialReset = clips.GetOrCreate("initialReset", ExperimentParsing.ParsePopulationResetOperation,
+                () => clips.Get("randomVector", s => new RandomVectorProviderPopulationResetOperation(CliBestiary.ParseVectorProvider(s)), null));
+
+            var samples = GenerateSamples(console, exp, clips, rand, true, initialReset, generationsOverride);
+            var mean = MeanHebbian(samples, extractor, fitnessPowerFactor);
+            WriteOutHebbian(console, clips, exp.Population.PeekAll()[0].Genome, context, mean);
+        }
+
+        public void RunOnTracee(TextWriter console, CliParams clips, TraceInfo<DenseIndividual> traceInfo, bool epochsx)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Hebbian (wholesamples) Options:\n" +
+                    " - seed\n" +
+                    " - start\n" +
+                    " - end\n" +
+                    " - extractor\n" +
+                    " - initialReset\n" +
+                    " - fitnesspowerfactor\n");
+            }
+
+            if (epochsx)
+            {
+                int start = clips.Get("start", Misc.ParseInt, traceInfo.TraceWholeSamples.First().Epoch);
+                int end = clips.Get("end", Misc.ParseInt, traceInfo.TraceWholeSamples.Last().Epoch);
+
+                traceInfo = traceInfo.ExtractEpochs(start, end);
+            }
+            else
+            {
+                long start = clips.Get("start", Misc.ParseLong, traceInfo.TraceWholeSamples.First().Generations);
+                long end = clips.Get("end", Misc.ParseLong, traceInfo.TraceWholeSamples.Last().Generations);
+
+                traceInfo = traceInfo.ExtractGenerations(start, end);
+            }
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new CustomMersenneTwister(seed);
+            var context = new ModelExecutionContext(rand);
+
+            var extractor = clips.Get("extractor", ParseDenseExtractor, InitialStateExtractor.Instance);
+
+            var fitnessPowerFactor = clips.GetOrCreate("fitnesspowerfactor", double.Parse, () => clips.IsSet("weighted") ? 1.0 : 0.0);
+            var initialReset = clips.GetOrCreate("initialReset", ExperimentParsing.ParsePopulationResetOperation,
+                () => clips.Get("randomVector", s => new RandomVectorProviderPopulationResetOperation(CliBestiary.ParseVectorProvider(s)), null));
+
+            var samples = traceInfo.TraceWholeSamples.SelectMany(ws => ws.Judgements).ToList();
+            var mean = MeanHebbian(samples, extractor, fitnessPowerFactor);
+            WriteOutHebbian(console, clips, samples[0].Individual.Genome, context, mean);
+        }
+
+        public static Matrix<double> MeanHebbian<T>(IReadOnlyList<IndividualJudgement<T>> samples, IVectorExtractor<T> extractor, double fitnessPowerFactor) where T : IIndividual<T>
+        {
+            Matrix<double> total = null;
+            var fsum = 0.0;
+            foreach (var sample in samples)
+            {
+                var v = extractor.Extract(sample.Individual).Clone();
+
+                if (fitnessPowerFactor != 0)
+                {
+                    var ffac = Math.Pow(sample.Judgement.CombinedFitness, fitnessPowerFactor);
+                    fsum += ffac;
+                    v.Multiply(ffac, v);
+                }
+                else
+                {
+                    fsum++;
+                }
+
+                if (total == null)
+                    total = CreateMatrix.Dense<double>(v.Count, v.Count);
+
+                total.Add(v.OuterProduct(v), total);
+            }
+
+            if (total == null)
+                throw new ArgumentException("No samples");
+
+            return total / fsum;
+        }
+
+        public static void WriteOutHebbian(TextWriter console, CliParams clips, DenseGenome template, ModelExecutionContext context, Matrix<double> mean)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Hebbian write-out options:\n" +
+                    " - title\n" +
+                    " - out\n" +
+                    " - outdir");
+            }
+
+            var postfix = clips.Get("postfix", "");
+            var title = clips.Get("title", "HebbianMean");
+            var outFile = clips.Get("out", title) + postfix;
+            var outDir = clips.Get("outdir", "");
+
+            if (!string.IsNullOrEmpty(outDir))
+                Misc.EnsureDirectory(outDir);
+
+            var plot = GenomePlotting.PlotDtm(title, mean, mean.Enumerate().Min(), mean.Enumerate().Max());
+            SimplePdfPlotExporter.ExportToPdf(plot, Path.Combine(outDir, outFile + ".pdf"), 500, 500, true, true, null);
+
+            var exportGenome = template.Clone(context, newTransMat: mean);
+            Analysis.SaveGenome(Path.Combine(outDir, "genome_" + outFile + ".dat"), exportGenome);
+        }
+
+        public static List<IndividualJudgement<TIndividual>> GenerateSamples<TIndividual>(TextWriter console, PopulationExperiment<TIndividual> exp, CliParams clips, RandomSource rand, bool printProgress, IPopulationResetOperation<TIndividual> initialReset, int? generationsOverride) where TIndividual : IIndividual<TIndividual>
+        {
+            var popConfig = exp.PopulationConfig;
+            var config = popConfig.ExperimentConfiguration;
+            var startEpoch = clips.Get("startEpoch", int.Parse, exp.Epoch);
+            var epochs = clips.Get("epochs", int.Parse, 1);
+
+            var rrules = config.ReproductionRules;
+            var drules = config.DevelopmentRules;
+            var jrules = config.JudgementRules;
+
+            int[] targetIndexes = clips.GetOrCreate("targets", s => s.Replace(" ", "").Split(new[] { ',', ';' }).Select(int.Parse).ToArray(), () => new int[] { clips.Get("target", int.Parse, 0) });
+            var count = clips.Get("count", int.Parse, 1000);
+
+            var templatePop = exp.Population;
+
+            var context = new ModelExecutionContext(rand);
+            var populationSpinner = popConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<TIndividual>.Instance;
+
+            var resultantIndividuals = new List<IndividualJudgement<TIndividual>>();
+
+            var sw = printProgress ? new System.Diagnostics.Stopwatch() : null;
+            sw?.Start();
+
+            for (int i = 0; i < count; i++)
+            {
+                // new population
+                var pop = templatePop.Clone(context);
+
+                // remember the last target so we can make a judgement at the end
+                ITarget currentTarget = config.Targets[targetIndexes[0]];
+
+                // reset if necessary
+                if (initialReset != null)
+                {
+                    initialReset.Reset(context, pop, config.InitialStateResetRange, drules, rrules, currentTarget);
+                }
+
+                for (int epoch = startEpoch; epoch < startEpoch + epochs; epoch++)
+                    foreach (var targetIndex in targetIndexes)
+                    {
+                        currentTarget = config.Targets[targetIndex];
+
+                        ExposureInformation exposureInformation = new ExposureInformation(generationsOverride ?? popConfig.ExperimentConfiguration.GenerationsPerTargetPerEpoch);
+                        currentTarget.NextExposure(context.Rand, jrules, epoch, ref exposureInformation);
+
+                        // reset if we should
+                        if (context.Rand.NextDouble() < config.InitialStateResetProbability)
+                            popConfig.PopulationResetOperation?.Reset(context, pop, config.InitialStateResetRange, drules, rrules, currentTarget);
+
+                        if (exposureInformation.ExposureDuration <= 0)
+                            continue;
+
+                        // mutate, judge, select cycle
+                        populationSpinner.SpinPopulation(pop, context, rrules, drules, jrules, currentTarget, popConfig.SelectorPreparer, exposureInformation.ExposureDuration, null, popConfig.EliteCount, popConfig.HillclimberMode);
+                    }
+
+                // accumulate products
+                currentTarget.NextGeneration(rand, jrules);
+                resultantIndividuals.AddRange(pop.ExtractAndJudgeAll(jrules, currentTarget));
+
+                if (printProgress && sw.ElapsedMilliseconds > 30000)
+                {
+                    console.WriteLine($"{i}/{count}");
+                    sw.Restart();
+                }
+            }
+
+            return resultantIndividuals;
+        }
+
+        public static IVectorExtractor<DenseIndividual> ParseDenseExtractor(string str)
+        {
+            if (str.Equals("g", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return InitialStateExtractor.Instance;
+            }
+            else if (str.Equals("p", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PhenotypeExtractor.Instance;
+            }
+            else
+            {
+                throw new ArgumentException("Unrecognised dense extractor: " + str + "\nTry one of g or p.");
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliMultiPlot.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliMultiPlot.cs
new file mode 100644
index 0000000..861dc70
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliMultiPlot.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class CliMultiPlot : ICliProvider
+    {
+        public string Key => "multiplot";
+
+        public CliPlot CliPlot { get; }
+
+        public CliMultiPlot(CliPlot cliPlot)
+        {
+            CliPlot = cliPlot;
+        }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            var filename = clips.Get("multiplot");
+            var title = clips.Get("title", null);
+            var outFileName = filename.Contains(";")
+                ? clips.Get("out")
+                : clips.Get("out", filename);
+
+            PreparePlot(console, clips, filename, title, out var plot);
+
+            CliPlot.PlotPlot(console, clips, plot, outFileName);
+        }
+        
+        public bool PreparePlot(TextWriter console, CliParams clips, string filename, string title, out PlotModel plot)
+        {
+            var autoTier = clips.Get("autotier", bool.Parse, true);
+
+            plot = new PlotModel() { Title = title };
+
+            var lines = filename.Contains(";")
+                ? StringHelpers.Split(filename, new CodePoint[] { ';' }, '"', '\\')
+                : File.ReadAllLines(filename);
+
+            foreach (var line in lines)
+            {
+                if (string.IsNullOrWhiteSpace(line))
+                    continue;
+
+                var innerClips = new CliParams();
+                innerClips.ConsumeLine(line);
+                var innerFile = innerClips.Get("plot");
+                
+                if (!CliPlot.PreparePlot(console, innerClips, innerFile, null, out var innerPlot))
+                {
+                    console.WriteLine("Unable to plot innerplot " + innerFile);
+                    continue;
+                }
+
+                foreach (var a in innerPlot.Axes.ToArray())
+                {
+                    innerPlot.Axes.Remove(a);
+
+                    var current = plot.Axes.FirstOrDefault(_a => _a.Key == a.Key);
+                    if (current != null)
+                    {
+                        console.WriteLine($"Combining axis with key {current.Key}, Title: {current.Title} ({a.Title})");
+
+                        if (current.IsHorizontal() != a.IsHorizontal())
+                        {
+                            console.WriteLine(" - Problem: axes have different orientations");
+                        }
+                    }
+                    else
+                    {
+                        if (autoTier)
+                        {
+                            int tier = a.PositionTier;
+                            while (plot.Axes.Any(_a => _a.PositionTier == tier && _a.Position == a.Position))
+                            {
+                                tier++;
+                            }
+                            a.PositionTier = tier;
+                        }
+
+                        plot.Axes.Add(a);
+                    }
+                }
+                foreach (var s in innerPlot.Series.ToArray())
+                {
+                    innerPlot.Series.Remove(s);
+                    plot.Series.Add(s);
+                }
+                foreach (var a in innerPlot.Annotations.ToArray())
+                {
+                    innerPlot.Annotations.Remove(a);
+                    plot.Annotations.Add(a);
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliPlot.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliPlot.cs
new file mode 100644
index 0000000..956a97b
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliPlot.cs
@@ -0,0 +1,3539 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using M4M.Epistatics;
+using M4M.New.OxyEx;
+using MathNet.Numerics.LinearAlgebra;
+using MathNet.Numerics.Random;
+using OxyPlot;
+using OxyPlot.Annotations;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+
+namespace M4M
+{
+    public interface IColourFilter
+    {
+        OxyColor Filter(OxyColor color);
+    }
+
+    // naive RGB blend
+    public class WashoutFilter : IColourFilter
+    {
+        public WashoutFilter(OxyColor washoutColour)
+        {
+            WashoutColour = washoutColour;
+        }
+
+        public OxyColor WashoutColour { get; set; }
+
+        public OxyColor Filter(OxyColor color)
+        {
+            if (color.IsInvisible())
+            { // transparent
+                return OxyColors.Transparent;
+            }
+
+            if (color.A == 255)
+            { // opaque
+                return color;
+            }
+
+            double l = color.A / 255.0;
+
+            color = OxyColor.FromRgb(color.R, color.G, color.B);
+            return OxyColor.Interpolate(WashoutColour, color, l);
+        }
+    }
+
+    /// <summary>
+    /// A render decorator which clips transparent elements
+    /// </summary>
+    public class ClippingRenderDecorator : RenderContextBase
+    {
+        private IRenderContext RenderContext { get; }
+        private HashSet<OxyColor> ClipColours { get; }
+        private bool ClipInvisible { get; }
+        private bool RemoveTransparency { get; }
+        private IColourFilter PreFilter { get; }
+        private bool ClipZeroWidth { get; }
+
+        public ClippingRenderDecorator(IRenderContext renderContext, IEnumerable<OxyColor> clipColours, bool clipInvisible, IColourFilter preFilter, bool clipZeroWidth)
+        {
+            RenderContext = renderContext;
+            ClipInvisible = clipInvisible;
+            PreFilter = preFilter;
+            ClipZeroWidth = clipZeroWidth;
+
+            ClipColours = new HashSet<OxyColor>(clipColours ?? Enumerable.Empty<OxyColor>());
+        }
+
+        public bool Filter(ref OxyColor color)
+        {
+            if (PreFilter != null)
+                color = PreFilter.Filter(color);
+            return (color.IsInvisible() && ClipInvisible) || ClipColours.Contains(color);
+        }
+
+        public override void DrawLine(IList<ScreenPoint> points, OxyColor stroke, double thickness, double[] dashArray, LineJoin lineJoin, bool aliased)
+        {
+            if (ClipZeroWidth && Math.Abs(thickness) == 0.0)
+                return;
+
+            if (Filter(ref stroke))
+                return;
+
+            RenderContext.DrawLine(points, stroke, thickness, dashArray, lineJoin, aliased);
+        }
+
+        public override void DrawPolygon(IList<ScreenPoint> points, OxyColor fill, OxyColor stroke, double thickness, double[] dashArray, LineJoin lineJoin, bool aliased)
+        {
+            bool clipFill = Filter(ref fill);
+            bool clipStroke = Filter(ref stroke);
+
+            if (clipFill && clipStroke)
+                return;
+
+            if (clipFill)
+                fill = OxyColors.Transparent;
+            if (clipStroke)
+                stroke = OxyColors.Transparent;
+
+            RenderContext.DrawPolygon(points, fill, stroke, thickness, dashArray, lineJoin, aliased);
+        }
+
+        public override void DrawText(ScreenPoint p, string text, OxyColor fill, string fontFamily, double fontSize, double fontWeight, double rotate, HorizontalAlignment halign, VerticalAlignment valign, OxySize? maxSize)
+        {
+            if (ClipZeroWidth && Math.Abs(fontSize) == 0.0)
+                return;
+
+            if (Filter(ref fill))
+                return;
+
+            RenderContext.DrawText(p, text, fill, fontFamily, fontSize, fontWeight, rotate, halign, valign, maxSize);
+        }
+
+        public override OxySize MeasureText(string text, string fontFamily, double fontSize, double fontWeight)
+        {
+            return RenderContext.MeasureText(text, fontFamily, fontSize, fontWeight);
+        }
+    }
+
+    public interface ICliPlotter
+    {
+        string Prefix { get; }
+        OxyPlot.PlotModel Plot(TextWriter console, CliParams clips, string filename, string title);
+    }
+
+    public class CliPlot : ICliProvider
+    {
+        public static readonly CliPresets DefaultPresets = new CliPresets("plotpresets", new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
+        {
+            ["gridlines"] = "majorgridlinestyle=solid minorgridlinestyle=dot",
+            ["nominors"] = "minorgridlinecolour=transparent minorticksize=0",
+            ["small"] = "size=0.5 intervalsize=1 minpad=0.05 maxpad=0.05", // switch to ss-margin when we can
+        });
+
+        public CliPresets Presets { get; }
+
+        public static IEnumerable<ICliPlotter> CreateDefaultPlotters()
+        {
+            var traceePlotter = new CliTraceePlotter(CliTraceePlotter.DefaultDenseCliTraceeProviders);
+            var defaultPlotters = new ICliPlotter[] {
+                traceePlotter,
+                new CliTracesPlotter(),
+                CliTrajectoryPlotter.FitnessPlotter,
+                CliTrajectoryPlotter.SudokuSamplesPlotter,
+                CliTrajectoryPlotter.NQueensSamplesPlotter,
+                CliTrajectoryPlotter.IstPlotter,
+                CliTrajectoryPlotter.PstPlotter,
+                CliTrajectoryPlotter.IvmcModuleSwitchesPlotter,
+                CliTrajectoryPlotter.IvmcModuleSolvesPlotter,
+                CliTrajectoryPlotter.IvmcPropersPlotter,
+                new CliRcsPlotter(),
+                new CliGenomePlotter(),
+                new CliWholeSamplePlotter(traceePlotter),
+                new CliPlotMisc(CliPlotMisc.DefaultMiscPlotters),
+                new CliExpPlotter(),
+                new CliGroupTraces(),
+                new CliGroupBox(),
+                new CliTrajTracesPlotter(),
+                new CliGroupMeanRowSum(),
+                new CliGroupPostWinFreq(),
+                new CliGroupTimeToHierarchy()
+            };
+
+            return defaultPlotters;
+        }
+
+        public string Key => "plot";
+
+        public IPlotExporter PlotExporter { get; }
+        public ICliPlotter[] Plotters { get; }
+
+        public CliPlot(IPlotExporter plotExporter, IEnumerable<ICliPlotter> plotters, CliPresets presets)
+        {
+            PlotExporter = plotExporter;
+            Plotters = plotters.ToArray();
+            Presets = presets;
+        }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            Presets.Apply(console, clips);
+
+            string infname = clips.Get("plot");
+
+            if (infname != null)
+            {
+                string outfname = clips.Get("out", infname);
+
+                string ifname = System.IO.Path.GetFileName(infname);
+                string ofname = System.IO.Path.GetFileName(outfname);
+
+                string title = clips.Get("title", ofname);
+
+                if (PreparePlot(console, clips, infname, title, out var plot))
+                {
+                    if (plot == null)
+                    {
+                        console.WriteLine("(No Plot)");
+                    }
+                    else
+                    {
+                        PlotPlot(console, clips, plot, outfname);
+                    }
+
+                    return;
+                }
+                else
+                {
+                    console.WriteLine("Unsure how to plot " + infname);
+                }
+            }
+
+            console.WriteLine("Recognised files (by start of file-name) are:");
+
+            foreach (var plotter in Plotters)
+            {
+                console.WriteLine(plotter.Prefix);
+            }
+        }
+
+        /// <summary>
+        /// Attempts to prepare a plot from the given CliParams, FileName, and Title
+        /// Returns true if a plotter was available, otherwise false
+        /// </summary>
+        public bool PreparePlot(TextWriter console, CliParams clips, string inFileName, string title, out PlotModel plot)
+        {
+            Presets.Apply(console, clips); // re-apply for e.g. M4MPlotting
+
+            string filePrefix = clips.Get("prefix", System.IO.Path.GetFileName(inFileName));
+            console.WriteLine(filePrefix);
+
+            foreach (var plotter in Plotters)
+            {
+                if (filePrefix.StartsWith(plotter.Prefix, StringComparison.InvariantCultureIgnoreCase))
+                {
+                    plot = plotter.Plot(console, clips, inFileName, title);
+                    return true;
+                }
+            }
+
+            plot = null;
+            return false;
+        }
+
+        public void PlotPlot(TextWriter console, CliParams clips, PlotModel plot, string outfilename)
+        {
+            double size = clips.Get("size", double.Parse, 1.0);
+            double width = clips.Get("width", double.Parse, -1);
+            double height = clips.Get("height", double.Parse, -1);
+
+            bool bgBox = clips.IsSet("bgbox");
+
+            bool clipInvisible = clips.Get("clipInvisible", bool.Parse, false);
+            bool clipZeroWidth = clips.Get("clipZeroWidth", bool.Parse, false);
+            var clipColours = clips.Get("clipColours", s => s.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(CliPlotHelpers.ParseColour), null);
+            var washout = clips.IsSet("washoutColour");
+
+            bool forceAspect = clips.IsSet("forceAspect");
+
+            if (bgBox)
+            {
+                var bgBoxColour = clips.Get("bgbox", c => c == null ? OxyColors.White : CliPlotHelpers.ParseColour(c));
+                new ViewBackgroundAnnotation(plot, bgBoxColour); // bit of hackery
+            }
+
+            if (clipInvisible || clipColours != null || washout || clipZeroWidth)
+            {
+                IColourFilter preFilter = null;
+                if (washout)
+                {
+                    var washoutColour = clips.Get("washoutColour", s => s == null ? OxyColors.White : CliPlotHelpers.ParseColour(s), OxyColors.White);
+                    preFilter = new WashoutFilter(washoutColour);
+                }
+
+                plot.RenderingDecorator = rc => new ClippingRenderDecorator(rc, clipColours, clipInvisible, preFilter, clipZeroWidth);
+            }
+
+            PreProcessPlot(clips, plot);
+            PostProcessPlot postProcessor = model => PostProcessPlot(clips, model);
+
+            var plotReport = width > 0 && height > 0
+                ? PlotExporter.ExportPlot(outfilename, plot, width, height, forceAspect, postProcessor)
+                : PlotExporter.ExportPlot(outfilename, plot, size, forceAspect, postProcessor);
+
+            if (!clips.IsSet("quiet"))
+            {
+                PrintBasicInfo(console, plot);
+                console.WriteLine(plotReport.Summary);
+            }
+        }
+
+        public static void PrintBasicInfo(TextWriter console, PlotModel plot)
+        {
+            console.WriteLine("Axes: " + string.Join(", ", plot.Axes.Select(a => a.Key)));
+            console.WriteLine("Padding (Margins): " + plot.ActualPlotMargins);
+            console.WriteLine("InnerPadding: " + plot.Padding);
+        }
+
+        private string Dp => System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
+
+        private string StripZeroDps(string str)
+        {
+            if (str.Contains(Dp) && str.EndsWith("0"))
+            {
+                str = str.Substring(0, str.LastIndexOf('0'));
+            }
+
+            return str;
+        }
+
+        private string FormatDefault(double d)
+        {
+            return StripZeroDps(d.ToString("g6"));
+        }
+
+        public void PostProcessPlot(CliParams clips, PlotModel plot)
+        {
+            // axes formatting
+            foreach (var axis in plot.Axes)
+            {
+                // shoild not most of these be in PreProcessPlot?
+                var axisFormat = clips.Get("format:" + axis.Key, null);
+                var axisTitle = clips.Get("title:" + axis.Key, null);
+                var axisVisible = clips.Get("visible:" + axis.Key, bool.Parse, true);
+                double? intervalSize = clips.Get("intervalsize:" + axis.Key, s => (double?)double.Parse(s), null);
+
+                if (intervalSize.HasValue)
+                    axis.IntervalLength *= intervalSize.Value;
+
+                axis.IsAxisVisible = axisVisible;
+                if (!axisVisible)
+                    continue; // don't try
+
+                if (axisFormat == null || axisFormat == "auto")
+                {
+                    axis.GetTickValues(out var mjlvs, out var mjtvs, out var mntvs);
+                    if (mjtvs.Select(FormatDefault).Any(g6str => g6str.Contains("E")))
+                    {
+                        // give up and use the G6 (can't seem to create a variable precision exponential format string)
+                        axis.StringFormat = "g6";
+                    }
+                    else
+                    {
+                        int ups = mjtvs.Select(FormatDefault).Max(g6str => g6str.IndexOf(Dp) < 0 ? g6str.Length : g6str.IndexOf(Dp));
+                        int dps = mjtvs.Select(FormatDefault).Max(g6str => g6str.IndexOf(Dp) < 0 ? 0 : g6str.Length - g6str.IndexOf(Dp) - Dp.Length);
+
+                        // don't normalise ups for now
+                        ups = 1;
+
+                        string sf = new string('0', ups) + (dps > 0 ? "." + new string('0', dps) : "");
+                        axis.StringFormat = sf;
+                    }
+                }
+            }
+        }
+
+        public void PreProcessPlot(CliParams clips, PlotModel plot)
+        {
+            // remove yellow in default colours (too light), add SlateGrey on the end
+            plot.DefaultColors.Add(OxyColors.SlateGray);
+            plot.DefaultColors.RemoveAt(6);
+
+            if (clips.IsSet("allgrey"))
+            {
+                int greyCount = Math.Max(2, (int)(plot.Series.Count * 1.1) + 1); // we don't want anything too white
+                plot.DefaultColors = OxyPalettes.Gray(greyCount).Colors.Take(plot.Series.Count).ToArray();
+            }
+
+            if (clips.IsSet("defaultcolors"))
+            {
+                plot.DefaultColors = clips.Get("defaultcolors").Split(',', ';').Select(CliPlotHelpers.ParseColour).ToArray();
+            }
+
+            CursorInfoAnnotation cia = null;
+
+            // axes formatting
+            foreach (var axis in plot.Axes)
+            {
+                var axisFormat = clips.Get("format:" + axis.Key, null);
+                if (axisFormat != null && axisFormat != "auto" && axisFormat != "default")
+                {
+                    axis.StringFormat = axisFormat;
+                }
+
+                axis.IsAxisVisible = clips.Get("visible:" + axis.Key, bool.Parse, axis.IsAxisVisible);
+                axis.Title = clips.Get("title:" + axis.Key, axis.Title);
+
+                axis.MajorTickSize = GetMaybeQualified(clips, "MajorTickSize", axis.Key, double.Parse, axis.MajorTickSize);
+                axis.MajorGridlineStyle = GetMaybeQualified(clips, "MajorGridlinestyle", axis.Key, CliPlotHelpers.ParseLineStyle, axis.MajorGridlineStyle);
+                axis.MajorGridlineColor = GetMaybeQualified(clips, "MajorGridlinecolour", axis.Key, CliPlotHelpers.ParseColour, axis.MajorGridlineColor);
+                axis.MajorGridlineThickness = GetMaybeQualified(clips, "MajorGridlinethickness", axis.Key, double.Parse, axis.MajorGridlineThickness);
+
+                axis.MinorTickSize = GetMaybeQualified(clips, "MinorTickSize", axis.Key, double.Parse, axis.MinorTickSize);
+                axis.MinorGridlineStyle = GetMaybeQualified(clips, "MinorGridlinestyle", axis.Key, CliPlotHelpers.ParseLineStyle, axis.MinorGridlineStyle);
+                axis.MinorGridlineColor = GetMaybeQualified(clips, "MinorGridlinecolour", axis.Key, CliPlotHelpers.ParseColour, axis.MinorGridlineColor);
+                axis.MinorGridlineThickness = GetMaybeQualified(clips, "MinorGridlinethickness", axis.Key, double.Parse, axis.MinorGridlineThickness);
+
+                axis.AxislineStyle = GetMaybeQualified(clips, "axislinestyle", axis.Key, CliPlotHelpers.ParseLineStyle, axis.AxislineStyle);
+                axis.AxislineColor = GetMaybeQualified(clips, "axislinecolour", axis.Key, CliPlotHelpers.ParseColour, axis.AxislineColor);
+                axis.AxislineThickness = GetMaybeQualified(clips, "axislinethickness", axis.Key, double.Parse, axis.AxislineThickness);
+
+                axis.StartPosition = GetMaybeQualified(clips, "StartPosition", axis.Key, double.Parse, axis.StartPosition);
+                axis.EndPosition = GetMaybeQualified(clips, "EndPosition", axis.Key, double.Parse, axis.EndPosition);
+
+                axis.AxisTitleDistance = GetMaybeQualified(clips, "axistitledistance", axis.Key, double.Parse, axis.AxisTitleDistance);
+
+                axis.TicklineColor = GetMaybeQualified(clips, "tickcolour", axis.Key, CliPlotHelpers.ParseColour, axis.TicklineColor);
+                axis.TickStyle = GetMaybeQualified(clips, "tickstyle", axis.Key, CliPlotHelpers.ParseTickStyle, axis.TickStyle);
+                axis.TextColor = GetMaybeQualified(clips, "axislabelcolour", axis.Key, CliPlotHelpers.ParseColour, axis.TextColor);
+                axis.TitleColor = GetMaybeQualified(clips, "axistitlecolour", axis.Key, CliPlotHelpers.ParseColour, axis.TitleColor);
+
+                axis.Unit = clips.Get("unit:" + axis.Key, axis.Unit);
+
+                axis.Minimum = clips.Get("min:" + axis.Key, double.Parse, axis.Minimum);
+                axis.Maximum = clips.Get("max:" + axis.Key, double.Parse, axis.Maximum);
+                axis.MinimumPadding = GetMaybeQualified(clips, "minpad", axis.Key, double.Parse, axis.MinimumPadding);
+                axis.MaximumPadding = GetMaybeQualified(clips, "maxpad", axis.Key, double.Parse, axis.MaximumPadding);
+                axis.PositionTier = clips.Get("tier:" + axis.Key, int.Parse, axis.PositionTier);
+                axis.PositionAtZeroCrossing = GetMaybeQualified(clips, "zerocrossing", axis.Key, bool.Parse, axis.PositionAtZeroCrossing);
+
+                var axisPos = clips.Get("position:" + axis.Key, clips.Get("pos:" + axis.Key, null));
+                if (!string.IsNullOrEmpty(axisPos))
+                {
+                    axis.Position = CliPlotHelpers.ParseAxisPosition(axisPos);
+                }
+
+                var cursorColor = GetMaybeQualified(clips, "cursors", axis.Key, s => s == null ? OxyColors.Automatic : s == "" ? OxyColors.Transparent : CliPlotHelpers.ParseColour(s), OxyColors.Undefined);
+                if ((axis.IsHorizontal() || axis.IsVertical()) && (cursorColor.IsVisible() || cursorColor.IsAutomatic()))
+                {
+                    if (cia == null)
+                    {
+                        cia = new CursorInfoAnnotation();
+                        plot.Annotations.Add(cia);
+                    }
+
+                    if (cursorColor.IsAutomatic())
+                    {
+                        var s = plot.Series.FirstOrDefault(_s => _s is XYAxisSeries xys && xys.YAxisKey == axis.Key);
+                        if (s is LineSeries ls)
+                            cursorColor = ls.Color;
+                        else if (s is ScatterSeries ss)
+                            cursorColor = ss.MarkerFill;
+                        else if (s is AreaSeries @as)
+                            cursorColor = @as.Fill;
+                    }
+
+                    if (cursorColor.IsAutomatic())
+                    {
+                        cursorColor = OxyColors.Gray;
+                    }
+
+                    Axis otherAxis = null;// plot.Axes.FirstOrDefault(a => (a.IsHorizontal() || a.IsVertical()) && a.IsHorizontal() != axis.IsHorizontal());
+
+                    bool ry = axis.IsVertical() ^ (axis.Position == AxisPosition.Left || axis.Position == AxisPosition.Bottom);
+                    bool rx = axis.IsVertical() ^ (axis.Position == AxisPosition.Left || axis.Position == AxisPosition.Bottom);
+                    var low = new CursorAnnotation(axis.Key, otherAxis?.Key) { Color = cursorColor, ReverseX = rx, ReverseY = ry };
+                    var high = new CursorAnnotation(axis.Key, otherAxis?.Key) { Color = cursorColor, ReverseX = rx, ReverseY = ry };
+                    cia.AddPair(low, high);
+                    plot.Annotations.Add(low);
+                    plot.Annotations.Add(high);
+                }
+            }
+
+            // border
+            OxyThickness border = clips.Get("border", ParseOxyThickness, plot.PlotAreaBorderThickness);
+            plot.PlotAreaBorderThickness = border;
+
+            // size
+            double size = clips.Get("size", double.Parse, 1.0);
+
+            // padding (overrides min)
+            if (clips.IsSet("padding"))
+            {
+                plot.PlotMargins = clips.Get("padding", ParseOxyThickness);
+            }
+
+            // innerpadding
+            if (clips.IsSet("innerpadding"))
+            {
+                plot.Padding = clips.Get("innerpadding", ParseOxyThickness);
+            }
+
+            if (clips.IsSet("reverselegend"))
+            {
+                plot.LegendItemOrder = LegendItemOrder.Reverse;
+            }
+
+            // intervals
+            double intervalSize = clips.Get("intervalsize", double.Parse, size);
+            double horizontalIntervalSize = clips.Get("intervalsizeh", double.Parse, intervalSize);
+            double verticleIntervalSize = clips.Get("intervalsizev", double.Parse, intervalSize);
+            foreach (var axis in plot.Axes)
+            {
+                if (axis.IsHorizontal())
+                    axis.IntervalLength *= horizontalIntervalSize;
+                else if (axis.IsVertical())
+                    axis.IntervalLength *= verticleIntervalSize;
+                else
+                    axis.IntervalLength *= intervalSize;
+            }
+
+            // legend
+            var legend = clips.Get("legend", null);
+            plot.LegendTitle = clips.Get("legendTitle", plot.LegendTitle);
+            plot.LegendBackground = clips.Get("legendBackgroundColour", CliPlotHelpers.ParseColour, plot.LegendBackground);
+            plot.LegendBorder = clips.Get("legendBorderColour", CliPlotHelpers.ParseColour, plot.LegendBorder);
+            plot.LegendBorderThickness = clips.Get("legendBorderThickness", double.Parse, plot.LegendBorderThickness);
+
+            if (legend != null)
+            {
+                // this should be in a method
+                if (legend == "")
+                {
+                    plot.IsLegendVisible = false;
+                }
+
+                if (legend.Length > 0)
+                {
+                    var stewer = new StringChewer();
+
+                    var inside = legend.Contains('i') || legend.Contains('I');
+                    var outside = legend.Contains('o') || legend.Contains('O');
+                    var bottom = legend.Contains('b') || legend.Contains('B');
+                    var top = legend.Contains('t') || legend.Contains('T');
+                    var middle = legend.Contains('m') || legend.Contains('M');
+                    var left = legend.Contains('l') || legend.Contains('L');
+                    var right = legend.Contains('r') || legend.Contains('R');
+                    var horizontal = legend.Contains('h') || legend.Contains('H');
+                    var vertial = legend.Contains('v') || legend.Contains('V');
+                    var noPadding = legend.Contains('0');
+
+                    if (int.TryParse(legend, out int legendPosition))
+                    {
+                        plot.LegendPosition = (LegendPosition)legendPosition;
+                    }
+                    else
+                    {
+                        LegendPlacement pl = plot.LegendPlacement;
+                        LegendPosition lp = plot.LegendPosition;
+                        LegendOrientation lo = plot.LegendOrientation;
+
+                        if (inside)
+                        {
+                            pl = LegendPlacement.Inside;
+                        }
+                        else if (outside)
+                        {
+                            pl = LegendPlacement.Outside;
+                        }
+
+                        if (left)
+                        {
+                            lo = LegendOrientation.Vertical;
+
+                            if (bottom)
+                            {
+                                lp = LegendPosition.BottomLeft;
+                            }
+                            else if (middle)
+                            {
+                                lp = LegendPosition.LeftMiddle;
+                            }
+                            else
+                            {
+                                lp = LegendPosition.TopLeft;
+                            }
+                        }
+                        else if (right)
+                        {
+                            lo = LegendOrientation.Vertical;
+
+                            if (bottom)
+                            {
+                                lp = LegendPosition.BottomRight;
+                            }
+                            else if (middle)
+                            {
+                                lp = LegendPosition.RightMiddle;
+                            }
+                            else
+                            {
+                                lp = LegendPosition.TopRight;
+                            }
+                        }
+                        else
+                        {
+                            lo = LegendOrientation.Horizontal;
+
+                            if (top)
+                            {
+                                lp = LegendPosition.TopCenter;
+                            }
+                            else if (bottom)
+                            {
+                                lp = LegendPosition.BottomCenter;
+                            }
+                        }
+
+                        if (horizontal)
+                        {
+                            lo = LegendOrientation.Horizontal;
+                        }
+                        else if (vertial)
+                        {
+                            lo = LegendOrientation.Vertical;
+                        }
+
+                        plot.LegendPlacement = pl;
+                        plot.LegendPosition = lp;
+                        plot.LegendOrientation = lo;
+                    }
+                }
+            }
+
+            // font
+            string font = clips.Get("font", "Arial");
+            plot.DefaultFont = font;
+
+            plot.TitleFontSize = clips.Get("titleFontSize", double.Parse, plot.TitleFontSize);
+            plot.TitleFontWeight = clips.Get("titleFontWeight", double.Parse, plot.TitleFontWeight);
+
+            // figure label
+            var figureLabel = clips.Get("fig", null);
+            var figureLabelColour = clips.Get("figcolour", CliPlotHelpers.ParseColour, plot.TextColor);
+            if (figureLabel != null)
+                new FigureLabelAnnotation(plot, figureLabel, figureLabelColour);
+
+            // water mark
+            var waterMark = clips.Get("watermark", null);
+            var waterMarkColour = clips.Get("watermarkcolour", CliPlotHelpers.ParseColour, OxyColors.LightGray);
+            if (waterMark != null)
+                new WaterMarkAnnotation(plot, waterMark, waterMarkColour);
+
+            // misc annotations
+            var annotations = clips.Get("annotations", s => s.Split(';'), null);
+            if (annotations != null)
+            {
+                foreach (var annStr in annotations)
+                {
+                    foreach (var a in ParseAnnotation(annStr, OxyColors.LightGray, OxyColors.DimGray, OxyColors.Automatic))
+                    {
+                        plot.Annotations.Add(a);
+                    }
+                }
+            }
+
+            // side annotations
+            var sides = new[] { AxisPosition.Bottom, AxisPosition.Top, AxisPosition.Left, AxisPosition.Right };
+            foreach (var side in sides)
+            {
+                var sideText = clips.Get("side:" + side, null);
+                if (!string.IsNullOrWhiteSpace(sideText))
+                    plot.Annotations.Add(new SideAnnotation(side, sideText));
+            }
+
+            // for when the target doesn't change
+            double repeatLines = clips.Get("repeatlines", double.Parse, double.NaN);
+            if (!double.IsNaN(repeatLines))
+            {
+                var rl = new RepeatingLineAnnotation(plot, OxyColors.Gray, repeatLines);
+            }
+
+            // series
+            foreach (var s in plot.Series)
+            {
+                var key = s.Title;
+                bool reduceGeometry = GetMaybeQualified(clips, "reduceGeometry", key, bool.Parse, false);
+
+                if (reduceGeometry)
+                {
+                    switch (s)
+                    {
+                        case OxyPlot.Series.AreaSeries a:
+                            {
+                                var ps = CliPlotHelpers.Reduce(a.Points, 1E-10).ToArray();
+                                a.Points.Clear();
+                                a.Points.AddRange(ps);
+                                ps = CliPlotHelpers.Reduce(a.Points2, 1E-10).ToArray();
+                                a.Points2.Clear();
+                                a.Points2.AddRange(ps);
+                                break;
+                            }
+                        case OxyPlot.Series.LineSeries l:
+                            {
+                                var ps = CliPlotHelpers.Reduce(l.Points, 1E-10).ToArray();
+                                l.Points.Clear();
+                                l.Points.AddRange(ps);
+                                break;
+                            }
+                    }
+                }
+            }
+        }
+
+        private static IEnumerable<Annotation> ParseAnnotation(string annotationString, OxyColor defaultStrokeColour, OxyColor defaultFillColour, OxyColor defaultTextColour)
+        {
+            var data = StringHelpers.Split(annotationString).ToArray();
+            if (data.Length < 1)
+                throw new Exception("Invalid annotation string: " + annotationString);
+
+            var layer = AnnotationLayer.BelowSeries;
+
+            // common args
+            if (data[0].StartsWith("!"))
+            {
+                var common = data[0];
+                data = data.Skip(1).ToArray();
+
+                if (common.Contains("a"))
+                {
+                    layer = AnnotationLayer.AboveSeries;
+                }
+            }
+
+            Annotation applyCommon(Annotation a)
+            {
+                a.Layer = layer;
+                return a;
+            }
+
+            var cmd = data[0];
+
+            T required<T>(int idx, Func<string, T> parser)
+            {
+                if (data.Length > idx + 1)
+                    return parser(data[idx + 1]);
+                else
+                    throw new Exception("Invalid annotation string: " + annotationString);
+            }
+
+            T optional<T>(int idx, Func<string, T> parser, T @default)
+            {
+                if (data.Length > idx + 1)
+                    return parser(data[idx + 1]);
+                else
+                    return @default;
+            }
+
+            // TODO: these should be in a dictionary
+            if (cmd.SoftEquals("lx"))
+            { // lx x text colour textColor
+                var x = required(0, double.Parse);
+                var text = optional(1, s => s, "");
+                var color = optional(2, CliPlotHelpers.ParseColour, defaultStrokeColour);
+                var textColor = optional(3, CliPlotHelpers.ParseColour, defaultTextColour);
+                yield return applyCommon(new LineAnnotation() { X = x, Text = text, Color = color, Type = LineAnnotationType.Vertical, TextColor = textColor });
+            }
+            else if (cmd.SoftEquals("ly"))
+            { // ly y text colour textColor
+                var y = required(0, double.Parse);
+                var text = optional(1, s => s, "");
+                var color = optional(2, CliPlotHelpers.ParseColour, defaultStrokeColour);
+                var textColor = optional(3, CliPlotHelpers.ParseColour, defaultTextColour);
+                yield return applyCommon(new LineAnnotation() { Y = y, Text = text, Color = color, Type = LineAnnotationType.Horizontal, TextColor = textColor });
+            }
+            else if (cmd.SoftEquals("ax"))
+            { // ax x0 x1 text colour textColor
+                var x0 = required(0, double.Parse);
+                var x1 = required(1, double.Parse);
+                var text = optional(2, s => s, "");
+                var color = optional(3, CliPlotHelpers.ParseColour, defaultFillColour);
+                var textColor = optional(4, CliPlotHelpers.ParseColour, defaultTextColour);
+                yield return applyCommon(new RectangleAnnotation() { MinimumX = x0, MaximumX = x1, MinimumY = double.NaN, MaximumY = double.NaN, Text = text, Fill = color, TextColor = textColor });
+            }
+            else if (cmd.SoftEquals("ay"))
+            { // ay y0 x1 text colour textColor
+                var y0 = required(0, double.Parse);
+                var y1 = required(1, double.Parse);
+                var text = optional(2, s => s, "");
+                var color = optional(3, CliPlotHelpers.ParseColour, defaultFillColour);
+                var textColor = optional(4, CliPlotHelpers.ParseColour, defaultTextColour);
+                yield return applyCommon(new RectangleAnnotation() { MinimumY = y0, MaximumY = y1, MinimumX = double.NaN, MaximumX = double.NaN, Text = text, Fill = color, TextColor = textColor });
+            }
+        }
+
+        public static string GetMaybeQualified(CliParams clips, string prefix, string qualifier, string @default)
+        {
+            return clips.GetOrCreate(prefix + ":" + qualifier, () => clips.Get(prefix, @default));
+        }
+
+        public static T GetMaybeQualified<T>(CliParams clips, string prefix, string qualifier, Func<string, T> parser, T @default)
+        {
+            return clips.GetOrCreate(prefix + ":" + qualifier, parser, () => clips.Get(prefix, parser, @default));
+        }
+
+        public static OxyThickness ParseOxyThickness(string str)
+        {
+            double[] arr = str.Split(new char[] { ',', ';' }).Select(s => s == "" ? double.NaN : double.Parse(s)).ToArray();
+
+            if (arr.Length == 1)
+            {
+                return new OxyThickness(arr[0], arr[0], arr[0], arr[0]);
+            }
+            else if (arr.Length == 2)
+            {
+                return new OxyThickness(arr[0], arr[1], arr[0], arr[1]);
+            }
+            else if (arr.Length == 4)
+            {
+                return new OxyThickness(arr[0], arr[1], arr[2], arr[3]);
+            }
+
+            throw new ArgumentException(nameof(str), "Not a valid OxyThickness");
+        }
+    }
+
+    public class StringChewer
+    {
+        private Dictionary<char, Action> Actions { get; } = new Dictionary<char, Action>();
+
+        public StringChewer()
+        {
+        }
+
+        public void Add(Action action, params char[] chars)
+        {
+            foreach (char c in chars)
+                Actions.Add(c, action);
+        }
+
+        public string Chew(string str)
+        {
+            while (str.Length > 0)
+            {
+                char c = str[0];
+                if (Actions.TryGetValue(c, out var action))
+                {
+                    action();
+                    str = str.Substring(1);
+                    continue;
+                }
+
+                break;
+            }
+
+            return str;
+        }
+    }
+
+    public interface ICliTraceeProvider<T> where T : IIndividual<T>
+    {
+        string Prefix { get; }
+        void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<T> traceInfo, bool epochsx, double offset);
+    }
+
+    public class HuskyProvider : ICliTraceeProvider<DenseIndividual>
+    {
+        public HuskyProvider(string prefix, string defaultTitle, OxyColor c0, OxyColor c1, double strokeThickness)
+        {
+            Prefix = prefix;
+            DefaultTitle = defaultTitle;
+
+            C0 = c0;
+            C1 = c1;
+            StrokeThickness = strokeThickness;
+        }
+
+        public string Prefix { get; }
+        public string DefaultTitle { get; }
+        public OxyColor C0 { get; }
+        public OxyColor C1 { get; }
+        public double StrokeThickness { get; }
+
+        public static Func<int, Func<WholeSample<DenseIndividual>, double>> ModulesHuskySampler(IReadOnlyList<IReadOnlyList<int>> moduleAssignments)
+        {
+            return i => ModuleHuskySampler(moduleAssignments[i]);
+        }
+
+        public static Func<WholeSample<DenseIndividual>, double> ModuleHuskySampler(IReadOnlyList<int> mts)
+        {
+            return ws =>
+            {
+                return ws.Judgements.Average(j => Analysis.ComputeModuleHuskyness(j.Individual.Genome.TransMat, mts));
+            };
+        }
+
+        public void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<DenseIndividual> traceInfo, bool epochsx, double offset)
+        {
+            var modules = clips.Get("modules:" + name, Modular.MultiModulesStringParser.Instance.Parse, CliPlotHelpers.GuessModules(console, clips, traceInfo.TraceWholeSamples[0].Target.Size));
+            var lineCount = modules.ModuleCount;
+
+            Tracee1DProvider<DenseIndividual>.PlotStuff(console, model, name, clips, traceInfo, epochsx, offset, lineCount, ModulesHuskySampler(modules.ModuleAssignments), DefaultTitle, C0, C1, StrokeThickness);
+        }
+    }
+
+    public class ModuleIndependenceProvider : ICliTraceeProvider<DenseIndividual>
+    {
+        public ModuleIndependenceProvider(string prefix, string defaultTitle, OxyColor c0, OxyColor c1, double strokeThickness)
+        {
+            Prefix = prefix;
+            DefaultTitle = defaultTitle;
+
+            C0 = c0;
+            C1 = c1;
+            StrokeThickness = strokeThickness;
+        }
+
+        public string Prefix { get; }
+        public string DefaultTitle { get; }
+        public OxyColor C0 { get; }
+        public OxyColor C1 { get; }
+        public double StrokeThickness { get; }
+
+        public static Func<int, Func<WholeSample<DenseIndividual>, double>> ModulesIndependenceSampler(IReadOnlyList<IReadOnlyList<int>> moduleAssignments)
+        {
+            return i => ModuleModuleIndependenceSampler(moduleAssignments[i]);
+        }
+
+        public static Func<WholeSample<DenseIndividual>, double> ModuleModuleIndependenceSampler(IReadOnlyList<int> mts)
+        {
+            return ws =>
+            {
+                return ws.Judgements.Average(j => Analysis.ComputeModuleIndependence(j.Individual.Genome.TransMat, mts));
+            };
+        }
+
+        public void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<DenseIndividual> traceInfo, bool epochsx, double offset)
+        {
+            var modules = clips.Get("modules:" + name, Modular.MultiModulesStringParser.Instance.Parse, CliPlotHelpers.GuessModules(console, clips, traceInfo.TraceWholeSamples[0].Target.Size));
+            var lineCount = modules.ModuleCount;
+
+            Tracee1DProvider<DenseIndividual>.PlotStuff(console, model, name, clips, traceInfo, epochsx, offset, lineCount, ModulesIndependenceSampler(modules.ModuleAssignments), DefaultTitle, C0, C1, StrokeThickness);
+        }
+    }
+
+    public class DeltaBProvider : ICliTraceeProvider<DenseIndividual>
+    {
+        public DeltaBProvider(string prefix, string defaultTitle, OxyColor c00, OxyColor c10, OxyColor c01, OxyColor c11, double strokeThickness)
+        {
+            Prefix = prefix;
+            DefaultTitle = defaultTitle;
+
+            C00 = c00;
+            C10 = c10;
+            C01 = c01;
+            C11 = c11;
+            StrokeThickness = strokeThickness;
+        }
+
+        public string Prefix { get; }
+        public string DefaultTitle { get; }
+
+        public OxyColor C00 { get; }
+        public OxyColor C10 { get; }
+        public OxyColor C01 { get; }
+        public OxyColor C11 { get; }
+        public double StrokeThickness { get; }
+
+        public void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<DenseIndividual> traceInfo, bool epochsx, double offset)
+        {
+            var config = CliPlotHelpers.LoadDenseExperimentConfig(clips.Get("expfile"));
+
+            var delta = clips.Get("delta", double.Parse);
+            var signed = clips.Get("signed", bool.Parse);
+
+            var strokeThickness = clips.Get("strokeThickness", double.Parse, StrokeThickness);
+            var diff = clips.Get("diff:" + name, bool.Parse, clips.IsSet("diff"));
+            var title = clips.Get("title:" + name, DefaultTitle);
+
+            int dim1 = traceInfo.TraceWholeSamples[0].Judgements[0].Individual.Genome.Size;
+            int dim2 = dim1;
+            int[] lines = Enumerable.Range(0, dim1 * dim2).ToArray();
+
+            if (clips.IsSet("keep:" + name))
+            {
+                IEnumerable<int> keep = clips.Get("keep:" + name, CliPlotHelpers.ParseIntList);
+                lines = CliPlotHelpers.Keep(lines, keep, -1);
+            }
+
+            List<OxyColor> colours = new List<OxyColor>();
+            List<Func<IEnumerable<WholeSample<DenseIndividual>>, double>> samplerMetrics = new List<Func<IEnumerable<WholeSample<DenseIndividual>>, double>>();
+
+            OxyColor[] colors2d = TrajectoryPlotting.Colours2D(dim1, C00, C10, C01, C11);
+
+            var context = new ModelExecutionContext(new CustomMersenneTwister(0));
+            var rand = context.DisableRandom();
+
+            Func<WholeSample<DenseIndividual>, double> sampler(int i, int j)
+            {
+                var meas = new MatrixEntryAddress[] { new MatrixEntryAddress(i, j) };
+                return (WholeSample<DenseIndividual> ws) =>
+                {
+                    return Analysis.ComputeSimpleDelta<double>(context, ws.Target, config.JudgementRules, config.DevelopmentRules, ws.Judgements.First().Individual, meas, delta, signed, (f2, f1) => (f2.CombinedFitness - f1.CombinedFitness) / delta);
+                };
+            }
+
+            context.EnableRandom(rand);
+
+            // lines
+            for (int i = 0; i < dim1; i++)
+            {
+                for (int j = 0; j < dim2; j++)
+                {
+                    if (lines[i * dim1 + j] < 0) // not-kept
+                        continue;
+
+                    var samplerMetric = sampler(i, j);
+
+                    samplerMetrics.Add(many => samplerMetric(many.First()));
+                    colours.Add(colors2d[i * dim1 + j]);
+                }
+            }
+
+            Tracee1DProvider<DenseIndividual>.PlotStuffs(model, name, title, clips, traceInfo, epochsx, offset, samplerMetrics, colours, strokeThickness);
+        }
+    }
+
+    public class Tracee1DProvider<T> : ICliTraceeProvider<T> where T : IIndividual<T>
+    {
+        public static int DenseSize(TraceInfo<DenseIndividual> traceInfo) => traceInfo.TraceWholeSamples.First().Judgements[0].Individual.Genome.Size;
+
+        public Tracee1DProvider(string prefix, string defaultTitle, Func<int, Func<WholeSample<T>, double>> sampler1D, Func<TraceInfo<T>, int> defaultCount, OxyColor c0, OxyColor c1, double strokeThickness)
+        {
+            Prefix = prefix;
+            DefaultTitle = defaultTitle;
+            Sampler1D = sampler1D;
+            DefaultCount = defaultCount;
+
+            C0 = c0;
+            C1 = c1;
+            StrokeThickness = strokeThickness;
+        }
+
+        public string Prefix { get; }
+        public string DefaultTitle { get; }
+        public Func<int, Func<WholeSample<T>, double>> Sampler1D { get; }
+        public Func<TraceInfo<T>, int> DefaultCount { get; }
+
+        public OxyColor C0 { get; }
+        public OxyColor C1 { get; }
+        public double StrokeThickness { get; }
+
+        public void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<T> traceInfo, bool epochsx, double offset)
+        {
+            int lineCount = DefaultCount(traceInfo);
+            PlotStuff(console, model, name, clips, traceInfo, epochsx, offset, lineCount, Sampler1D, DefaultTitle, C0, C1, StrokeThickness);
+        }
+
+        public static void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<T> traceInfo, bool epochsx, double offset, int lineCount, Func<int, Func<WholeSample<T>, double>> sampler1D, string defaultTitle, OxyColor c0, OxyColor c1, double strokeThickness)
+        {
+            var diff = clips.Get("diff:" + name, bool.Parse, clips.IsSet("diff"));
+            var title = clips.Get("title:" + name, defaultTitle);
+
+            int[] lines = Enumerable.Range(0, lineCount).ToArray();
+
+            if (clips.IsSet("keep:" + name))
+            {
+                IEnumerable<int> keep = clips.Get("keep:" + name, CliPlotHelpers.ParseIntList);
+                lines = CliPlotHelpers.Keep(lines, keep, -1);
+            }
+
+            c0 = clips.Get("c0:" + name, CliPlotHelpers.ParseColour, c0);
+            c1 = clips.Get("c1:" + name, CliPlotHelpers.ParseColour, c1);
+
+            List<OxyColor> colours = new List<OxyColor>();
+            List<Func<IEnumerable<WholeSample<T>>, double>> samplerMetrics = new List<Func<IEnumerable<WholeSample<T>>, double>>();
+
+            // lines
+            for (int i = 0; i < lines.Length; i++)
+            {
+                if (lines[i] < 0) // not-kept
+                    continue;
+
+                OxyColor colour;
+
+                if (lines.Length > 1 && c0 != null && c1 != null)
+                {
+                    var z = (double)i / (lines.Length - 1);
+                    colour = OxyColor.Interpolate(c0, c1, z);
+                }
+                else
+                {
+                    colour = c0;
+                }
+
+                var sampler = sampler1D(i);
+                var samplerMetric = TracePlotting.AverageMetric(traceInfo, diff, sampler);
+
+                colours.Add(colour);
+                samplerMetrics.Add(samplerMetric);
+            }
+
+            PlotStuffs(model, name, title, clips, traceInfo, epochsx, offset, samplerMetrics, colours, strokeThickness);
+        }
+
+        public static IReadOnlyList<OxyPlot.Series.Series> PlotStuffs(PlotModel model, string name, string title, CliParams clips, TraceInfo<T> traceInfo, bool epochsx, double offset, IReadOnlyList<Func<IEnumerable<WholeSample<T>>, double>> samplerMetrics, IReadOnlyList<OxyColor> colours, double strokeThickness)
+        {
+            strokeThickness = CliPlot.GetMaybeQualified(clips, "strokeThickness", name, double.Parse, strokeThickness);
+            var yAxisKey = clips.Get("yaxiskey:" + name, name);
+            var xAxisKey = clips.Get("xaxiskey:" + name, clips.Get("xaxiskey", "generations"));
+            var resolution = CliPlot.GetMaybeQualified(clips, "resolution", name, Misc.ParseInt, 1);
+            var onRight = clips.Get("onRight:" + name, bool.Parse, clips.IsSet("onRight"));
+            var noAxis = clips.IsSet("noAxis:" + name) || model.Axes.Any(a => a.Key == yAxisKey);
+            var diff = clips.Get("diff:" + name, bool.Parse, clips.IsSet("diff"));
+
+            var colour = clips.Get("colour:" + name, s => s == null ? null : (OxyColor?)CliPlotHelpers.ParseColour(s), null);
+            var markerColour = clips.Get("markerColour:" + name, s => s == null ? OxyColors.Transparent : CliPlotHelpers.ParseColour(s), OxyColors.Automatic);
+            var plotType = clips.Get("plotType:" + name, CliPlotHelpers.ParseGenerousTrajectoryPlotType, clips.Get("plottype", CliPlotHelpers.ParseGenerousTrajectoryPlotType, TrajectoryPlotType.Line));
+            var markerType = clips.Get("markerType:" + name, CliPlotHelpers.ParseMarkerType, clips.Get("markerType", CliPlotHelpers.ParseMarkerType, plotType == TrajectoryPlotType.Line ? MarkerType.None : MarkerType.Diamond));
+            var lineStyle = clips.Get("lineStyle:" + name, CliPlotHelpers.ParseLineStyle, LineStyle.Automatic);
+            var renderInLegend = clips.Get("legend:" + name, bool.Parse, plotType != TrajectoryPlotType.Rectangles);
+            var injectBreaks = CliPlot.GetMaybeQualified(clips, "injectbreaks", name, bool.Parse, false);
+            var brokenLineStyle = CliPlot.GetMaybeQualified(clips, "brokenLineStyle", name, CliPlotHelpers.ParseLineStyle, LineStyle.Dot);
+            var seriesName = clips.Get("seriesName:" + name, title);
+
+            if (plotType == TrajectoryPlotType.Rectangles)
+            { // TODO: can we push this into PlotTraceLine?
+                var colorAxisKey = CliPlot.GetMaybeQualified(clips, "coloraxiskey", name, "color");
+                var noColorAxis = clips.IsSet("noColorAxis:" + name) || model.Axes.Any(a => a.Key == yAxisKey);
+
+                // axes
+                if (!noAxis)
+                {
+                    var yAxis = new OxyPlot.Axes.LinearAxis() { Key = yAxisKey, Title = "Gene", Position = onRight ? OxyPlot.Axes.AxisPosition.Right : OxyPlot.Axes.AxisPosition.Left, StartPosition = 1, EndPosition = 0 };
+                    model.Axes.Add(yAxis);
+                }
+
+                if (!noColorAxis)
+                {
+                    var colorAxis = new OxyPlot.Axes.LinearColorAxis() { Key = colorAxisKey, Title = (diff ? "Change in " : "") + title, Position = OxyPlot.Axes.AxisPosition.Right };
+                    colorAxis.Palette = OxyPalettes.Gray(100);
+                    model.Axes.Add(colorAxis);
+                }
+
+                var s = new OxyPlot.Series.RectangleSeries();
+                s.Title = title;
+                s.ColorAxisKey = colorAxisKey;
+                s.XAxisKey = xAxisKey;
+                s.YAxisKey = yAxisKey;
+                s.RenderInLegend = renderInLegend;
+
+                for (int i = 0; i < samplerMetrics.Count; i++)
+                {
+                    var samplerMetric = samplerMetrics[i];
+                    var samples = TracePlotting.Sample(traceInfo, resolution, samplerMetric, epochsx, offset);
+                    DataPoint last = DataPoint.Undefined;
+                    foreach (var sample in samples)
+                    {
+                        if (last.IsDefined() && sample.IsDefined())
+                        {
+                            var rect = new RectangleItem(last.X, sample.X, i, i + 1, sample.Y);
+                            s.Items.Add(rect);
+                        }
+
+                        last = sample;
+                    }
+                }
+
+                model.Series.Add(s);
+                return new OxyPlot.Series.Series[] { s };
+            }
+            else
+            {
+                // axes
+                if (!noAxis)
+                {
+                    var yAxis = new OxyPlot.Axes.LinearAxis() { Key = yAxisKey, Title = (diff ? "Change in " : "") + title, Position = onRight ? OxyPlot.Axes.AxisPosition.Right : OxyPlot.Axes.AxisPosition.Left };
+                    model.Axes.Add(yAxis);
+                }
+
+                // lines
+                List<OxyPlot.Series.Series> series = new List<OxyPlot.Series.Series>();
+                for (int i = 0; i < samplerMetrics.Count; i++)
+                {
+                    bool first = i == 0;
+
+                    var lineColour = colour.HasValue ? colour.Value : colours[i];
+                    var samplerMetric = samplerMetrics[i];
+                    var s = TracePlotting.PlotTraceLine(model, traceInfo, xAxisKey, yAxisKey, title, resolution, epochsx, samplerMetric, lineColour, strokeThickness, offset, plotType, markerType, markerColour, lineStyle, injectBreaks, brokenLineStyle);
+                    s.RenderInLegend = first && renderInLegend;
+                    series.Add(s);
+                }
+
+                return series;
+            }
+        }
+    }
+
+    public class Tracee2DProvider<T> : ICliTraceeProvider<T> where T : IIndividual<T>
+    {
+        public static int DenseSize(TraceInfo<DenseIndividual> traceInfo) => traceInfo.TraceWholeSamples.First().Judgements[0].Individual.Genome.Size;
+
+        public Tracee2DProvider(string prefix, string defaultTitle, Func<int, int, Func<WholeSample<T>, double>> sampler2D, Func<TraceInfo<T>, int> defaultDim1, Func<TraceInfo<T>, int> defaultDim2, OxyColor c00, OxyColor c10, OxyColor c01, OxyColor c11, double strokeThickness)
+        {
+            Prefix = prefix;
+            DefaultTitle = defaultTitle;
+            Sampler2D = sampler2D;
+            DefaultDim1 = defaultDim1;
+            DefaultDim2 = defaultDim2;
+
+            C00 = c00;
+            C10 = c10;
+            C01 = c01;
+            C11 = c11;
+            StrokeThickness = strokeThickness;
+        }
+
+        public string Prefix { get; }
+        public string DefaultTitle { get; }
+        public Func<int, int, Func<WholeSample<T>, double>> Sampler2D { get; }
+        public Func<TraceInfo<T>, int> DefaultDim1 { get; }
+        public Func<TraceInfo<T>, int> DefaultDim2 { get; }
+
+        public OxyColor C00 { get; }
+        public OxyColor C10 { get; }
+        public OxyColor C01 { get; }
+        public OxyColor C11 { get; }
+        public double StrokeThickness { get; }
+
+        public void PlotStuff(TextWriter console, PlotModel model, string name, CliParams clips, TraceInfo<T> traceInfo, bool epochsx, double offset)
+        {
+            var strokeThickness = clips.Get("strokeThickness", double.Parse, StrokeThickness);
+            var diff = clips.Get("diff:" + name, bool.Parse, clips.IsSet("diff"));
+            var title = clips.Get("title:" + name, DefaultTitle);
+
+            int dim1 = DefaultDim1(traceInfo);
+            int dim2 = DefaultDim1(traceInfo);
+            int[] lines = Enumerable.Range(0, dim1 * dim2).ToArray();
+
+            if (clips.IsSet("keep:" + name))
+            {
+                IEnumerable<int> keep = clips.Get("keep:" + name, CliPlotHelpers.ParseIntList);
+                lines = CliPlotHelpers.Keep(lines, keep, -1);
+            }
+
+            List<OxyColor> colours = new List<OxyColor>();
+            List<Func<IEnumerable<WholeSample<T>>, double>> samplerMetrics = new List<Func<IEnumerable<WholeSample<T>>, double>>();
+
+            OxyColor[] colors2d = TrajectoryPlotting.Colours2D(dim1, C00, C10, C01, C11);
+
+            // lines
+            for (int i = 0; i < dim1; i++)
+            {
+                for (int j = 0; j < dim2; j++)
+                {
+                    if (lines[i * dim1 + j] < 0) // not-kept
+                        continue;
+
+                    var sampler = Sampler2D(i, j);
+                    var samplerMetric = TracePlotting.AverageMetric(traceInfo, diff, sampler);
+
+                    samplerMetrics.Add(samplerMetric);
+                    colours.Add(colors2d[i * dim1 + j]);
+                }
+            }
+
+            Tracee1DProvider<T>.PlotStuffs(model, name, title, clips, traceInfo, epochsx, offset, samplerMetrics, colours, strokeThickness);
+        }
+    }
+
+    public class CliTraceePlotter : ICliPlotter
+    {
+        private class PlotOptions
+        {
+            public static readonly PlotOptions DefaultOff = new PlotOptions(false, false);
+            public static readonly PlotOptions DefaultOn = new PlotOptions(true, false);
+
+            public PlotOptions(bool enabled, bool leftAxis, LineStyle lineStyle = LineStyle.Solid, TrajectoryPlotType plotType = TrajectoryPlotType.Line, MarkerType markerType = MarkerType.None)
+            {
+                Enabled = enabled;
+                LeftAxis = leftAxis;
+                LineStyle = lineStyle;
+                PlotType = plotType;
+                MarkerType = markerType;
+            }
+
+            public bool Enabled { get; }
+            public bool LeftAxis { get; }
+            public LineStyle LineStyle { get; }
+            public TrajectoryPlotType PlotType { get; }
+            public MarkerType MarkerType { get; }
+
+            public static readonly Func<string, PlotOptions> DefaultParser = Parser(DefaultOn);
+
+            public static Func<string, PlotOptions> Parser(PlotOptions template)
+            {
+                return str =>
+                {
+                    if (str == null)
+                        return template;
+
+                    if (str.Length == 0)
+                        return DefaultOff; // off
+
+                    bool leftAxis = str.Contains('l') ? true : str.Contains('r') ? false : template.LeftAxis;
+                    LineStyle lineStyle = str.Contains('d') ? LineStyle.Dash : LineStyle.Solid;
+                    TrajectoryPlotType plotType = str.Contains('s') ? TrajectoryPlotType.Scatter : TrajectoryPlotType.Line;
+                    MarkerType markerType =
+                        str.Contains("*") ? MarkerType.Star :
+                        str.Contains("x") ? MarkerType.Cross :
+                        str.Contains("o") ? MarkerType.Circle :
+                        str.Contains("v") ? MarkerType.Diamond :
+                        plotType == TrajectoryPlotType.Line ? MarkerType.None :
+                        MarkerType.Diamond;
+
+                    return new PlotOptions(true, leftAxis, lineStyle, plotType, markerType);
+                };
+            }
+        }
+
+        public string Prefix => "tracee";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var traceInfo = TraceInfo<DenseIndividual>.Load(filename);
+
+            if (clips.IsSet("gswitch"))
+            {
+                Func<WholeSample<DenseIndividual>, WholeSample<DenseIndividual>, bool> cutter = null;
+
+                // cut-period in generations
+                int cutPeriod = clips.Get("cutPeriod", Misc.ParseInt, -1);
+                long cutStart = clips.Get("start", Misc.ParseLong, traceInfo.TraceWholeSamples.First().Generations);
+                long cutEnd = clips.Get("end", Misc.ParseLong, traceInfo.TraceWholeSamples.Last().Generations);
+
+                if (clips.IsSet("cutPeriod"))
+                {
+                    cutter = (a, b) => (a.Generations - cutStart) / cutPeriod != (b.Generations - cutStart) / cutPeriod;
+                }
+                else
+                {
+                    // cut by target if possible
+                    var allTargets = traceInfo.TraceWholeSamples.Select(ws => ws.Target).Distinct().ToArray();
+                    if (allTargets.Length > 1)
+                    {
+                        cutter = (a, b) => a.Target != b.Target;
+                    }
+                    else
+                    {
+                        throw new Exception("Cannot cut by target; only one target used: qualify cutperiod");
+                    }
+                }
+
+                var extract = traceInfo.ExtractGenerations(cutStart, cutEnd);
+                var cuts = Misc.CutOn(extract.TraceWholeSamples, cutter).Select(c => c.ToArray()).ToArray(); // each entry is a single 'switch'
+
+                if (extract.TraceWholeSamples[0].Judgements.Count != 1)
+                    console.WriteLine("gswitch on population without exactly one individual is meaningless");
+
+                int size = extract.TraceWholeSamples[0].Judgements[0].Individual.Genome.InitialState.Count;
+                List<int>[] delays = Misc.Create(size, i => new List<int>());
+
+                foreach (var cut in cuts)
+                {
+                    // first entry is always 'from the previous target'
+                    var ws0 = cut[0];
+                    var g0 = ws0.Judgements[0].Individual.Genome.InitialState;
+                    var generations0 = ws0.Generations;
+
+                    for (int i = 0; i < size; i++)
+                    {
+                        var change = cut.FirstOrDefault(ws => ws.Judgements[0].Individual.Genome.InitialState[i] != g0[i]);
+
+                        if (change == null)
+                            continue;
+
+                        var delay = (int)(change.Generations - generations0);
+                        delays[i].Add(delay);
+                    }
+
+                }
+
+                bool needlesslyWordyTraitLabels = clips.Get("needlesslyWordyTraitLabels", bool.Parse, false);
+                var plot = new OxyPlot.PlotModel() { Title = title };
+
+                var traitAxis = new OxyPlot.Axes.CategoryAxis() { Title = "Traits", Key = "traits" };
+                var countAxis = new OxyPlot.Axes.LinearAxis() { Title = "Count", Key = "count", Position = OxyPlot.Axes.AxisPosition.Left, MinimumPadding = 0 };
+                var delayAxis = new OxyPlot.Axes.LinearAxis() { Title = "Delay", Key = "delay", Position = OxyPlot.Axes.AxisPosition.Right, MinimumPadding = 0 };
+
+                plot.Axes.Add(traitAxis);
+                plot.Axes.Add(countAxis);
+                plot.Axes.Add(delayAxis);
+
+                var countSeries = new OxyPlot.Series.ColumnSeries() { Title = "Count", XAxisKey = traitAxis.Key, YAxisKey = countAxis.Key };
+                var delaySeries = new OxyPlot.Series.ErrorColumnSeries() { Title = "Mean Delay", XAxisKey = traitAxis.Key, YAxisKey = delayAxis.Key };
+                for (int i = 0; i < size; i++)
+                {
+                    traitAxis.Labels.Add(needlesslyWordyTraitLabels ? $"Trait {i}" : "" + i);
+                    countSeries.Items.Add(new OxyPlot.Series.ColumnItem(delays[i].Count, i));
+                    if (delays[i].Count > 0)
+                    {
+                        var stats = MathNet.Numerics.Distributions.Normal.Estimate(delays[i].Select(d => (double)d));
+                        var confidenceWidth = 1.96 /* gaussian, 95% */ * stats.StdDev / Math.Sqrt(delays[i].Count);
+                        delaySeries.Items.Add(new OxyPlot.Series.ErrorColumnItem(stats.Mean, confidenceWidth, i));
+                    }
+                }
+                plot.Series.Add(countSeries);
+                plot.Series.Add(delaySeries);
+
+                plot.LegendPlacement = LegendPlacement.Outside;
+                plot.LegendOrientation = LegendOrientation.Horizontal;
+                plot.LegendPosition = LegendPosition.TopCenter;
+                plot.IsLegendVisible = true;
+
+                return plot;
+            }
+
+            if (clips.IsSet("generation"))
+            {
+                // plot last genome in epoch
+                int generation = clips.Get("generation", Misc.ParseInt);
+
+                var samples = traceInfo.TraceWholeSamples.Where(ws => ws.Generations == generation).ToArray();
+
+                if (samples.Length == 0)
+                    throw new Exception("No samples for generation: " + generation);
+
+                int subSampleIndex = clips.Get("subsampleindex", int.Parse, samples.Length - 1); // default to last
+
+                var individual = samples[subSampleIndex].Judgements.First().Individual;
+
+                var thing = clips.Get("thing", null);
+
+                if (thing == "ps")
+                { // we use the known ps instead of generating one
+                    var configFile = clips.Get("config", clips.Get("expfile", null));
+                    var config = configFile == null ? null : CliPlotHelpers.LoadExperimentConfig(configFile);
+                    
+                    return CliGenomePlotter.PlotExpressionVector(clips, individual.Phenotype.Vector, title ?? "Phenotypic Expression", config);
+                }
+                else
+                {
+                    return CliGenomePlotter.PlotDenseGenome(console, clips, individual.Genome, title, null);
+                }
+            }
+            else
+            {
+                return PlotTracee(console, clips, traceInfo, title, false);
+            }
+        }
+
+        // this will do for now
+        public static readonly List<ICliTraceeProvider<DenseIndividual>> DefaultDenseCliTraceeProviders = new List<ICliTraceeProvider<DenseIndividual>>()
+        {
+            new Tracee1DProvider<DenseIndividual>("fitness:", "Fitness", _ => ws => ws.Judgements.Average(ij => ij.Judgement.CombinedFitness), _ => 1, OxyColors.Red, OxyColors.Red, 2),
+            new Tracee1DProvider<DenseIndividual>("benefit:", "Benefit", _ => ws => ws.Judgements.Average(ij => ij.Judgement.Benefit), _ => 1, OxyColors.OrangeRed, OxyColors.OrangeRed, 2),
+            new Tracee1DProvider<DenseIndividual>("cost:", "Cost", _ => ws => ws.Judgements.Average(ij => ij.Judgement.Cost), _ => 1, OxyColors.Orange, OxyColors.Orange, 2),
+            new Tracee1DProvider<DenseIndividual>("ptraits:", "Phenotypic Traits", i => ws => ws.Judgements.Average(ij => ij.Individual.Phenotype[i]), Tracee1DProvider<DenseIndividual>.DenseSize, OxyColors.DarkBlue, OxyColors.LightBlue, 2),
+            new Tracee1DProvider<DenseIndividual>("gtraits:", "Genotypic Traits", i => ws => ws.Judgements.Average(ij => ij.Individual.Genome.InitialState[i]), Tracee1DProvider<DenseIndividual>.DenseSize, OxyColors.DarkViolet, OxyColors.Violet, 2),
+            new Tracee2DProvider<DenseIndividual>("regcoefs:", "Regulation Coefficients", (i, j) => ws => ws.Judgements.Average(ij => ij.Individual.Genome.TransMat[i, j]), Tracee1DProvider<DenseIndividual>.DenseSize, Tracee1DProvider<DenseIndividual>.DenseSize, OxyColors.DarkCyan, OxyColors.LightCyan, OxyColors.DarkGreen, OxyColors.LightGreen, 2),
+            new Tracee1DProvider<DenseIndividual>("gsum:", "Genotypic Trait Sum", i => ws => ws.Judgements.Average(ij => ij.Individual.Genome.InitialState.Sum()), ti => 1, OxyColors.Purple, OxyColors.Purple, 2),
+            new Tracee1DProvider<DenseIndividual>("sudoku:", "Sudoku Score", i => ws => ws.Judgements.Average(ij => Sudoku.LocateConflicts(Sudoku.RenderSolution(ij.Individual.Phenotype.Vector, out _)).Count()), ti => 1, OxyColors.Brown, OxyColors.SaddleBrown, 2),
+            new HuskyProvider("huskyness:", "Degree of Hierarchy", OxyColors.DarkSlateGray, OxyColors.SlateGray, 2),
+            new ModuleIndependenceProvider("moduleindependence:", "Degree of Module Independence", OxyColors.Teal, OxyColors.DarkTurquoise, 2),
+            new DeltaBProvider("deltab:", "db/dB", OxyColors.DarkCyan, OxyColors.LightCyan, OxyColors.DarkGreen, OxyColors.LightGreen, 2),
+        };
+
+        public CliTraceePlotter(IEnumerable<ICliTraceeProvider<DenseIndividual>> denseCliTraceeProviders)
+        {
+            Providers = new List<ICliTraceeProvider<DenseIndividual>>(denseCliTraceeProviders);
+        }
+
+        public List<ICliTraceeProvider<DenseIndividual>> Providers { get; }
+
+        public PlotModel PlotTracee(TextWriter console, CliParams clips, TraceInfo<DenseIndividual> traceInfo, string title, bool epochsx)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("The trace plotter can plot a combination of things from a trace.\n" +
+                    "All things are mean samples\n" +
+                    "Here are the options:\n" +
+                    " recoefs -> plots reg-coefs\n" +
+                    " ptraits -> plots phenotypic trait expression\n" +
+                    " gtraits -> plots genotypic trait expression\n" +
+                    " fitness -> plots fitness (by default, this is plotted, and on the right-hand axis)\n" +
+                    "You can change the start and end generation.\n" +
+                    "You can downsample the data (i.e. reduce resolution without average; performed after target filtering)\n" +
+                    "You can change the resolution (number of samples averaged per data-point (i.e. higher resolution gives courser plot))\n" +
+                    "You can configure target change indicators targetlines=line/labels (labels by default)\n" +
+                    "You can switch to plotting the change of metrics with diff\n" +
+                    "You can specify the targets to look at, qualified by index");
+            }
+
+            if (epochsx)
+            {
+                int start = clips.Get("start", Misc.ParseInt, traceInfo.TraceWholeSamples.First().Epoch);
+                int end = clips.Get("end", Misc.ParseInt, traceInfo.TraceWholeSamples.Last().Epoch);
+
+                traceInfo = traceInfo.ExtractEpochs(start, end);
+            }
+            else
+            {
+                long start = clips.Get("start", Misc.ParseLong, traceInfo.TraceWholeSamples.First().Generations);
+                long end = clips.Get("end", Misc.ParseLong, traceInfo.TraceWholeSamples.Last().Generations);
+
+                traceInfo = traceInfo.ExtractGenerations(start, end);
+            }
+
+            // boundary dropping
+            var dropMode = clips.Get("dropMode", null);
+            if (string.IsNullOrEmpty(dropMode) || dropMode.Equals("none", StringComparison.InvariantCultureIgnoreCase))
+            {
+                // nix
+            }
+            else if (dropMode.Equals("dropFirst", StringComparison.InvariantCultureIgnoreCase))
+            {
+                traceInfo = traceInfo.DropEpochBoundaries(true);
+            }
+            else if (dropMode.Equals("dropLast", StringComparison.InvariantCultureIgnoreCase))
+            {
+                traceInfo = traceInfo.DropEpochBoundaries(false);
+            }
+            else
+            {
+                throw new Exception("Unrecognised dropMode: " + dropMode + "\nConsider one of: - dropFist\n - dropLast\n - none");
+            }
+
+            int downsample = clips.Get("downsample", Misc.ParseInt, 1); // skipping
+            int resolution = clips.Get("resolution", Misc.ParseInt, 1); // averaging
+
+            double offset = 0.0;
+            if (clips.IsSet("retime"))
+            {
+                long retime = clips.Get("retime", Misc.ParseLong);
+                offset = retime - (epochsx ? traceInfo.TraceWholeSamples[0].Epoch : traceInfo.TraceWholeSamples[0].Generations);
+            }
+
+            string targetlines = clips.Get("targetlines", "labels");
+            string epochlines = clips.Get("epochlines", "none");
+            string[] targets = clips.Get("targets", s => s.Split(new[] { ',', ';' }), null);
+
+            if (targets != null && (targets.Length == 0 || (targets.Length == 1 && targets[0] == "")))
+            { // use the first one we see, whatever it is
+                targets = new[] { traceInfo.TraceWholeSamples[0].Target.FriendlyName };
+                console.WriteLine("Using default target: " + targets[0]);
+            }
+
+            if (targets != null)
+            {
+                HashSet<string> ttable = new HashSet<string>(targets, StringComparer.InvariantCultureIgnoreCase);
+                traceInfo = traceInfo.Filter(ws => ttable.Contains(ws.Target.FriendlyName));
+            }
+            else if (epochsx) // we don't want targets when processing at generation level
+            {
+                console.WriteLine("No targets specified. Available targets include: " + string.Join(", ", traceInfo.TraceWholeSamples.Select(ws => ws.Target.FriendlyName).Distinct().Take(5)));
+            }
+
+            if (downsample > 1)
+            {
+                traceInfo = traceInfo.DownSample(downsample, true);
+            }
+
+            string evalexpfile = clips.Get("evalexpfile", null);
+            if (evalexpfile != null)
+            {
+                var evalExp = PopulationExperiment<DenseIndividual>.Load(evalexpfile);
+                var projectionMode = clips.Get("projectionmode", CliProject.ParseProjectionMode);
+                bool noNextExposure = clips.Get("noNextExposure", bool.Parse, false);
+
+                var projector = new BasicExtractorWholeSampleProjectorPreparer(projectionMode, noNextExposure).PrepareProjector<DenseIndividual>(evalExp);
+                var rand = new MathNet.Numerics.Random.MersenneTwister();
+                var context = new ModelExecutionContext(rand);
+
+                var projected = WholeSampleProjectionHelpers.Project(console, context, traceInfo.TraceWholeSamples, projector, true, true);
+                traceInfo = new TraceInfo<DenseIndividual>(projected);
+            }
+
+            string xAxisKey = clips.Get("xaxiskey", "generations");
+            bool logX = clips.Get("logX", bool.Parse, false);
+
+            bool fitnessOnByDefault = true;
+            var plot = TracePlotting.PreparePlot(title, true, epochsx, xAxisKey, logX);
+
+            // provider plotting
+            if (clips.IsSet("providers"))
+            {
+                var providerStrings = clips.Get("providers").Split(';');
+                foreach (var ps in providerStrings)
+                {
+                    var provider = Providers.FirstOrDefault(p => ps.StartsWith(p.Prefix));
+                    if (provider == null)
+                    {
+                        console.WriteLine("No provider available for " + ps);
+                    }
+                    else
+                    {
+                        provider.PlotStuff(console, plot, ps.Substring(provider.Prefix.Length), clips, traceInfo, epochsx, offset);
+                    }
+                }
+                fitnessOnByDefault = false;
+            }
+
+            // legacy/specialist plotting
+            double? ymin = clips.Get("ymin", s => (double?)double.Parse(s), null);
+            double? ymax = clips.Get("ymax", s => (double?)double.Parse(s), null);
+            double? fitnessmin = clips.Get("fitnessmin", s => (double?)double.Parse(s), null);
+            double? fitnessmax = clips.Get("fitnessmax", s => (double?)double.Parse(s), null);
+            bool diff = clips.IsSet("diff");
+            var injectBreaks = clips.Get("injectBreaks", bool.Parse, false);
+            var brokenLineStyle = clips.Get("brokenLineStyle", CliPlotHelpers.ParseLineStyle, LineStyle.Dot);
+
+            // TODO: would like to abstract these out... the problem is that ptraits and fitness don't require a DenseIndividual... and I can't think right now how to handle that
+            // (real problem is that TraceInfo<T> doesn't have an abstract version without type info (e.g. ITraceInfo) which can pull IIndividuals)
+            PlotOptions regcoefs = clips.Get("regcoefs", PlotOptions.DefaultParser, PlotOptions.DefaultOff);
+            PlotOptions ptraits = clips.Get("ptraits", PlotOptions.DefaultParser, PlotOptions.DefaultOff);
+            PlotOptions gtraits = clips.Get("gtraits", PlotOptions.DefaultParser, PlotOptions.DefaultOff);
+            PlotOptions fitness = clips.Get("fitness", PlotOptions.Parser(new PlotOptions(fitnessOnByDefault, false)), new PlotOptions(fitnessOnByDefault, false));
+            PlotOptions huskyness = clips.Get("huskyness", PlotOptions.DefaultParser, PlotOptions.DefaultOff);
+            PlotOptions devtimedominance = clips.Get("devtimedominance", PlotOptions.DefaultParser, PlotOptions.DefaultOff);
+
+            if (regcoefs.Enabled)
+                TracePlotting.PlotTraceRegCoegs(plot, traceInfo, resolution, ymin, ymax, regcoefs.LeftAxis, epochsx, diff, offset);
+            if (ptraits.Enabled)
+                TracePlotting.PlotPhenotypicTraceTraits(plot, traceInfo, resolution, ymin, ymax, ptraits.LeftAxis, epochsx, diff, offset, ptraits.PlotType, ptraits.MarkerType, injectBreaks, brokenLineStyle);
+            if (gtraits.Enabled)
+                TracePlotting.PlotGenotypicTraceTraits(plot, traceInfo, resolution, ymin, ymax, gtraits.LeftAxis, epochsx, diff, offset, gtraits.PlotType, gtraits.MarkerType, injectBreaks, brokenLineStyle);
+            if (fitness.Enabled)
+                TracePlotting.PlotTraceFitness(plot, traceInfo, resolution, fitnessmin, fitnessmax, fitness.LeftAxis, epochsx, diff, offset, fitness.LineStyle, fitness.PlotType, fitness.MarkerType);
+            if (huskyness.Enabled || devtimedominance.Enabled)
+            {
+                int exampleN = traceInfo.TraceWholeSamples[0].Judgements[0].Individual.Genome.Size;
+
+                var modules = CliPlotHelpers.GuessModules(console, clips, exampleN);
+                var mts = modules.ToArray();
+
+                if (huskyness.Enabled)
+                {
+                    TracePlotting.PlotTraceHuskyness(plot, traceInfo, mts, resolution, ymin, ymax, huskyness.LeftAxis, epochsx, diff, offset, huskyness.LineStyle, huskyness.PlotType, huskyness.MarkerType, injectBreaks, brokenLineStyle);
+                }
+
+                if (devtimedominance.Enabled)
+                {
+                    // need a config at a minimum
+                    var configFile = clips.Get("config", clips.Get("expfile", null));
+                    if (configFile != null)
+                    {
+                        ExperimentConfiguration config = CliPlotHelpers.LoadExperimentConfig(configFile);
+                        int seed = clips.GetOrCreate("seed", int.Parse, () => CliExp.SeedSource.Next());
+                        var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+
+                        TracePlotting.PlotTraceDevTimeDominance(plot, rand, config.DevelopmentRules, traceInfo, mts, resolution, ymin, ymax, devtimedominance.LeftAxis, epochsx, diff, offset, devtimedominance.LineStyle, devtimedominance.PlotType, devtimedominance.MarkerType, injectBreaks, brokenLineStyle);
+                    }
+                    else
+                    {
+                        console.WriteLine("Require a config or expfile to compute dev-time dominance");
+                    }
+                }
+            }
+
+            var al = plot.Axes.Where(a => a.Position == OxyPlot.Axes.AxisPosition.Left);
+            var ar = plot.Axes.Where(a => a.Position == OxyPlot.Axes.AxisPosition.Right);
+
+            foreach (var axes in new[] { ar, al })
+            {
+                int ai = 1;
+                foreach (var a in axes)
+                {
+                    a.PositionTier = ai++;
+                }
+            }
+
+            if (!epochsx && (targetlines == null || targetlines == "line" || targetlines == "labels"))
+                TracePlotting.AnnotateTraceTargetTransitions(plot, traceInfo, targetlines == "labels", offset);
+
+            if (!epochsx && (epochlines == null || epochlines == "line" || epochlines == "labels"))
+                TracePlotting.AnnotateTraceEpochsTransitions(plot, traceInfo, epochlines == "labels", offset);
+
+            return plot;
+        }
+    }
+
+    public class CliTracesPlotter : ICliPlotter
+    {
+        public string Prefix => "traces";
+
+        public OxyPlot.PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("The traces plotter can plot one or more traces on the same graph\n" +
+                    " - names (list of comma-separated names\n" +
+                    " - resolution (plot sample period)\n" +
+                    " - mean (flag, uses 'mean' rather than 'best' samples)" +
+                    " - areas (flag, plots percentile areas rather than individual trajectories)\n" +
+                    " - generation (plots a histogram for the given generation, rather than the whole trace\n" +
+                    " - ymax/ymin");
+            }
+
+            double? ymin = clips.Get("ymin", s => (double?)double.Parse(s), null);
+            double? ymax = clips.Get("ymax", s => (double?)double.Parse(s), null);
+            
+            string xAxisKey = clips.Get("xaxiskey", "x");
+            string yAxisKey = clips.Get("yaxiskey", "y");
+
+            var filenames = filename.Split(';');
+            var tracesInfos = filenames.Select(fname => TracesInfo.Load(fname)).ToArray();
+            bool mean = clips.IsSet("mean");
+            var names = clips.Get("names", "").Split(new[] { ',', ';' });
+            int resolution = clips.Get("resolution", Misc.ParseInt, 1);
+
+            var strokeThickness = clips.Get("strokeThickness", double.Parse, 2.0);
+
+            if (clips.IsSet("generation"))
+            {
+                // TODO: plotting
+                long generation = clips.Get("generation", long.Parse);
+
+                double[][] samples = mean
+                    ? tracesInfos.Select(ti => ti.ExtractMeanSamples(generation)).ToArray()
+                    : tracesInfos.Select(ti => ti.ExtractBestSamples(generation)).ToArray();
+                
+                // TODO: test this
+                console.WriteLine("Pairwise Mann-whitney");
+                for (int i = 0; i < samples.Length; i++)
+                    for (int j = i + 1; j < samples.Length; j++)
+                        console.WriteLine($"{i}, {j}:\t" + M4M.Stats.MannWhitneyU(samples[0], samples[1], TestType.TwoTailed).P);
+
+                // not good
+                return null;
+            }
+            else
+            {
+                bool logX = clips.Get("logX", bool.Parse, false);
+                bool logY = clips.Get("logY", bool.Parse, false);
+
+                var plot = TrajectoryPlotting.PrepareTrajectoriesPlot(title == "" ? (mean ? "Mean Fitnesses" : "Best Fitness") : title, "Generation", "Fitness", null, ymin, ymax, xAxisKey, yAxisKey, null, logX, logY, false);
+
+                OxyColor[] colours = new[] { OxyColors.Red, OxyColors.Blue, OxyColors.Green, OxyColors.Purple, OxyColors.Goldenrod };
+                colours = clips.Get("colors", s => s.Split(';').Select(CliPlotHelpers.ParseColour).ToArray(), colours);
+
+                int tii = 0;
+                foreach (var _tracesInfo in tracesInfos)
+                {
+                    long start = clips.Get("start", Misc.ParseLong, _tracesInfo.StartGeneration);
+                    long end = clips.Get("end", Misc.ParseLong, _tracesInfo.EndGeneration);
+                    var tracesInfo = _tracesInfo.Extract(start, end);
+
+                    string targets = clips.Get("targets", "labels");
+
+                    var colour = colours[tii % colours.Length]; // TODO: make this less terrible
+
+                    var samples = mean ? tracesInfo.MeanFitnesses.ToArray() : tracesInfo.BestFitnesses.ToArray();
+
+                    long retimeStart = clips.Get("retimeStart", long.Parse, tracesInfo.StartGeneration);
+
+                    if (clips.IsSet("areas"))
+                    {
+                        var labelAreas = clips.Get("labelAreas", bool.Parse, true);
+                        var cts = new ColourfulTraces(names[tii % names.Length], colour, samples, tracesInfo.SamplePeriod, retimeStart);
+                        var wide = clips.Get("wide", str => str == null ? new[] { 0, 50 } : CliPlotHelpers.ParseStrictIntList(str), new[] { 10, 25, 50 });
+                        EvolvabilityTraces.PlotArea(plot, cts, EvolvabilityTraces.SymetricTracePercentileInformations(cts.Name, cts.Colour, wide, labelAreas, strokeThickness), resolution);
+                    }
+                    else
+                    {
+                        TrajectoryPlotting.PlotTrajectories(plot, "Mean Fitness", samples, tracesInfo.SamplePeriod, retimeStart, colour, colour, strokeThickness, resolution, TrajectoryPlotType.Line, xAxisKey, yAxisKey);
+                    }
+
+                    if (tii == 0)
+                    { // NOTE: these don't make a whole lot of sense for multiple traces
+                        if (targets == null || targets == "line" || targets == "labels")
+                            TracePlotting.AnnotateTracesTargetTransitions(plot, tracesInfo, targets == "labels");
+                    }
+
+                    tii++;
+                }
+                return plot;
+            }
+        }
+    }
+
+    public class CliTrajTracesPlotter : ICliPlotter
+    {
+        public string Prefix => "trajtraces";
+        public OxyColor[] DefaultFixedColors = new[] { OxyColors.Red, OxyColors.Blue, OxyColors.Green, OxyColors.Purple, OxyColors.Goldenrod };
+        public ITrajectoryColours DefaultColorGenerator = new TrajectoryColours1D(OxyColors.Blue, OxyColors.Red);
+
+        public OxyColor[] DefaultColors(int count)
+        {
+            if (count <= DefaultFixedColors.Length)
+                return DefaultFixedColors.Take(count).ToArray();
+            else
+                return DefaultColorGenerator.Colours(count);
+        }
+
+        public OxyPlot.PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("The traj traces plotter can plot one or more traj traces on the same graph\n" +
+                    " - names (list of comma-separated names\n" +
+                    " - resolution (plot sample period)\n" +
+                    " - areas (flag, plots percentile areas rather than individual trajectories)\n" +
+                    " - ymax/ymin");
+            }
+            
+            int resolution = clips.Get("resolution", Misc.ParseInt, 1);
+
+            var tracesFiles = clips.Get("files", s => s.Split(';'), new[] { filename });
+
+            double? ymin = clips.Get("ymin", s => (double?)double.Parse(s), null);
+            double? ymax = clips.Get("ymax", s => (double?)double.Parse(s), null);
+            
+            string xAxisKey = clips.Get("xaxiskey", "x");
+            string yAxisKey = clips.Get("yaxiskey", "y");
+
+            bool logX = clips.Get("logX", bool.Parse, false);
+            bool logY = clips.Get("logY", bool.Parse, false);
+
+            var strokeThickness = clips.Get("strokeThickness", double.Parse, 2.0);
+
+            var keeper = CliPlotHelpers.Keeper(clips);
+            var resampler = CliPlotHelpers.DefaultResampler(clips);
+            void resample(ref double[][] trajectories, ref int samplePeriod, out int startTime, bool stripNulls)
+            {
+                keeper(ref trajectories, ref samplePeriod); // leaves nulls where things are no kept
+
+                if (stripNulls)
+                    trajectories = trajectories.Where(t => t != null).ToArray();
+
+                resampler(ref trajectories, ref samplePeriod);
+
+                var example = trajectories.First(t => t != null);
+
+                int start = clips.Get("start", Misc.ParseInt, 0);
+                int end = clips.Get("end", Misc.ParseInt, example.Length * samplePeriod);
+                console.WriteLine(end);
+                int skip = start / samplePeriod;
+                int take = Math.Min((end + samplePeriod - start) / samplePeriod, example.Length - skip);
+                trajectories = trajectories.Select(traj => traj?.Skip(skip).Take(take).ToArray()).ToArray();
+
+                startTime = start;
+            }
+
+            if (clips.IsSet("generation") || clips.IsSet("epoch"))
+            {
+                var epochs = clips.Get("epoch", clips.Get("generation", "-1")).Split(';').Select(Misc.ParseInt).ToArray();
+                var colors = clips.Get("color", s => s.Split(';').Select(CliPlotHelpers.ParseColour).ToArray(), DefaultColors(epochs.Length));
+
+                var labels = clips.Get("label", s => s.Split(';'), Enumerable.Range(0, tracesFiles.Length).Select(i => ""+i).ToArray());
+                var catTitle = clips.Get("catTitle", "Category");
+
+                if (clips.IsSet("histogram"))
+                {
+                    var plot = HistogramPlotting.PrepareDodgyHistogram(title, $"(qualify title:{xAxisKey})", "Occurances", xAxisKey, yAxisKey);
+                    var yAxis = plot.Axes.First(a => a.Key == yAxisKey);
+                    if (ymin != null)
+                        yAxis.Minimum = ymin.Value;
+                    if (ymax != null)
+                        yAxis.Maximum = ymax.Value;
+
+                    var epoch = epochs.Single();
+
+                    var samples = new List<double[]>();
+
+                    for (int i = 0; i < tracesFiles.Length; i++)
+                    {
+                        var traj = Analysis.LoadTrajectories(tracesFiles[i], out var samplePeriod);
+                        var idx = epoch / samplePeriod;
+                        var sample = Analysis.ExtractArray(traj, idx);
+
+                        samples.Add(sample);
+                    }
+
+                    double min = clips.Get("min", double.Parse, samples.Min(s => s.Min()));
+                    double max = clips.Get("max", double.Parse, samples.Max(s => s.Max()));
+                    int binCount = clips.Get("binCount", int.Parse, 10);
+
+                    HistogramPlotting.DodgyHistogram(plot, samples, colors, min, max, binCount);
+
+                    return plot;
+                }
+                else if(clips.IsSet("MWU"))
+                {
+                    // matrix of P-values
+                    var plot = new PlotModel() { Title = title };
+
+                    var samples = new List<double[]>();
+
+                    for (int i = 0; i < tracesFiles.Length; i++)
+                    {
+                        var subsamples = new List<double>();
+
+                        foreach (var epoch in epochs)
+                        {
+                            var traj = Analysis.LoadTrajectories(tracesFiles[i], out var samplePeriod);
+                            var idx = epoch / samplePeriod;
+                            subsamples.AddRange(Analysis.ExtractArray(traj, idx));
+                        }
+
+                        samples.Add(subsamples.ToArray());
+                        console.WriteLine($"SubSamples for {catTitle} = {labels[i]}");
+                        console.WriteLine(string.Join(",", subsamples));
+                        console.WriteLine($"Distincts for {catTitle} = {labels[i]}");
+                        console.WriteLine(string.Join(",", subsamples.Distinct()));
+                    }
+
+                    int scount = samples.Count;
+                    var mat = new double[scount, scount];
+
+                    var testType = clips.IsSet("twotailed") ? TestType.TwoTailed : TestType.UpperTailed;
+                    var showu = clips.IsSet("showu");
+
+                    // populate mat with p-values from MWU
+                    for (int i = 0; i < scount; i++)
+                    {
+                        for (int j = 0; j < scount; j++)
+                        {
+                            if (showu)
+                            {
+                                mat[i, j] = Stats.MannWhitneyU(samples[i], samples[j], testType).U;
+                            }
+                            else
+                            {
+                                mat[i, j] = Math.Log10(Stats.MannWhitneyU(samples[i], samples[j], testType).P);
+                            }
+                        }
+                    }
+
+                    var ms = new HeatMapSeries()
+                    {
+                        CoordinateDefinition = HeatMapCoordinateDefinition.Center,
+                        Data = mat,
+                        X0 = 0,
+                        X1 = scount - 1,
+                        Y0 = 0,
+                        Y1 = scount - 1,
+                        Interpolate = false,
+                        LabelFontSize = 0.25,
+                    };
+
+                    var cx = new CategoryAxis() { Position = AxisPosition.Bottom, Title = catTitle };
+                    cx.Labels.AddRange(labels);
+
+                    var cy = new CategoryAxis() { Position = AxisPosition.Left, Title = catTitle, StartPosition = 1, EndPosition = 0 };
+                    cy.Labels.AddRange(labels);
+
+                    var coloraxis = new OxyPlot.Axes.LinearColorAxis() { Title = showu ? "U" : "p-Value", Position = OxyPlot.Axes.AxisPosition.Right, Palette = OxyPalettes.Gray(10), Key = "color", InvalidNumberColor = OxyColors.Pink };
+
+                    plot.Axes.Add(cx);
+                    plot.Axes.Add(cy);
+                    plot.Axes.Add(coloraxis);
+                    plot.Series.Add(ms);
+
+                    if (epochs.Length > 1)
+                    {
+                        console.WriteLine("Warning! Using multiple epochs violates the assumption of sample independence! Don't do it!");
+                    }
+
+                    var f = new[] { 14.8, 7.3, 5.6, 6.3, 9, 4.2, 10.6, 12.5, 12.9, 16.1, 11.4, 2.7 };
+                    var t = new[] { 12.7, 14.2, 12.6, 2.1, 17.7, 11.8, 16.9, 7.9, 16, 10.6, 5.6, 5.6, 7.6, 11.3, 8.3, 6.7, 3.6, 1, 2.4, 6.4, 9.1, 6.7, 18.6, 3.2, 6.2, 6.1, 15.3, 10.6, 1.8, 5.9, 9.9, 10.6, 14.8, 5, 2.6, 4 };
+
+                    console.WriteLine(Stats.MannWhitneyU(f, t, TestType.LowerTailed));
+                    console.WriteLine(Stats.MannWhitneyU(f, t, TestType.UpperTailed));
+                    console.WriteLine(Stats.MannWhitneyU(f, t, TestType.TwoTailed));
+
+                    return plot;
+                }
+                else if (clips.IsSet("bar"))
+                {
+
+                }
+                else // box plots
+                {
+                    var plot = BoxPlotting.PrepareBoxPlot(labels, catTitle, $"(qualify title:{yAxisKey})", xAxisKey, yAxisKey);
+
+                    int ei = 0;
+                    foreach (var epoch in epochs)
+                    {
+                        var samples = new List<double[]>();
+
+                        for (int i = 0; i < tracesFiles.Length; i++)
+                        {
+                            var traj = Analysis.LoadTrajectories(tracesFiles[i], out var samplePeriod);
+                            var idx = epoch / samplePeriod;
+                            var sample = Analysis.ExtractArray(traj, idx);
+                            
+                            samples.Add(sample);
+                        }
+
+                        var bp = BoxPlotting.BoxPlot(plot, samples);
+                        
+                        var color = colors[ei++];
+                        bp.Stroke = color;
+                    }
+
+                    return plot;
+                }
+            }
+
+            if (clips.IsSet("MWU"))
+            {
+                if (tracesFiles.Length != 2)
+                {
+                    console.WriteLine("Mann-Whitney U over a range only works with exactly 2 traces");
+                    return null;
+                }
+
+                // assume 2
+                
+                var traj1 = Analysis.LoadTrajectories(tracesFiles[0], out var samplePeriod1);
+                var traj2 = Analysis.LoadTrajectories(tracesFiles[1], out var samplePeriod2);
+                resample(ref traj1, ref samplePeriod1, out int start, true);
+                resample(ref traj2, ref samplePeriod2, out start, true);
+
+                if (samplePeriod1 != samplePeriod2)
+                    console.WriteLine("Sample periods do not match.");
+
+                var samples1 = Misc.TrajectoriesToSamples(traj1);
+                var samples2 = Misc.TrajectoriesToSamples(traj2);
+
+                List<double> mwu = new List<double>();
+                for (int i = 0; i < traj1[0].Length; i += resolution)
+                    mwu.Add(M4M.Stats.MannWhitneyU(samples1[i], samples2[i], TestType.TwoTailed).P);
+
+                var plot = TrajectoryPlotting.PlotTrajectories(title, "Epochs", "Two-Tailed P-value", null, "", new [] { mwu.ToArray() }, samplePeriod1 * resolution, start, ymin, ymax, OxyColors.Green, null, 2, 1, TrajectoryPlotType.Scatter, xAxisKey, yAxisKey, null, logX, logY, false);
+                var ls = (OxyPlot.Series.ScatterSeries)plot.Series.First();
+
+                if (clips.IsSet("pvaluethreshold"))
+                {
+                    // nightmare inducing 'significance' colouring (makes little sense if you ask me)
+                    var pvaluethreshold = clips.Get("pvaluethreshold", s => s == null ? 0.05 : double.Parse(s));
+                    foreach (var sp in ls.Points)
+                        sp.Value = sp.Y;
+                    var ca = new OxyPlot.Axes.RangeColorAxis() { Minimum = 0.0, Maximum = 1.0, LowColor = OxyColors.DarkGreen, HighColor = OxyColors.DarkRed, Key = "pvalue_ca" };
+                    ca.AddRange(0.0, pvaluethreshold, OxyColors.DarkGreen);
+                    ca.AddRange(pvaluethreshold, 1.0, OxyColors.DarkRed);
+                    ls.ColorAxisKey = ca.Key;
+                    ls.MarkerFill = OxyColors.Automatic;
+                    plot.Axes.Add(ca);
+                }
+
+                return plot;
+            }
+            else
+            {
+                var labels = clips.Get("label", s => s.Split(';'), Enumerable.Range(0, tracesFiles.Length).Select(i => ""+i).ToArray());
+                var colors = clips.Get("color", s => s.Split(';').Select(CliPlotHelpers.ParseColour).ToArray(), DefaultColors(tracesFiles.Length));
+
+                var plot = TrajectoryPlotting.PrepareTrajectoriesPlot(title, "Generation", $"(Qualify title:{yAxisKey})", null, ymin, ymax, xAxisKey, yAxisKey, null, logX, logY, false);
+
+                for (int i = 0; i < tracesFiles.Length; i++)
+                {
+                    bool lines = clips.IsSet("lines");
+
+                    var traj = Analysis.LoadTrajectories(tracesFiles[i], out var samplePeriod);
+                    resample(ref traj, ref samplePeriod, out int start, lines == true);
+                    var cts = new ColourfulTraces(labels[i], colors[i], traj, samplePeriod, start);
+   
+                    if (lines)
+                    {
+                        TrajectoryPlotting.PlotTrajectories(plot, "Mean Fitness", traj, samplePeriod * resolution, start, cts.Colour, cts.Colour, strokeThickness, resolution, TrajectoryPlotType.Line, xAxisKey, yAxisKey);
+                    }
+                    else // areas
+                    {
+                        var labelAreas = clips.Get("labelAreas", bool.Parse, true);
+                        var wide = clips.Get("wide", str => str == null ? new[] { 0, 50 } : CliPlotHelpers.ParseStrictIntList(str), new[] { 10, 25, 50 });
+                        EvolvabilityTraces.PlotArea(plot, cts, EvolvabilityTraces.SymetricTracePercentileInformations(cts.Name, cts.Colour, wide, labelAreas, strokeThickness), resolution);
+                    }
+                }
+
+                return plot;
+            }
+        }
+    }
+
+    public class CliRcsPlotter : ICliPlotter
+    {
+        private static readonly IReadOnlyList<ICliTrajectoryAnnotater> DefaultAnnotators = new ICliTrajectoryAnnotater[] { new CliTrajectoryDtmTransitionAnnotater(), new CliTrajectoryHuskynessTransitionAnnotater(), new CliTrajectoryModuleIndependenceTransitionAnnotater() };
+        private static readonly CliTrajectoryPlotter RcsTrajectoryPlotterGreen = new CliTrajectoryPlotter("rcs", "Epochs", "Regulation Coefficient", "Regulation Coefficients", new TrajectoryColours2D(OxyColors.DarkCyan, OxyColors.LightCyan, OxyColors.DarkGreen, OxyColors.LightGreen), 2.0, DefaultAnnotators);
+        private static readonly CliTrajectoryPlotter RcsTrajectoryPlotter = new CliTrajectoryPlotter("rcs", "Epochs", "Regulation Coefficient", "Regulation Coefficients", new TrajectoryColours1D(null, null), 2.0, DefaultAnnotators);
+
+        public string Prefix => "rcs";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            if (clips.IsSet("epoch"))
+            {
+                int samplePeriod;
+                var trajectories = Analysis.LoadTrajectories(filename, out samplePeriod);
+                samplePeriod = clips.Get("samplePeriod", Misc.ParseInt, samplePeriod);
+                if (samplePeriod == -1)
+                {
+                    samplePeriod = 1; // default
+                    console.WriteLine("SamplePeriod unqualified");
+                }
+
+                int epoch = clips.Get("epoch", Misc.ParseInt);
+
+                int size = (int)Math.Round(Math.Sqrt(trajectories.Length));
+                int index = epoch / samplePeriod;
+                if (index * samplePeriod != epoch)
+                    console.WriteLine("Plotting closest available epoch: " + (index * samplePeriod));
+
+                // plot a genome, not trjectories
+                var transMat = Analysis.ExtractMatrix(trajectories, index, size, size);
+
+                return CliGenomePlotter.PlotDtm(clips, transMat, title);
+            }
+            else
+            {
+                if (clips.IsSet("green"))
+                    return RcsTrajectoryPlotterGreen.Plot(console, clips, filename, title);
+                else
+                    return RcsTrajectoryPlotter.Plot(console, clips, filename, title);
+            }
+        }
+    }
+
+    public interface ICliTrajectoryAnnotater
+    {
+        string Key { get; }
+        bool Default { get; }
+        /// <summary>
+        /// Plots the annotations
+        /// </summary>
+        /// <param name="console">The reporting console</param>
+        /// <param name="plot">The plot model to modify</param>
+        /// <param name="clips">CliParams</param>
+        /// <param name="trajectories">The Rcs Trajectories being plotted</param>
+        /// <param name="samplePeriod">The trajectories sample period</param>
+        /// <param name="startTime">Plotting start time</param>
+        /// <param name="xAxisKey">Temporal X-Axis Key</param>
+        /// <param name="yAxisKey">Rcs Y-Axis Key</param>
+        void AnnotateTrajectories(TextWriter console, PlotModel plot, CliParams clips, double[][] trajectories, int samplePeriod, long startTime, string temporalAxisKey, string rcsAxisKey);
+    }
+
+    public class CliTrajectoryDtmTransitionAnnotater : ICliTrajectoryAnnotater
+    {
+        public CliTrajectoryDtmTransitionAnnotater(double thresholdFactor = 10.0)
+        {
+            ThresholdFactor = thresholdFactor;
+        }
+
+        public string Key => "dtminfo";
+        public bool Default => false;
+
+        public double ThresholdFactor { get; }
+
+        public void AnnotateTrajectories(TextWriter console, PlotModel plot, CliParams clips, double[][] trajectories, int samplePeriod, long startTime, string xAxisKey, string yAxisKey)
+        {
+            foreach (var tc in DtmTimeClassification.ClassifyTrajectories(trajectories, samplePeriod, startTime, ThresholdFactor, 5))
+            {
+                var la = new LineAnnotation() { Type = LineAnnotationType.Vertical, X = tc.Time, Color = OxyColors.DarkKhaki };
+
+                la.TextOrientation = AnnotationTextOrientation.AlongLine;
+                
+                // auto classification is not good enough, just spit out the Dtm Summary
+                //var type1 = DtmClassification.Type1Classify(tc.DtmInfo, DtmClassification.ComputeAutoThreshold(tc.DtmInfo.Matrix, ThresholdFactor));
+                //la.Text = $"{type1} ({tc.DtmInfo.Summary})";
+
+                la.Text = tc.DtmInfo.Summary;
+
+                plot.Annotations.Add(la);
+            }
+        }
+    }
+
+    public class CliTrajectoryHuskynessTransitionAnnotater : ICliTrajectoryAnnotater
+    {
+        public CliTrajectoryHuskynessTransitionAnnotater()
+        {
+        }
+
+        public string Key => "huskyinfo";
+        public bool Default => false;
+        int hnum = 5;
+
+        public void AnnotateTrajectories(TextWriter console, PlotModel plot, CliParams clips, double[][] trajectories, int samplePeriod, long startTime, string temporalAxisKey, string rcsAxisKey)
+        {
+            double completeThreshold = clips.Get("huskythreshold", double.Parse, 0.9);
+            int N = (int)Math.Round(Math.Sqrt(trajectories.Length));
+            int resolution = clips.Get("resolution", Misc.ParseInt, 1);
+
+            string huskynessAxisKey = clips.Get("huskynessAxiskey", "huskyness");
+
+            var huskySamples = CliRcsAnalyser.ComputeHuskynessesSamples(console, clips, trajectories, samplePeriod, startTime, out var modules);
+            int completeIndex = CliRcsAnalyser.DetectThreshold(huskySamples, completeThreshold, hnum);
+
+            int[][] mts = modules.ToArray();
+
+            if (completeIndex >= 0)
+            {
+                long completeTime = completeIndex * samplePeriod + startTime;
+                console.WriteLine($"HuskyCompleted Epoch {completeTime} (Threshold {completeThreshold})");
+
+                var la = new LineAnnotation() { Type = LineAnnotationType.Vertical, X = completeTime, Color = OxyColors.DarkKhaki };
+
+                la.TextOrientation = AnnotationTextOrientation.AlongLine;
+
+                la.Text = $"HuskyCompleted Epoch {completeTime} (Threshold {completeThreshold})";
+
+                plot.Annotations.Add(la);
+            }
+
+            var c0 = OxyColors.DarkSlateGray;
+            var c1 = OxyColors.SlateGray;
+
+            var huskyTrajectories = Misc.SamplesToTrajectories(huskySamples);
+
+            TrajectoryPlotting.AddY2Axis(plot, "Huskyness", huskynessAxisKey, 0, null);
+            TrajectoryPlotting.PlotTrajectories(plot, "Huskyness", huskyTrajectories, samplePeriod, startTime, c0, c1, 2, resolution, TrajectoryPlotType.Line, temporalAxisKey, huskynessAxisKey);
+        }
+    }
+
+    public class CliTrajectoryModuleIndependenceTransitionAnnotater : ICliTrajectoryAnnotater
+    {
+        public CliTrajectoryModuleIndependenceTransitionAnnotater()
+        {
+        }
+
+        public string Key => "moduleindependenceinfo";
+        public bool Default => false;
+        int hnum = 5;
+
+        public void AnnotateTrajectories(TextWriter console, PlotModel plot, CliParams clips, double[][] trajectories, int samplePeriod, long startTime, string temporalAxisKey, string rcsAxisKey)
+        {
+            double completeThreshold = clips.Get("ModuleIndependencethreshold", double.Parse, 0.9);
+            int N = (int)Math.Round(Math.Sqrt(trajectories.Length));
+            int resolution = clips.Get("resolution", Misc.ParseInt, 1);
+
+            string moduleIndependenceAxisKey = clips.Get("ModuleIndependenceAxiskey", "ModuleIndependence");
+
+            var moduleIndependenceSamples = CliRcsAnalyser.ComputeModuleIndependenceSamples(console, clips, trajectories, samplePeriod, startTime, out var modules);
+            int completeIndex = CliRcsAnalyser.DetectThreshold(moduleIndependenceSamples, completeThreshold, hnum);
+
+            int[][] mts = modules.ToArray();
+
+            if (completeIndex >= 0)
+            {
+                long completeTime = completeIndex * samplePeriod + startTime;
+                console.WriteLine($"ModuleIndependenceCompleted Epoch {completeTime} (Threshold {completeThreshold})");
+
+                var la = new LineAnnotation() { Type = LineAnnotationType.Vertical, X = completeTime, Color = OxyColors.DarkKhaki };
+
+                la.TextOrientation = AnnotationTextOrientation.AlongLine;
+
+                la.Text = $"ModuleIndependenceCompleted Epoch {completeTime} (Threshold {completeThreshold})";
+
+                plot.Annotations.Add(la);
+            }
+
+            var c0 = OxyColors.DarkSlateGray;
+            var c1 = OxyColors.SlateGray;
+
+            var moduleIndependenceTrajectories = Misc.SamplesToTrajectories(moduleIndependenceSamples);
+
+            TrajectoryPlotting.AddY2Axis(plot, "Module Independence", moduleIndependenceAxisKey, 0, null);
+            TrajectoryPlotting.PlotTrajectories(plot, "Module Independence", moduleIndependenceTrajectories, samplePeriod, startTime, c0, c1, 2, resolution, TrajectoryPlotType.Line, temporalAxisKey, moduleIndependenceAxisKey);
+        }
+    }
+
+    public class CliTrajectoryPlotter : ICliPlotter
+    {
+        public static readonly CliTrajectoryPlotter IstPlotter = new CliTrajectoryPlotter("ist", "Epochs", "Genotypic Trait Expression", "Genotypic Trait Expression", new TrajectoryColours1D(OxyColors.DarkViolet, OxyColors.Violet), 2.0);
+        public static readonly CliTrajectoryPlotter PstPlotter = new CliTrajectoryPlotter("pst", "Epochs", "Phenotypic Trait Expression", "Phenotypic Trait Expression", new TrajectoryColours1D(OxyColors.DarkBlue, OxyColors.LightBlue), 2.0);
+        public static readonly CliTrajectoryPlotter FitnessPlotter = new CliTrajectoryPlotter("fitness", "Epochs", "Fitness", "Fitness", new TrajectoryColours1D(OxyColors.Red, OxyColors.DarkOrange), 2.0);
+        public static readonly CliTrajectoryPlotter SudokuSamplesPlotter = new CliTrajectoryPlotter("sudokusamples", "Epochs", "Sudoku Score", "Sudoku Score", new TrajectoryColours1D(OxyColors.Brown, OxyColors.SaddleBrown), 2.0);
+        public static readonly CliTrajectoryPlotter NQueensSamplesPlotter = new CliTrajectoryPlotter("nqueenssamples", "Epochs", "NQueens Score", "NQueens Score", new TrajectoryColours1D(OxyColors.Brown, OxyColors.SaddleBrown), 2.0);
+        public static readonly CliTrajectoryPlotter IvmcModuleSwitchesPlotter = new CliTrajectoryPlotter("ivmcswitches", "Epochs", "Switch Event Frequency", "Switch Event Frequency", new TrajectoryColours1D(OxyColors.DeepPink, OxyColors.HotPink), 2.0);
+        public static readonly CliTrajectoryPlotter IvmcModuleSolvesPlotter = new CliTrajectoryPlotter("ivmcsolves", "Epochs", "Solve Frequency", "Solve Frequency", new TrajectoryColours1D(OxyColors.IndianRed, OxyColors.DarkRed), 2.0);
+        public static readonly CliTrajectoryPlotter IvmcPropersPlotter = new CliTrajectoryPlotter("ivmcproper", "Epochs", "Ivmc Module Benefit", "mc Module Benefit", new TrajectoryColours1D(OxyColors.SaddleBrown, OxyColors.SandyBrown), 2.0);
+
+        public List<ICliTrajectoryAnnotater> TrajectoryAnnotaters { get; } = new List<ICliTrajectoryAnnotater>();
+
+        public CliTrajectoryPlotter(string prefix, string xLabel, string yLabel, string seriesName, ITrajectoryColours colors, double strokeThickness, IEnumerable<ICliTrajectoryAnnotater> annotators = null)
+        {
+            Prefix = prefix;
+            XLabel = xLabel;
+            YLabel = yLabel;
+            SeriesName = seriesName;
+            Colors = colors;
+            StrokeThickness = strokeThickness;
+
+            if (annotators != null)
+                TrajectoryAnnotaters.AddRange(annotators);
+        }
+        
+        public string Prefix { get; }
+        public string XLabel { get; }
+        public string YLabel { get; }
+        public string SeriesName { get; }
+
+        public ITrajectoryColours Colors { get; }
+
+        public double StrokeThickness { get; }
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            int samplePeriod;
+            var trajectories = Analysis.LoadTrajectories(filename, out samplePeriod);
+            samplePeriod = clips.Get("samplePeriod", Misc.ParseInt, samplePeriod);
+            if (samplePeriod == -1)
+            {
+                samplePeriod = 1; // default
+                console.WriteLine("SamplePeriod unqualified");
+            }
+
+            title = clips.Get("title", title);
+            var seriesName = clips.Get("seriesname", SeriesName);
+
+            return PlotTrajectories(console, clips, title, seriesName, trajectories, samplePeriod);
+        }
+
+        public PlotModel PlotTrajectories(TextWriter console, CliParams clips, string title, string seriesName, double[][] trajectories, int samplePeriod)
+        {
+            double? ymin = clips.Get("ymin", s => (double?)double.Parse(s), null);
+            double? ymax = clips.Get("ymax", s => (double?)double.Parse(s), null);
+
+            int start = clips.Get("start", Misc.ParseInt, 0);
+            int end = clips.Get("end", Misc.ParseInt, trajectories[0].Length * samplePeriod);
+            
+            var resample = CliPlotHelpers.DefaultResampler(clips);
+            double scaleFactor = clips.Get("scaleFactor", double.Parse, 1.0); // useful, e.g. for ivmcs which are cumulative totals rather than means
+            int resolution = clips.Get("resolution", Misc.ParseInt, 1);
+
+            var trajectoryPlotType = clips.Get("plottype", CliPlotHelpers.ParseGenerousTrajectoryPlotType, TrajectoryPlotType.Line);
+
+            string xAxisKey = clips.Get("xaxiskey", "x");
+            string yAxisKey = clips.Get("yaxiskey", "y");
+            string colorAxisKey = clips.Get("coloraxiskey", trajectoryPlotType == TrajectoryPlotType.Rectangles ? "color" : null);
+
+            int skip = start / samplePeriod;
+            int take = Math.Min((end + samplePeriod - start) / samplePeriod, trajectories[0].Length - skip);
+            trajectories = trajectories.Select(traj => traj.Skip(skip).Take(take).ToArray()).ToArray();
+
+            resample(ref trajectories, ref samplePeriod);
+
+            if (scaleFactor != 1.0)
+            {
+                for (int i = 0; i < trajectories.Length; i++)
+                    for (int j = 0; j < trajectories[i].Length; j++)
+                        trajectories[i][j] *= scaleFactor;
+            }
+
+            int startTime = start;
+
+            CliPlotHelpers.Keeper(clips)(ref trajectories, ref samplePeriod);
+
+            var strokeThickness = clips.Get("strokethickness", double.Parse, StrokeThickness);
+
+            bool logX = clips.Get("logX", bool.Parse, false);
+            bool logY = clips.Get("logY", bool.Parse, false);
+
+            // at this point, swap out samplePeriod for alternativeSamplePeriod (so that we plot as if it were whatever sample period we want, e.g. can plot epochs instead of generations or w/e)
+            int plotSamplePeriod = clips.Get("plotSamplePeriod", Misc.ParseInt, samplePeriod);
+
+            var tags = TrajectoryPlotting.Tags1D(trajectories.Length, i => i);
+            var colors = clips.Get("color", s => new ConstantColours(CliPlotHelpers.ParseColour(s)).Colours(trajectories.Length), Colors.Colours(trajectories.Length));
+
+            PlotModel plot;
+            if (clips.IsSet("annotationsOnly"))
+            {
+                plot = TrajectoryPlotting.PrepareTrajectoriesPlot(title, XLabel, null, null, ymin, ymax, xAxisKey, null, null, logX, logY, false);
+            }
+            else
+            {
+                if (trajectoryPlotType == TrajectoryPlotType.Rectangles)
+                {
+                    plot = TrajectoryPlotting.PlotTrajectories(title, XLabel, "Trait", YLabel, seriesName, trajectories, plotSamplePeriod, startTime, ymin, ymax, tags, colors, strokeThickness, resolution, trajectoryPlotType, xAxisKey, yAxisKey, colorAxisKey, logX, logY, true);
+                }
+                else
+                {
+                    plot = TrajectoryPlotting.PlotTrajectories(title, XLabel, YLabel, null, seriesName, trajectories, plotSamplePeriod, startTime, ymin, ymax, tags, colors, strokeThickness, resolution, trajectoryPlotType, xAxisKey, yAxisKey, null, logX, logY, false);
+                }
+            }
+
+            foreach (var annotater in TrajectoryAnnotaters)
+            {
+                if (clips.Get(annotater.Key, bool.Parse, annotater.Default))
+                    annotater.AnnotateTrajectories(console, plot, clips, trajectories, plotSamplePeriod, startTime, xAxisKey, yAxisKey);
+            }
+
+            return plot;
+        }
+    }
+
+    public interface ITrajectoryTags
+    {
+        object[] Tags(int n);
+    }
+
+    public class TrajectoryTags1D : ITrajectoryTags
+    {
+        public TrajectoryTags1D(Func<int, object> tagger)
+        {
+            Tagger = tagger;
+        }
+
+        public Func<int, object> Tagger { get; }
+
+        public object[] Tags(int n)
+        {
+            return TrajectoryPlotting.Tags1D(n, Tagger);
+        }
+    }
+
+    public class TrajectoryTags2D : ITrajectoryTags
+    {
+        public TrajectoryTags2D(Func<int, int, object> tagger)
+        {
+            Tagger = tagger;
+        }
+
+        public Func<int, int, object> Tagger { get; }
+
+        public object[] Tags(int n)
+        {
+            return TrajectoryPlotting.Tags2D(n, Tagger);
+        }
+    }
+
+    public interface ITrajectoryColours
+    {
+        OxyColor[] Colours(int n);
+    }
+
+    public class ConstantColours : ITrajectoryColours
+    {
+        public ConstantColours(OxyColor colour)
+        {
+            Colour = colour;
+        }
+
+        public OxyColor Colour { get; }
+
+        public OxyColor[] Colours(int n)
+        {
+            return Misc.Create(n, i => Colour);
+        }
+    }
+
+    public class TrajectoryColours1D : ITrajectoryColours
+    {
+        public TrajectoryColours1D(OxyColor? c0, OxyColor? c1)
+        {
+            C0 = c0;
+            C1 = c1;
+        }
+
+        public OxyColor? C0 { get; }
+        public OxyColor? C1 { get; }
+
+        public OxyColor[] Colours(int n)
+        {
+            return TrajectoryPlotting.Colours1D(n, C0, C1);
+        }
+    }
+
+    public class TrajectoryColours2D : ITrajectoryColours
+    {
+        public TrajectoryColours2D(OxyColor c00, OxyColor c10, OxyColor c01, OxyColor c11)
+        {
+            C00 = c00;
+            C10 = c10;
+            C01 = c01;
+            C11 = c11;
+        }
+
+        public OxyColor C00 { get; }
+        public OxyColor C10 { get; }
+        public OxyColor C01 { get; }
+        public OxyColor C11 { get; }
+
+        public OxyColor[] Colours(int n)
+        {
+            n = (int)Math.Round(Math.Sqrt(n));
+            return TrajectoryPlotting.Colours2D(n, C00, C10, C01, C11);
+        }
+    }
+
+    public class CliGenomePlotter : ICliPlotter
+    {
+        public string Prefix => "genome";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Plots genomes.");
+            }
+
+            var genome = Analysis.LoadGenome(filename);
+            return PlotDenseGenome(console, clips, genome, title, null);
+        }
+
+        public static PlotModel PlotDenseGenome(TextWriter console, CliParams clips, DenseGenome genome, string title, ExperimentConfiguration config)
+        {
+            var thing = clips.Get("thing", "genome");
+
+            if (thing == "genome" || thing == "dtm")
+            {
+                return PlotDtm(clips, genome.TransMat, title ?? "Regulatory Matrix");
+            }
+            else if (thing == "is")
+            {
+                var configFile = clips.Get("config", clips.Get("expfile", null));
+                config = configFile == null ? config : CliPlotHelpers.LoadExperimentConfig(configFile);
+
+                return PlotExpressionVector(clips, genome.InitialState, title ?? "Initial Trait Expression", config);
+            }
+            else if (thing == "ps")
+            {   
+                var configFile = clips.Get("config", clips.Get("expfile", null));
+                config = configFile == null ? config : CliPlotHelpers.LoadExperimentConfig(configFile);
+
+                int seed = clips.GetOrCreate("seed", int.Parse, () => CliExp.SeedSource.Next());
+                var rand = new MathNet.Numerics.Random.MersenneTwister(seed);
+                var ctx = new ModelExecutionContext(rand);
+
+                var p = genome.Develop(ctx, config.DevelopmentRules);
+                
+                return PlotExpressionVector(clips, p.Vector, title ?? "Phenotypic Trait Expression", config);
+            }
+            else if (thing == "dev")
+            {
+                var expFile = clips.Get("expfile", null);
+                var configFile = clips.Get("config", null);
+                
+                if (expFile != null)
+                {
+                    config = PopulationExperiment<DenseIndividual>.Load(expFile).PopulationConfig.ExperimentConfiguration;
+                }
+                else if (configFile != null)
+                {
+                    var cfg = M4M.State.GraphSerialisation.Read<object>(configFile);
+
+                    if (cfg is PopulationExperimentConfig<DenseIndividual> pec)
+                        config = pec.ExperimentConfiguration;
+                    else if (cfg is ExperimentConfiguration ec)
+                        config = ec;
+                    else
+                    {
+                        throw new Exception("Config must be dense");
+                    }
+                }
+                else if (config == null)
+                {
+                    throw new Exception("Development trajectories require an expfile or configfile");
+                }
+                
+                int seed = clips.GetOrCreate("seed", int.Parse, () => CliExp.SeedSource.Next());
+
+                var rand = new MathNet.Numerics.Random.MersenneTwister(seed);
+                return PlotDevelopment(console, clips, genome, config.DevelopmentRules, rand, title ?? "Developmental Trajectories");
+            }
+            else if (thing == "biasvector")
+            {
+                var configFile = clips.Get("config", clips.Get("expfile", null));
+                config = configFile == null ? config : CliPlotHelpers.LoadExperimentConfig(configFile);
+
+                if (genome.BiasVector == null)
+                    throw new Exception("No bias vector");
+
+                return PlotExpressionVector(clips, genome.BiasVector, title ?? "Bias Vector", config);
+            }
+            else
+            {
+                throw new Exception("Not sure what a '" + thing + "' is, try one of these:\n" +
+                    " - dtm\n" +
+                    " - is\n" +
+                    " - ps\n" +
+                    " - dev\n" +
+                    " - biasvector");
+            }
+        }
+
+        public static PlotModel PlotDtm(CliParams clips, MathNet.Numerics.LinearAlgebra.Matrix<double> transMat, string title)
+        {
+            double? min = clips.Get("min", s => (double?)double.Parse(s), null);
+            double? max = clips.Get("max", s => (double?)double.Parse(s), null);
+
+            var plot = GenomePlotting.PlotDtm(title, transMat, min, max);
+
+            return plot;
+        }
+
+        public static PlotModel PlotExpressionVector(CliParams clips, MathNet.Numerics.LinearAlgebra.Vector<double> expressionVector, string title, ExperimentConfiguration config)
+        {
+            if (clips.IsSet("sudoku"))
+            {
+                var sudokuPlot = PlotSudokuSolution(expressionVector, config?.Targets[0] as CorrelationMatrixTarget);
+                sudokuPlot.Title = clips.Get("title", sudokuPlot.Title);
+                return sudokuPlot;
+            }
+
+            double? min = clips.Get("min", s => (double?)double.Parse(s), null);
+            double? max = clips.Get("max", s => (double?)double.Parse(s), null);
+
+            int sqrt = (int)Math.Round(Math.Sqrt((double)expressionVector.Count));
+            int moduleCount = clips.Get("moduleCount", int.Parse, sqrt * sqrt == expressionVector.Count ? sqrt : 1);
+
+            var plot = GenomePlotting.PlotExpressionVector(title, expressionVector, moduleCount, min, max);
+
+            // TODO: find a better place to put this
+            if (clips.IsSet("localMatrixBoundryAnnotation"))
+            {
+                if (config == null)
+                    throw new ArgumentException(nameof(config), "Config must be provided to plot localMatrixBoundryAnnotations");
+
+                var localMatrixBoundryAnnotationColour = clips.Get("localMatrixBoundryAnnotation", s => s == null ? OxyColors.Red : CliPlotHelpers.ParseColour(s));
+                var target = CliPlotHelpers.UnwrapTarget(config.Targets[0]);
+
+                if (target is Epistatics.CorrelationMatrixTarget cmt)
+                {
+                    var mat = cmt.CorrelationMatrix;
+                    var mih = new MatrixIndexHelper(moduleCount, moduleCount); // assume square
+
+                    foreach (var unhappy in Epistatics.StandardCorrelationMatrixTargets.EnumerateUnhappyCorrelations(mat, expressionVector))
+                    {
+                        int i = unhappy.Row;
+                        int j = unhappy.Col;
+
+                        mih.ToRowCol(i, out int r, out int c);
+                        mih.ToRowCol(j, out int r2, out int c2);
+                        int dr = r2 - r;
+                        int dc = c2 - c;
+
+                        double x0 = c + dc / 2.0 + Math.Abs(dr) * -0.5;
+                        double x1 = c + dc / 2.0 + Math.Abs(dr) * 0.5;
+                        double y0 = r + dr / 2.0 + Math.Abs(dc) * -0.5;
+                        double y1 = r + dr / 2.0 + Math.Abs(dc) * 0.5;
+
+                        var la = new PolylineAnnotation();
+                        la.Color = localMatrixBoundryAnnotationColour;
+                        la.LineStyle = LineStyle.Solid;
+                        la.Points.Add(new DataPoint(x0, y0));
+                        la.Points.Add(new DataPoint(x1, y1));
+                        la.StrokeThickness = 2;
+                        plot.Annotations.Add(la);
+                    }
+                }
+                else
+                {
+                    throw new ArgumentException(nameof(config), "Config must contain a single CorrelationMatrixTarget target");
+                }
+            }
+
+            return plot;
+        }
+
+        public static PlotModel PlotSudokuSolution(MathNet.Numerics.LinearAlgebra.Vector<double> expressionVector, CorrelationMatrixTarget target)
+        {
+            var solution = Epistatics.Sudoku.RenderSolution(expressionVector, out int n);
+            var partialSolution = Epistatics.Sudoku.RenderSolution(target?.ForcingVector ?? CreateVector.Dense<double>(solution.Length, 0), out _);
+            for (int r = 0; r < n; r++)
+            {
+                for (int c = 0; c < n; c++)
+                {
+                    Console.Write(solution[r, c] + 1);
+                }
+                Console.WriteLine();
+            }
+
+            var conflicts = Epistatics.Sudoku.LocateConflicts(solution);
+
+            var conflictCount = new int[n, n];
+            foreach (var conflict in conflicts)
+            {
+                foreach (var mea in conflict.Entries)
+                {
+                    conflictCount[mea.Row, mea.Col]++;
+                }
+            }
+
+            var plot = new PlotModel() { Title = "Sudoku" };
+
+            var caxis = new OxyPlot.Axes.LinearAxis() { Title = "Col", Position = OxyPlot.Axes.AxisPosition.Bottom, MinimumMajorStep = 1, MinimumMinorStep = 1, Key = "col", MinimumPadding  = 0.01, MaximumPadding = 0.01 };
+            var raxis = new OxyPlot.Axes.LinearAxis() { Title = "Row", Position = OxyPlot.Axes.AxisPosition.Left, StartPosition = 1, EndPosition = 0, MinimumMajorStep = 1, MinimumMinorStep = 1, Key = "row", MinimumPadding = 0.01, MaximumPadding = 0.01 };
+            var coloraxis = new OxyPlot.Axes.LinearColorAxis() { Title = "Conflicts", Position = OxyPlot.Axes.AxisPosition.Right, Palette = OxyPalette.Interpolate(100, OxyColors.White, OxyColors.Red), Key = "color" };
+
+            plot.Axes.Add(caxis);
+            plot.Axes.Add(raxis);
+            plot.Axes.Add(coloraxis);
+
+            var rs = new RectangleSeries();
+            rs.CanTrackerInterpolatePoints = false;
+
+            for (int r = 0; r < n; r++)
+            {
+                for (int c = 0; c < n; c++)
+                {
+                    var rect = new RectangleItem(c - 0.5, c + 0.5, r - 0.5, r + 0.5, conflictCount[r, c]);
+                    rs.Items.Add(rect);
+
+                    var labelColor = target != null && partialSolution[r, c] >= 0 ? (partialSolution[r, c] == solution[r, c] ? OxyColors.Blue : OxyColors.Brown) : OxyColors.Black;
+                    plot.Annotations.Add(new TextAnnotation() { TextPosition = new DataPoint(c, r), Text = ""+(solution[r, c] + 1), Layer = AnnotationLayer.AboveSeries, TextColor = labelColor, TextVerticalAlignment = VerticalAlignment.Middle, TextHorizontalAlignment = HorizontalAlignment.Center, StrokeThickness = 0 });
+                }
+            }
+
+            int subn = (int)Math.Floor(Math.Sqrt(n));
+
+            for (int r = 0; r <= subn; r++)
+            {
+                plot.Annotations.Add(new LineAnnotation() { Y = r * subn - 0.5, Type = LineAnnotationType.Horizontal, MinimumX = -0.5, MaximumX = n - 0.5, LineStyle = LineStyle.Solid, Color = OxyColors.Black, StrokeThickness = r == 0 || r == subn ? 2 : 1 });
+                plot.Annotations.Add(new LineAnnotation() { X = r * subn - 0.5, Type = LineAnnotationType.Vertical, MinimumY = -0.5, MaximumY = n - 0.5, LineStyle = LineStyle.Solid, Color = OxyColors.Black, StrokeThickness = r == 0 || r == subn ? 2 : 1 });
+            }
+
+            plot.PlotAreaBorderColor = OxyColors.Transparent;
+            plot.Series.Add(rs);
+
+            return plot;
+        }
+
+        public static PlotModel PlotDevelopment(TextWriter console, CliParams clips, DenseGenome genome, DevelopmentRules drules, MathNet.Numerics.Random.RandomSource rand, string title)
+        {
+            double? min = clips.Get("min", s => (double?)double.Parse(s), null);
+            double? max = clips.Get("max", s => (double?)double.Parse(s), null);
+            
+            string xAxisKey = clips.Get("xaxiskey", "x");
+            string yAxisKey = clips.Get("yaxiskey", "y");
+
+            double[][] trajectories = null;
+            genome.DevelopWithTrajectories(rand, drules, ref trajectories);
+
+            // this is NOT the job of a CliPlotter... but oh well
+            if (clips.Get("dtd", bool.Parse, true))
+            {
+                string modulesstring = clips.Get("modulesstring", null);
+
+                int[][] mts;
+
+                if (modulesstring == null)
+                {
+                    int N = trajectories.Length;
+                    int moduleSize = clips.Get("modulesize", int.Parse, (int)Math.Sqrt(N));
+                    int moduleCount = N / moduleSize;
+
+                    console.WriteLine("Assuming " + moduleCount + " block modules of size " + moduleSize + " for DTD (qualify modulesize or modulesstring)");
+                    mts = Analysis.BlockModuleTraits(moduleSize, moduleCount);
+                }
+                else
+                {
+                    mts = Modular.MultiModulesStringParser.Instance.Parse(modulesstring).ToArray();
+                }
+
+                var dtd = Analysis.DetectDevTimeDominance(trajectories, mts);
+                console.WriteLine(String.Join(", ", dtd));
+            }
+
+            if (clips.IsSet("keep"))
+            {
+                IEnumerable<int> keep = clips.Get("keep", CliPlotHelpers.ParseIntList);
+                trajectories = CliPlotHelpers.KeepLeaveNull(trajectories, keep);
+            }
+
+            bool logX = clips.Get("logX", bool.Parse, false);
+            bool logY = clips.Get("logY", bool.Parse, false);
+
+            var plot = TrajectoryPlotting.PlotTrajectories(title, "Developmental Time", "Trait Expression", null, "Trait Expression", trajectories, 1, 0, min, max, (OxyColor?)null, null, 2, 1, TrajectoryPlotType.Line, xAxisKey, yAxisKey, null, logX, logY, false);
+
+            return plot;
+        }
+    }
+
+    public class CliWholeSamplePlotter : ICliPlotter
+    {
+        public CliWholeSamplePlotter(CliTraceePlotter traceePlotter)
+        {
+            TraceePlotter = traceePlotter;
+        }
+
+        public CliTraceePlotter TraceePlotter { get; }
+
+        public string Prefix => "wholesample";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Plots wholesamples");
+            }
+
+            List<WholeSample<DenseIndividual>> wholeSamples;
+            if (filename.Contains(";"))
+            {
+                wholeSamples = WholeSample<DenseIndividual>.EnumerateWholeSamplesSeries2(filename.Split(';')).ToList();
+            }
+            else
+            {
+                wholeSamples = WholeSample<DenseIndividual>.LoadWholeSamples(filename);
+            }
+
+            if (clips.IsSet("epoch"))
+            {
+                // plot last genome in epoch
+                int epoch = clips.Get("epoch", Misc.ParseInt);
+
+                var samples = wholeSamples.Where(ws => ws.Epoch == epoch).ToArray();
+                
+                var individual = samples.Last().Judgements.First().Individual;
+                
+                var thing = clips.Get("thing", null);
+
+                if (thing == "ps")
+                { // we use the known ps instead of generating one
+                    var configFile = clips.Get("config", clips.Get("expfile", null));
+                    var config = configFile == null ? null : CliPlotHelpers.LoadExperimentConfig(configFile);
+
+                    return CliGenomePlotter.PlotExpressionVector(clips, individual.Phenotype.Vector, title ?? "Phenotypic Expression", config);
+                }
+                else
+                {
+                    return CliGenomePlotter.PlotDenseGenome(console, clips, individual.Genome, title, null);
+                }
+            }
+            else
+            {
+                TraceInfo<DenseIndividual> traceInfo = new TraceInfo<DenseIndividual>(wholeSamples.ToList());
+                return TraceePlotter.PlotTracee(console, clips, traceInfo, title, true);
+            }
+        }
+    }
+
+    public class CliExpPlotter : ICliPlotter
+    {
+        public string Prefix => "epoch";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var exp = PopulationExperiment<DenseIndividual>.Load(filename);
+
+            if (clips.IsSet("ma"))
+            {
+                if (clips.IsSet("help"))
+                {
+                    console.WriteLine("Plots fitness variation for mutants\n" +
+                        " - mutantCount, the number of mutatnts to generate (default 10000)\n" +
+                        " - binCount, the number of bins in the histogram (default 100)\n" +
+                        " - min and max, min and max of the histogram x axis\n" +
+                        " - seed, the random seed\n" +
+                        " - target, the target to use\n" +
+                        " - resetTarget, whether to reset the target before starting (default false)");
+                }
+
+                int mutantCount = clips.Get("mutantCount", Misc.ParseInt, 10000);
+
+                int binCount = clips.Get("binCount", int.Parse, 100);
+    
+                int seed = clips.GetOrCreate("seed", int.Parse, () => CliExp.SeedSource.Next());
+                var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+                var context = new ModelExecutionContext(rand);
+                var config = exp.PopulationConfig.ExperimentConfiguration;
+
+                string targetName = clips.Get("target", null);
+                var target = targetName != null
+                    ? config.Targets.FirstOrDefault(t => t.FriendlyName == targetName)
+                    : config.Targets.First();
+                
+                if (targetName == null)
+                {
+                    console.WriteLine("Assuming target in position 0. Available targets are:");
+                    foreach (var t in exp.PopulationConfig.ExperimentConfiguration.Targets)
+                        console.WriteLine(t.FriendlyName + " (" + t.FullName + ")");
+                }
+
+                if (clips.Get("resetTarget", bool.Parse, false))
+                {
+                    int exposureEpoch = clips.Get("exposureepoch", Misc.ParseInt, 1);
+
+                    ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                    target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+                }
+                
+                target.NextGeneration(rand, config.JudgementRules);
+                
+                var ma = new MutantAnalysis<DenseGenome>(exp.Population.PeekAll()[0].Genome, mutantCount, context, config.ReproductionRules, config.DevelopmentRules, config.JudgementRules, target);
+                
+                double min = clips.Get("min", double.Parse, ma.FitnessChanges.Min());
+                double max = clips.Get("max", double.Parse, ma.FitnessChanges.Max());
+
+                // TODO: this should be in its own method in Potting
+                PlotModel plot = new PlotModel() { Title = "Mutation Fitness Variation" };
+                plot.Axes.Add(new OxyPlot.Axes.LinearAxis() { Title = "Fitness Delta", Key = "x", Position = OxyPlot.Axes.AxisPosition.Bottom });
+                plot.Axes.Add(new OxyPlot.Axes.LinearAxis() { Title = "Frequency", Key = "y", Position = OxyPlot.Axes.AxisPosition.Left });
+
+                OxyPlot.Series.HistogramSeries hs = new OxyPlot.Series.HistogramSeries() { Title = "Mutation Benefit Variation" };
+                var bins = HistogramHelpers.Collect(ma.FitnessChanges, HistogramHelpers.CreateUniformBins(min, max, binCount), new BinningOptions(BinningOutlierMode.RejectOutliers, BinningIntervalType.InclusiveLowerBound, BinningExtremeValueMode.IncludeExtremeValues)).ToArray();
+                hs.ItemsSource = bins;
+                console.WriteLine("Bin width: " + bins[0].Width);
+                plot.Series.Add(hs);
+
+                return plot;
+            }
+            else if (clips.IsSet("target"))
+            {
+                string targetName = clips.Get("target");
+                var target = exp.PopulationConfig.ExperimentConfiguration.Targets.FirstOrDefault(t => t.FriendlyName == targetName);
+
+                if (target == null)
+                {
+                    console.WriteLine("Available targets are:");
+                    foreach (var t in exp.PopulationConfig.ExperimentConfiguration.Targets)
+                        console.WriteLine(t.FriendlyName + " (" + t.FullName + ")");
+                    return null;
+                }
+
+                while (target is ITargetDecorator decorator)
+                    target = decorator.Target;
+
+                if (target is VectorTarget vectorTarget)
+                {
+                    int moduleCount = clips.Get("modulecount", int.Parse, (int)Math.Sqrt(target.Size));
+                    return PlotTraitVector(clips, vectorTarget.Vector, moduleCount, title);
+                }
+                else if (target is Epistatics.CorrelationMatrixTarget correlTarget)
+                {
+                    string thing = clips.Get("thing", "mat");
+
+                    if (thing == "mat")
+                    {
+                        return PlotCorrelationMatrix(clips, correlTarget.CorrelationMatrix, title);
+                    }
+                    else if (thing == "forcingvector")
+                    {
+                        return CliGenomePlotter.PlotExpressionVector(clips, correlTarget.ForcingVector, title ?? "Forcing", exp.PopulationConfig.ExperimentConfiguration);
+                    }
+                    else
+                    {
+                        throw new Exception("Not sure what a '" + thing + "' is, try one of these:\n" +
+                            " - mat\n" +
+                            " - forcingvector");
+                    }
+                }
+            }
+            else if (clips.IsSet("genome"))
+            {
+                return CliGenomePlotter.PlotDenseGenome(console, clips, exp.Population.PeekAll()[0].Genome, title, exp.PopulationConfig.ExperimentConfiguration);
+            }
+            else
+            {
+                console.WriteLine("Defaulting to genome plot; options are:\n" +
+                    " - target\n" +
+                    " - ma\n" +
+                    " - genome");
+                return CliGenomePlotter.PlotDenseGenome(console, clips, exp.Population.PeekAll()[0].Genome, title, exp.PopulationConfig.ExperimentConfiguration);
+            }
+
+            console.WriteLine("No idea what to do. Sorry this message is so unhelpful.");
+            return null;
+        }
+
+        public static PlotModel PlotTraitVector(CliParams clips, MathNet.Numerics.LinearAlgebra.Vector<double> expressionVector, int moduleCount, string title)
+        {
+            double? min = clips.Get("min", s => (double?)double.Parse(s), null);
+            double? max = clips.Get("max", s => (double?)double.Parse(s), null);
+
+            var plot = GenomePlotting.PlotExpressionVector(title, expressionVector, moduleCount, min, max);
+
+            return plot;
+        }
+
+        public static PlotModel PlotCorrelationMatrix(CliParams clips, MathNet.Numerics.LinearAlgebra.Matrix<double> correlationMatrix, string title)
+        {
+            double? min = clips.Get("min", s => (double?)double.Parse(s), null);
+            double? max = clips.Get("max", s => (double?)double.Parse(s), null);
+
+            var plot = GenomePlotting.PlotDtm(title, correlationMatrix, min, max);
+
+            return plot;
+        }
+    }
+
+    public delegate void TrajectoryResampler(ref double[][] trajs, ref int samplePeriod);
+
+    public static class CliPlotHelpers
+    {
+        public static IEnumerable<double> LinearSamples(double min, double max, int resolution)
+        {
+            if (resolution == 0)
+            {
+                yield return min;
+                yield break;
+            }
+
+            for (int i = 0; i <= resolution; i++)
+            {
+                var x = (double)i / resolution;
+                yield return min * (1 - x) + max * x;
+            }
+        }
+
+        public static RandomSource ParseRand(CliParams clips, int? defaultSeed = null)
+        {
+            if (clips.IsSet("seed"))
+            {
+                int seed = clips.Get("seed", int.Parse);
+                return new CustomMersenneTwister(seed);
+            }
+            else if (defaultSeed.HasValue)
+            {
+                return new CustomMersenneTwister(defaultSeed.Value);
+            }
+            else
+            {
+                return new CustomMersenneTwister(CliExp.SeedSource.Next());
+            }
+        }
+
+        public static ITarget ParseTarget(TextWriter console, CliParams clips, IReadOnlyList<ITarget> targets, ITarget defaultTarget = null)
+        {
+            var targetString = clips.Get("target", null);
+            ITarget target = null;
+
+            if (targetString != null)
+            {
+                // by name
+                target = targets.FirstOrDefault(t => t.FriendlyName == targetString)
+                    ?? targets.FirstOrDefault(t => t.FullName == targetString);
+
+                // by idx
+                if (target is null && int.TryParse(targetString, out var targetIdx))
+                    target = targets[targetIdx];
+
+                // compose
+                if (targetString == "compose")
+                    target = ExperimentComposition.Typical.PrepareDefaultTargetPackageComposer().Compose(console, clips).Targets[0];
+            }
+
+            if (target is null)
+            {
+                // use default
+                console.WriteLine("Available targets are:");
+                for (int ti = 0; ti < targets.Count; ti++)
+                    console.WriteLine($" {ti}: {targets[ti].FullName}");
+
+                if (!(defaultTarget is null))
+                {
+                    console.WriteLine("Using default target " + defaultTarget.FullName);
+                    target = defaultTarget;
+                }
+                else
+                {
+                    throw new Exception("No default target");
+                }
+            }
+
+            return target;
+        }
+
+        public static void PrepTarget(RandomSource rand, CliParams clips, ExperimentConfiguration config, ITarget target, bool resetDefault, int defaultExposureEpoch = 1)
+        {
+            if (clips.Get("resetTarget", bool.Parse, resetDefault))
+            {
+                int exposureEpoch = clips.Get("exposureepoch", Misc.ParseInt, defaultExposureEpoch);
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+            }
+
+            target.NextGeneration(rand, config.JudgementRules);
+        }
+
+        public static OxyPlot.Axes.TickStyle ParseTickStyle(string str)
+        {
+            if (string.IsNullOrEmpty(str))
+            {
+                return OxyPlot.Axes.TickStyle.Outside;
+            }
+            else if (str.Equals("inside", StringComparison.CurrentCultureIgnoreCase))
+            { // don't use: if you want lines on the plot, use grid lines
+                return OxyPlot.Axes.TickStyle.Inside;
+            }
+            else if (str.Equals("outside", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.Axes.TickStyle.Outside;
+            }
+            else if (str.Equals("crossing", StringComparison.CurrentCultureIgnoreCase))
+            { // don't use
+                return OxyPlot.Axes.TickStyle.Crossing;
+            }
+            else if (str.Equals("none", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.Axes.TickStyle.None;
+            }
+
+            throw new ArgumentException("Unrecognised TickStyle string: " + str, nameof(str));
+        }
+
+        public static OxyPlot.LineStyle ParseLineStyle(string str)
+        {
+            if (string.IsNullOrEmpty(str))
+            {
+                return OxyPlot.LineStyle.None;
+            }
+            else if (str.Equals("none", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.LineStyle.None;
+            }
+            else if (str.Equals("solid", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.LineStyle.Solid;
+            }
+            else if (str.Equals("dash", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.LineStyle.Dash;
+            }
+            else if (str.Equals("dot", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.LineStyle.Dot;
+            }
+            else if (str.Equals("auto", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.LineStyle.Automatic;
+            }
+            else if (str.Equals("automatic", StringComparison.CurrentCultureIgnoreCase))
+            {
+                return OxyPlot.LineStyle.Automatic;
+            }
+
+            throw new ArgumentException("Unrecognised LineStyle string: " + str, nameof(str));
+        }
+
+        public static AxisPosition ParseAxisPosition(string str)
+        {
+            switch (str.ToLowerInvariant())
+            {
+                case "t":
+                case "top":
+                    return AxisPosition.Top;
+                case "b":
+                case "bottom":
+                    return AxisPosition.Bottom;
+                case "l":
+                case "left":
+                    return AxisPosition.Left;
+                case "r":
+                case "right":
+                    return AxisPosition.Right;
+                case "":
+                case "none":
+                    return AxisPosition.Right;
+                default:
+                    throw new ArgumentException("Unrecognised axis position: " + str);
+            }
+        }
+
+        private static Dictionary<string, OxyColor> NamedColourTable;
+
+        public static OxyColor ParseColour(string str)
+        {
+            if (str.StartsWith("magpie", StringComparison.InvariantCultureIgnoreCase))
+                return new[] { OxyColors.Blue, OxyColors.Green, OxyColors.Purple }[new Random().Next(3)];
+
+            if (str.StartsWith("#"))
+            {
+                if (str.Length == 1 + 1)
+                {
+                    var r = Convert.ToByte(str.Substring(1, 1), 16) * 16;
+                    return OxyColor.FromRgb((byte)r, (byte)r, (byte)r);
+                }
+                else if (str.Length == 3 + 1)
+                {
+                    var r = Convert.ToByte(str.Substring(1, 1), 16) * 16;
+                    var g = Convert.ToByte(str.Substring(2, 1), 16) * 16;
+                    var b = Convert.ToByte(str.Substring(3, 1), 16) * 16;
+                    return OxyColor.FromRgb((byte)r, (byte)g, (byte)b);
+                }
+                else if (str.Length == 6 + 1)
+                {
+                    var r = Convert.ToByte(str.Substring(1, 2), 16);
+                    var g = Convert.ToByte(str.Substring(3, 2), 16);
+                    var b = Convert.ToByte(str.Substring(5, 2), 16);
+                    return OxyColor.FromRgb(r, g, b);
+                }
+            }
+            else
+            {
+                if (NamedColourTable == null)
+                {
+                    NamedColourTable = new Dictionary<string, OxyColor>(StringComparer.InvariantCultureIgnoreCase);
+
+                    foreach (var cmi in typeof(OxyColors).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public))
+                    {
+                        NamedColourTable.Add(cmi.Name, (OxyColor)cmi.GetValue(null));
+                    }
+                    NamedColourTable.Add("crow", OxyColors.Black);
+                    NamedColourTable.Add("grey", OxyColors.Gray);
+                }
+
+                if (NamedColourTable.TryGetValue(str, out var named))
+                    return named;
+            }
+
+            throw new ArgumentException("Unrecognised colour string: " + str, nameof(str));
+        }
+
+        /// <summary>
+        /// Parses strings that look like 1.0;2;4.5;0.2 into a list of doubles
+        /// </summary>
+        public static IReadOnlyList<double> ParseDoubleList(string str)
+        {
+            return str.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(double.Parse).ToArray();
+        }
+
+        /// <summary>
+        /// Parses strings that look like 1-5,8,9-10 into a list of ints
+        /// Sorts the list, and throws if there are duplicates
+        /// </summary>
+        public static IReadOnlyList<int> ParseStrictIntList(string str)
+        {
+            var baseList = ParseIntList(str);
+            var uniques = baseList.OrderBy(x => x).Distinct().ToArray(); // by no means the most efficient mechanism immaginable
+            if (uniques.Length != baseList.Count)
+                throw new Exception("Duplicate entries in int-list: '" + str + "'");
+
+            return uniques;
+        }
+
+        /// <summary>
+        /// Parses strings that look like 1-5,8,9-10 into a list of ints
+        /// </summary>
+        public static IReadOnlyList<int> ParseIntList(string str)
+        {
+            string[] blocks = str.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
+
+            List<int> ints = new List<int>();
+            foreach (var block in blocks)
+            {
+                ints.AddRange(MaybeDash(block));
+            }
+
+            return ints;
+        }
+
+        private static IEnumerable<int> MaybeDash(string str)
+        {
+            if (str.Contains("-"))
+            {
+                string[] data = str.Split('-');
+                
+                if (data.Length != 2)
+                    throw new Exception("Cannot parse int list: '" + str + "'");
+
+                int l = int.Parse(data[0]);
+                int r = int.Parse(data[1]);
+                
+                if (l > r)
+                    throw new Exception("Cannot parse int list: '" + str + "'");
+
+                for (int i = l; i <= r; i++)
+                {
+                    yield return i;
+                }
+            }
+            else
+            {
+                yield return int.Parse(str);
+            }
+        }
+
+        public static TrajectoryPlotType ParseTrajectoryPlotType(string name)
+        {
+            if (string.Equals(name, "line", StringComparison.InvariantCultureIgnoreCase))
+                return TrajectoryPlotType.Line;
+            if (string.Equals(name, "scatter", StringComparison.InvariantCultureIgnoreCase))
+                return TrajectoryPlotType.Scatter;
+
+            throw new Exception("Unrecognised TrajectoryPlotType '" + name + "'. Please use 'line' or 'scatter'");
+        }
+
+        public static TrajectoryPlotType ParseGenerousTrajectoryPlotType(string name)
+        {
+            if (string.Equals(name, "line", StringComparison.InvariantCultureIgnoreCase))
+                return TrajectoryPlotType.Line;
+            if (string.Equals(name, "scatter", StringComparison.InvariantCultureIgnoreCase))
+                return TrajectoryPlotType.Scatter;
+            if (string.Equals(name, "rectangles", StringComparison.InvariantCultureIgnoreCase))
+                return TrajectoryPlotType.Rectangles;
+
+            throw new Exception("Unrecognised TrajectoryPlotType '" + name + "'. Please use 'line', 'scatter', or 'rectangles'");
+        }
+
+        public static MarkerType ParseMarkerType(string name)
+        {
+            if (name == null)
+                throw new ArgumentNullException(nameof(name));
+
+            if (name == "")
+                return MarkerType.None;
+            if (string.Equals(name, "none", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.None;
+
+            if (string.Equals(name, "triangle", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.Triangle;
+            if (name == "^")
+                return MarkerType.Triangle;
+
+            if (string.Equals(name, "diamond", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.Diamond;
+            if (name == "v")
+                return MarkerType.Diamond;
+                
+            if (string.Equals(name, "star", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.Star;
+            if (name == "*")
+                return MarkerType.Star;
+                
+            if (string.Equals(name, "circle", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.Circle;
+            if (name == "o")
+                return MarkerType.Circle;
+
+            if (string.Equals(name, "cross", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.Cross;
+            if (name == "x")
+                return MarkerType.Cross;
+
+            if (string.Equals(name, "plus", StringComparison.InvariantCultureIgnoreCase))
+                return MarkerType.Plus;
+            if (name == "+")
+                return MarkerType.Plus;
+
+            throw new Exception("Unrecognised MarkerType '" + name + "'.");
+        }
+
+        public static ExperimentConfiguration LoadExperimentConfig(string filename)
+        {
+            var cfg = M4M.State.GraphSerialisation.Read<object>(filename);
+
+            if (cfg is ExperimentConfiguration ec)
+                return ec;
+            else
+            { // try any type of experiment (may throw violently)
+                var expExtractor = ExperimentExtractorExperimentReceiver.LoadExtractorFromFile(filename);
+                return expExtractor.ExperimentConfiguration;
+            }
+        }
+
+        public static ExperimentConfiguration LoadDenseExperimentConfig(string filename)
+        {
+            var cfg = M4M.State.GraphSerialisation.Read<object>(filename);
+
+            if (cfg is PopulationExperiment<DenseIndividual> pe)
+                return pe.PopulationConfig.ExperimentConfiguration;
+            else if (cfg is PopulationExperimentConfig<DenseIndividual> pec)
+                return pec.ExperimentConfiguration;
+            else if (cfg is ExperimentConfiguration ec)
+                return ec;
+
+            throw new Exception("No dense experiment or config found in '" + filename + "'");
+        }
+
+        public static DenseGenome LoadDenseGenome(string filename)
+        {
+            try
+            {
+                if (System.IO.Path.GetFileName(filename).StartsWith("genome"))
+                {
+                    var genome = Analysis.LoadGenome(filename);
+                    return genome;
+                }
+            }
+            catch { }
+
+            var exp = M4M.State.GraphSerialisation.Read<object>(filename);
+
+            if (exp is PopulationExperiment<DenseIndividual> pe)
+                return pe.Population.ExtractAll()[0].Genome;
+
+            throw new Exception("No dense genome or experiment in '" + filename + "'");
+        }
+
+        /// <summary>
+        /// Returns a new array, where the non-kept elements have the default value (e.g. null)
+        /// </summary>
+        public static T[] KeepLeaveNull<T>(T[] arr, IEnumerable<int> indicies)
+        {
+            HashSet<int> keep = new HashSet<int>(indicies);
+
+            var kept = new T[arr.Length];
+            foreach (int k in keep)
+                kept[k] = arr[k];
+
+            return kept;
+        }
+
+        /// <summary>
+        /// Returns a new array, where the non-kept elements have the given nonValue
+        /// </summary>
+        public static T[] Keep<T>(T[] arr, IEnumerable<int> indicies, T nonValue)
+        {
+            HashSet<int> keep = new HashSet<int>(indicies);
+
+            var kept = new T[arr.Length];
+            for (int i = 0; i < arr.Length; i++)
+                kept[i] = keep.Contains(i) ? arr[i] : nonValue;
+
+            return kept;
+        }
+
+        public static Modular.Modules GuessModules(TextWriter console, CliParams clips, int size)
+        {
+            string modulesstring = clips.Get("modulesstring", null);
+
+            if (modulesstring == null)
+            {
+                int moduleSize = clips.Get("modulesize", int.Parse, (int)Math.Sqrt(size));
+                int moduleCount;
+
+                do
+                {
+                    moduleCount = size / moduleSize;
+                }
+                while (moduleSize * moduleCount != size && moduleSize-- > 0);
+
+                if (moduleSize * moduleCount != size || moduleSize == 0)
+                    throw new Exception($"Cannot guess modules for system of size {size}. \nYou must qualify modulesize or modulesstring.");
+
+                console?.WriteLine("Assuming " + moduleCount + " block modules of size " + moduleSize + " (qualify modulesize or modulesstring)");
+                return Modular.Modules.CreateBlockModules(moduleCount, moduleSize);
+            }
+            else
+            {
+                return Modular.MultiModulesStringParser.Instance.Parse(modulesstring);
+            }
+        }
+
+        public static TrajectoryResampler Keeper(CliParams clips)
+        {
+            var keep = clips.Get("keep", CliPlotHelpers.ParseIntList, null);
+
+            void resample(ref double[][] trajectories, ref int samplePeriod)
+            {
+                if (keep != null)
+                {
+                    trajectories = CliPlotHelpers.KeepLeaveNull(trajectories, keep);
+                }
+            }
+
+            return resample;
+        }
+
+        public static TrajectoryResampler DefaultResampler(CliParams clips)
+        {
+            // tolerates nulls, so we can 'keep' first
+
+            var sampleThreshold = clips.Get("sampleThreshold", double.Parse, double.NaN);
+            int movingAverageWindowRadius = clips.Get("maRadius", Misc.ParseInt, 0);
+            int meanDownsampleResolution = clips.Get("meanDownsample", Misc.ParseInt, 0);
+            int downsampleResolution = clips.Get("downsample", Misc.ParseInt, 0);
+            double sampleOffset = clips.Get("sampleOffset", double.Parse, 0);
+            double sampleScale = clips.Get("sampleScale", double.Parse, 1);
+
+            void resample(ref double[][] trajectories, ref int samplePeriod)
+            {
+                if (!double.IsNaN(sampleThreshold))
+                {
+                    trajectories = Misc.Threshold(trajectories, sampleThreshold, 0, 1);
+                }
+
+                if (movingAverageWindowRadius > 0)
+                {
+                    trajectories = Misc.MovingAverages(trajectories, movingAverageWindowRadius);
+                }
+
+                if (meanDownsampleResolution > 0)
+                {
+                    trajectories = Misc.MeanDownsample(trajectories, meanDownsampleResolution);
+                    samplePeriod *= meanDownsampleResolution;
+                }
+
+                if (downsampleResolution > 0)
+                {
+                    trajectories = Misc.Downsample(trajectories, downsampleResolution);
+                    samplePeriod *= downsampleResolution;
+                }
+
+                if (sampleScale != 0 || sampleScale != 1)
+                {
+                    trajectories = trajectories.Select(t => t.Select(s => s * sampleScale + sampleOffset).ToArray()).ToArray();
+                }
+            }
+
+            return resample;
+        }
+
+        public static IEnumerable<DataPoint> Reduce(IEnumerable<DataPoint> points, double threshold)
+        {
+            // this is terrible, but it does the job when I need it
+            bool first = true;
+            DataPoint last = default(DataPoint);
+            DataPoint prev = default(DataPoint);
+            double? lastAtan = null;
+            bool yieldedPrev = false;
+
+            foreach (var dp in points)
+            {
+                if (first)
+                {
+                    yield return dp;
+                    last = dp;
+                    prev = dp;
+                    first = false;
+                    continue;
+                }
+
+                var dx = dp.X - last.X;
+                var dy = dp.Y - last.Y;
+
+                var atan = Math.Atan2(dy, dx);
+                if (lastAtan.HasValue)
+                {
+                    var diff = Math.Abs(atan - lastAtan.Value);
+                    if (diff < threshold || diff > Math.PI * 2 - threshold)
+                    { // skip
+                        prev = dp;
+                        yieldedPrev = false;
+                        continue;
+                    }
+                }
+                
+                if (!yieldedPrev)
+                    yield return prev;
+                yield return dp;
+                last = dp;
+                prev = dp;
+                lastAtan = atan;
+                yieldedPrev = true;
+                continue;
+            }
+
+            if (!yieldedPrev)
+                yield return prev;
+        }
+
+        public static ITarget UnwrapTargetOnce(ITarget target)
+        {
+            if (target is ITargetDecorator decorator)
+                target = decorator.Target;
+
+            return target;
+        }
+
+        public static ITarget UnwrapTarget(ITarget target)
+        {
+            ITarget inner = null;
+            while (inner != target)
+            {
+                inner = UnwrapTargetOnce(target);
+            }
+
+            return inner;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliPlotOther.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliPlotOther.cs
new file mode 100644
index 0000000..86d26ac
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliPlotOther.cs
@@ -0,0 +1,2376 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using MathNet.Numerics.LinearAlgebra;
+using MathNet.Numerics.LinearAlgebra.Complex;
+using OxyPlot;
+using OxyPlot.Annotations;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public interface ICliMiscPlotter
+    {
+        string Key { get; }
+        PlotModel Plot(TextWriter console, CliParams clips, string title);
+    }
+
+    public class CliPlotMisc : ICliPlotter
+    {
+        public readonly static ICliMiscPlotter[] DefaultMiscPlotters = new ICliMiscPlotter[] {
+            new CliMiscPlotPitOfDespair(),
+            new CliMiscPlotDevTraj(),
+            new CliMiscPlotBeam(),
+            new CliMiscPlotExpBeam(),
+            new CliMiscPlotExpsBeam(),
+            new CliMiscPlotIvmcSwitchness(),
+            new CliMiscPlotIvmcProperSwitchness(),
+            new CliMiscPlotIvmcModel(),
+            new CliMiscPlotCbbnkStepModel(),
+            CliMiscEtaThings.IvmcProper,
+            CliMiscEtaThings.IvmcSplit,
+            CliMiscEtaEffective.IvmcProper,
+            CliMiscEtaEffective.IvmcSplit,
+            new CliMiscTransition(),
+            new CliMiscMcPhenotypeTransition(),
+            new CliTopologyOptimisation(),
+            new CliQ(),
+            new CliSaturationAnalysis(),
+            new CliModularDevelopment(),
+            new CliHi(),
+            new CliSquash(),
+            new CliCoins(),
+        };
+
+        public CliPlotMisc(IEnumerable<ICliMiscPlotter> miscPlotters)
+        {
+            foreach (var miscPlotter in miscPlotters)
+                Table.Add(miscPlotter.Key, miscPlotter);
+        }
+
+        private Dictionary<string, ICliMiscPlotter> Table { get; } = new Dictionary<string, ICliMiscPlotter>(StringComparer.InvariantCultureIgnoreCase);
+
+        public string Prefix => "misc";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string filename, string title)
+        {
+            var misc = clips.Get("misc");
+
+            if (Table.TryGetValue(misc, out var plotter))
+            {
+                return plotter.Plot(console, clips, title);
+            }
+
+            console.WriteLine("Available misc plotters are:");
+            console.WriteLine(string.Join(", ", Table.Keys));
+            
+            throw new Exception("Unrecognised misc type: " + misc);
+        }
+    }
+
+    public static class EtaEpistatics
+    {
+        public static Func<double, double> IvmcProperη(CliParams clips)
+        {
+            double cp = clips.Get("cp", double.Parse, 1.0);
+            double cn = clips.Get("cn", double.Parse, 0.7);
+
+            return IvmcProperη(cp, cn);
+        }
+
+        public static Func<double, double> IvmcProperη(double cp, double cn)
+        {
+            return x =>
+            {
+                double moduleFitness = 0.0;
+
+                double positiveness = 0.5 + x * 0.5;
+                moduleFitness += positiveness * positiveness * cp;
+                
+                double negativeness = 0.5 - x * 0.5;
+                moduleFitness += negativeness * negativeness * cn;
+
+                return moduleFitness;
+            };
+        }
+        
+        public static Func<double, double> IvmcSplitη(CliParams clips)
+        {
+            double cp = clips.Get("cp", double.Parse, 1.0);
+            double cn = clips.Get("cn", double.Parse, 0.7);
+
+            return IvmcSplitη(cp, cn);
+        }
+
+        public static Func<double, double> IvmcSplitη(double cp, double cn)
+        {
+            return x =>
+            {
+                if (x > 0)
+                    return x * cp;
+                else
+                    return x * -cn;
+            };
+        }
+    }
+    
+    public class CliMiscEtaThings : ICliMiscPlotter
+    {
+        public static CliMiscEtaThings IvmcProper = new CliMiscEtaThings("IvmcThing", "Phenotype Fitness", EtaEpistatics.IvmcProperη);
+        public static CliMiscEtaThings IvmcSplit = new CliMiscEtaThings("IvmcSplitThing", "Phenotype Fitness", EtaEpistatics.IvmcSplitη);
+
+        public CliMiscEtaThings(string key, string title, Func<CliParams, Func<double, double>> ηSource)
+        {
+            Key = key;
+            Title = title;
+            this.ηSource = ηSource;
+        }
+
+        public string Key { get; }
+        public string Title { get; }
+        public Func<CliParams, Func<double, double>> ηSource { get; }
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            bool diff = clips.IsSet("diff");
+
+            var model = new PlotModel { Title = title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Module Sum", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = diff ? "Fitness" : "Fitness", Key = "y" });
+            
+            int N = clips.Get("n", int.Parse, 4);
+            
+            double totalWeight=clips.Get("weight", double.Parse, N * N);
+            double strokeThickness = clips.Get("strokeThickness", double.Parse, 2.0);
+            
+            double xmin=clips.Get("xmin", double.Parse, -1);
+            double xmax=clips.Get("xmax", double.Parse, +1);
+            double ymin=clips.Get("ymin", double.Parse, 0);
+            double ymax=clips.Get("ymax", double.Parse, 1);
+            model.Axes[1].Minimum = ymin;
+            model.Axes[1].Maximum = ymax;
+            
+            var η = ηSource(clips);
+            
+            var mainCurve = new FunctionSeries(x => η(x), xmin, xmax, 1000) { RenderInLegend = false, StrokeThickness = strokeThickness };
+            model.Series.Add(mainCurve);
+            
+            int T = clips.Get("T", int.Parse, 10);
+            double tau = clips.Get("tau", double.Parse, 0.2);
+            var context = new ModelExecutionContext(new MathNet.Numerics.Random.MersenneTwister(0, false));
+            var drules = new DevelopmentRules(T, 1.0, tau, DevelopmentRules.TanhHalf);
+            
+            if (N > 0)
+            {
+                Linear.Matrix<double> transMat;
+
+                string genome = clips.Get("genome", null);
+
+                if (genome != null)
+                {
+                    var g = Analysis.LoadGenome(genome);
+                    transMat = g.TransMat;
+                }
+                else
+                {
+                    string dtm = clips.Get("dtm");
+                    Func<double, Linear.Matrix<double>> createDtm = GenomeHelpers.PrepareDtmCreater(dtm, N);
+                    transMat = createDtm(totalWeight);
+                }
+                
+                // judges the initialState given transMat
+                double judge(Linear.Vector<double> initialState, out double moduleMean)
+                {
+                    var g = new DenseGenome(initialState, transMat);
+                    moduleMean = g.Develop(context, drules).Vector.Sum() / N;
+                    var fitness = η(moduleMean);
+                    return fitness;
+                }
+                
+                var pointSeries = new ScatterSeries() { RenderInLegend = false, MarkerType = MarkerType.Circle, MarkerFill = OxyColors.Transparent, MarkerStroke = OxyColors.SteelBlue };
+                foreach (var initialState in GenomeHelpers.EnumerateAllBinary(N))
+                {
+                    var fitness = judge(initialState, out var moduleMean);
+                    pointSeries.Points.Add(new ScatterPoint(moduleMean, fitness));
+                }
+                model.Series.Add(pointSeries);
+
+                // epistasis table, -1 -> +1 at (-1)
+                if (clips.IsSet("et"))
+                {
+                    // creates a vector of length N containing all -1, except for the given entries, where it is +1
+                    Linear.Vector<double> m1Vector(params int[] entries)
+                    {
+                        var v = Linear.CreateVector.Dense(N, -1.0);
+                        foreach (var e in entries)
+                            v[e] = 1.0;
+                        return v;
+                    }
+                    
+                    double f(Linear.Vector<double> initialState) => judge(initialState, out var _);
+
+                    Linear.Matrix<double> epistasisTable = Linear.CreateMatrix.Dense(N, N, (a, b) =>
+                    {
+                        if (a == b)
+                            return 0;
+
+                        var ab = m1Vector();
+                        var Ab = m1Vector(a);
+                        var aB = m1Vector(b);
+                        var AB = m1Vector(a, b);
+                        
+                        return f(AB) - f(Ab) - f(aB) + f(ab);
+                    });
+
+                    console.WriteLine(epistasisTable.ToString());
+                }
+            }
+
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = 0.0, Color = OxyColors.Gray, Layer = OxyPlot.Annotations.AnnotationLayer.BelowSeries });
+
+            if (ymin < 0)
+            {
+                model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = 0.0, Color = OxyColors.Gray, Layer = OxyPlot.Annotations.AnnotationLayer.BelowSeries });
+            }
+            
+            return model;
+        }
+    }
+    
+    public class CliMiscEtaEffective : ICliMiscPlotter
+    {
+        public static CliMiscEtaEffective IvmcProper = new CliMiscEtaEffective("IvmcEffective", "Phenotype Fitness", EtaEpistatics.IvmcProperη);
+        public static CliMiscEtaEffective IvmcSplit = new CliMiscEtaEffective("IvmcSplitEffective", "Phenotype Fitness", EtaEpistatics.IvmcSplitη);
+        
+        public CliMiscEtaEffective(string key, string title, Func<CliParams, Func<double, double>> ηSource)
+        {
+            Key = key;
+            Title = title;
+            this.ηSource = ηSource;
+        }
+
+        public string Key { get; }
+        public string Title { get; }
+        public Func<CliParams, Func<double, double>> ηSource { get; }
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            bool diff = clips.IsSet("diff");
+
+            var model = new PlotModel { Title = title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Module Sum", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = diff ? "Fitness" : "Fitness", Key = "y" });
+            
+            int N = clips.Get("n", int.Parse, 4);
+            
+            double lambda = clips.Get("lambda", double.Parse, 0.0);
+
+            double maxWeight = clips.Get("maxweight", double.Parse, N * N);
+            double dweight = clips.Get("dweight", double.Parse, maxWeight / 100.0);
+            
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, double.NaN);
+            double ymin=clips.Get("ymin", double.Parse, 0);
+            double ymax=clips.Get("ymax", double.Parse, double.NaN);
+            model.Axes[1].Minimum = ymin;
+            model.Axes[1].Maximum = ymax;
+            model.Axes[0].Minimum = xmin;
+            model.Axes[0].Maximum = xmax;
+            
+            var η = ηSource(clips);
+            
+            int T = clips.Get("T", int.Parse, 10);
+            double tau = clips.Get("tau", double.Parse, 0.2);
+            var context = new ModelExecutionContext(new MathNet.Numerics.Random.MersenneTwister(0, false));
+            var drules = new DevelopmentRules(T, 1.0, tau, DevelopmentRules.TanhHalf);
+            
+            if (N > 0)
+            {
+                string dtm = clips.Get("dtm");
+                Func<double, Linear.Matrix<double>> createDtm = GenomeHelpers.PrepareDtmCreater(dtm, N);
+                
+                Linear.Vector<double> initialState = Linear.CreateVector.Dense(N, 1.0);
+
+                var pointSeries = new LineSeries() { RenderInLegend = false, Color = OxyColors.Red };
+                for (double weight = 0.0; weight <= maxWeight; weight += dweight)
+                {
+                    Linear.Matrix<double> transMat = createDtm(weight);
+                
+                    var g = new DenseGenome(initialState, transMat);
+                    var moduleMean = g.Develop(context, drules).Vector.Sum() / N;
+                    var benefit = η(moduleMean);
+                    var cost = weight / (N * N);
+                    var fitness = benefit - lambda * cost;
+                    pointSeries.Points.Add(new DataPoint(weight, fitness));
+                }
+                model.Series.Add(pointSeries);
+            }
+
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = 0.0, Color = OxyColors.Gray });
+
+            return model;
+        }
+    }
+    
+    public class CliMiscPlotBeam : ICliMiscPlotter
+    {
+        public string Key => "Beam";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var model = new PlotModel { Title = title == "" ? "Beam" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Total Weight", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            model.LegendTitle="s/(s+3r)";
+            int N = clips.Get("n", int.Parse, 16);
+            int sN = N / 4;
+            
+            double srmin=clips.Get("min", double.Parse, 0);
+            double srmax=clips.Get("max", double.Parse, 1);
+            double delta=clips.Get("delta", double.Parse, 0.1);
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 10);
+            
+            double lambda=clips.Get("lambda", double.Parse, 0.2);
+            model.Title = model.Title + $" (lambda = {lambda})";
+
+            double y0=1;
+
+            // Husky (Beam)
+            double j(double s, double r, double l)
+            {
+                double ys=y0,yr=y0;
+
+                for (int i=0;i<10;i++)
+                {
+                    yr += -0.2*yr+Math.Tanh(0.5*ys*r);
+                    ys += -0.2*ys+Math.Tanh(0.5*ys*s);
+                }
+
+                ys/=5;
+                yr/=5;
+
+                var b=0.5 * (1 + (ys+yr*3)*sN/N);
+                var c=(s+r*3)*sN/(N*N);
+                return b-l*c;
+            }
+
+            double f(double t,double x,double l)
+            {
+                return j(t*x, (t-t*x)/3, l);
+            }
+
+            for(double sr=srmin;sr<=srmax;sr+=delta)
+                model.Series.Add(new FunctionSeries(x => f(x, sr, lambda), xmin, xmax, 1000){Title=sr.ToString("0.0")});
+
+            return model;
+        }
+    }
+    
+    public class CliMiscPlotExpBeam : ICliMiscPlotter
+    {
+        public string Key => "ExpBeam";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var config = clips.Get("expfile", CliPlotHelpers.LoadDenseExperimentConfig);
+            var modules = clips.Get("modules", Modular.MultiModulesStringParser.Instance.Parse);
+
+            if (clips.IsSet("reconfigconfig"))
+            {
+                config = CliReconfig.Reconfig(console, clips, config);
+            }
+            
+            var moduleSize = modules.ModuleAssignments[0].Count;
+            if (modules.ModuleAssignments.Any(m => m.Count != moduleSize))
+                console.WriteLine("Modules have different sizes; Beam has little value in this instance.");
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+            var context = new ModelExecutionContext(rand);
+
+            int targetIdx = clips.Get("target", int.Parse, 0);
+            var target = config.Targets[targetIdx];
+            
+            if (clips.Get("resetTarget", bool.Parse, false))
+            {
+                int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+            }
+
+            if (clips.IsSet("proper"))
+            {
+                if (target is Epistatics.IIvmcProperTarget ipt)
+                {
+                    double[] cplus = clips.Get("cplus", s => s.Split(';').Select(double.Parse).ToArray(), null);
+                    double[] cminus = clips.Get("cminus", s => s.Split(';').Select(double.Parse).ToArray(), null);
+                    if (cplus != null)
+                    {
+                        cplus.CopyTo(ipt.Proper.Cplus, 0);
+                    }
+                    if (cminus != null)
+                    {
+                        cminus.CopyTo(ipt.Proper.Cminus, 0);
+                    }
+                }
+                else
+                {
+                    console.WriteLine("Ignoring proper parameter, as target is no an IIvmcProperTarget");
+                }
+            }
+            
+            target.NextGeneration(rand, config.JudgementRules);
+
+            var model = new PlotModel { Title = !clips.IsSet("title") && title == "" ? "Beam" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Total Weight", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            model.LegendTitle="sr Ratio";
+            int N = config.Size;
+            
+            double srmin=clips.Get("min", double.Parse, 0);
+            double srmax=clips.Get("max", double.Parse, 1);
+            double srdelta=clips.Get("srdelta", double.Parse, 0.1);
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 10);
+            
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            int srresolution = clips.Get("srresolution", int.Parse, 1);
+
+            bool showBest = clips.Get("showBest", bool.Parse, true);
+            bool saveBest = clips.Get("saveBest", bool.Parse, true);
+
+            bool showBenefit = clips.Get("showBenefit", bool.Parse, false);
+            bool showCost = clips.Get("showCost", bool.Parse, false);
+
+            var g0 = ExperimentComposition.BasicDensePopulationComposer.ComposeG0(clips, context, config);
+            g0 = g0 ?? ((VectorTarget)target).PreparePerfectP();
+
+            DenseIndividual create(double t, double sr)
+            {
+                var b0 = PrepareHierarchicalMatrix(modules, t, sr);
+                var genome = new DenseGenome(g0, b0);
+                var individual = DenseIndividual.Develop(genome, context, config.DevelopmentRules, false);
+                return individual;
+            }
+
+            double f(double t, double sr)
+            {
+                var individual = create(t, sr);
+                return individual.Judge(config.JudgementRules, target).CombinedFitness;
+            }
+
+            double b(double t, double sr)
+            {
+                var individual = create(t, sr);
+                return individual.Judge(config.JudgementRules, target).Benefit;
+            }
+
+            double c(double t, double sr)
+            {
+                var individual = create(t, sr);
+                return individual.Judge(config.JudgementRules, target).Cost;
+            }
+
+            double[] ts = Enumerable.Range(0, resolution + 1).Select(i => xmin + ((double)i / resolution) * (xmax - xmin)).ToArray();
+            IEnumerable<DataPoint> toDataPoints(double[] xs, double[] ys) => xs.Zip(ys, (x, y) => new DataPoint(x, y));
+
+            double srBest = double.NaN;
+            double tBest = double.NaN;
+            double fBest = double.NegativeInfinity;
+
+            int ci = 0;
+            OxyColor nextColor()
+            {
+                var col = model.DefaultColors[ci];
+                ci = (ci + 1) % model.DefaultColors.Count;
+                return col;
+            }
+
+            for (double outerSr = srmin; outerSr <= srmax; outerSr += srdelta)
+            {
+                double sr = outerSr;
+                for (int src = 0; src < srresolution; src++, sr += srdelta / srresolution)
+                {
+                    var beam = ts.Select(t => f(t, sr)).ToArray();
+
+                    int best = beam.IndexMax(x => x);
+
+                    if (beam[best] > fBest)
+                    {
+                        tBest = ts[best];
+                        srBest = sr;
+                        fBest = beam[best];
+                    }
+
+                    if (src == 0)
+                    {
+                        var color = showCost || showBenefit ? nextColor() : OxyColors.Automatic;
+
+                        var line = new LineSeries() { Title = sr.ToString("0.00"), Color = color };
+                        line.Points.AddRange(toDataPoints(ts, beam));
+                        model.Series.Add(line);
+
+                        if (showBenefit)
+                        {
+                            var bbeam = ts.Select(t => b(t, sr)).ToArray();
+                            var bline = new LineSeries() { Title = null, Color = color, LineStyle = LineStyle.Dot };
+                            bline.Points.AddRange(toDataPoints(ts, bbeam));
+                            model.Series.Add(bline);
+                        }
+
+                        if (showCost)
+                        {
+                            var cbeam = ts.Select(t => c(t, sr)).ToArray();
+                            var cline = new LineSeries() { Title = null, Color = color, LineStyle = LineStyle.Dot };
+                            cline.Points.AddRange(toDataPoints(ts, cbeam));
+                            model.Series.Add(cline);
+                        }
+                    }
+                }
+            }
+
+            if (showBest)
+                model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = tBest, Color = OxyColors.Gray, Text = $"T = {tBest}, SR = {srBest}, f = {fBest:0.00E+0}" });
+
+            if (saveBest)
+                Analysis.SaveGenome("genome_best.dat", create(tBest, srBest).Genome);
+
+            return model;
+        }
+        
+        public static Linear.Matrix<double> PrepareHierarchicalMatrix(Modular.Modules modules, double t, double sr)
+        {
+            var mat = Linear.CreateMatrix.Dense<double>(modules.ElementCount, modules.ElementCount);
+            foreach (var module in modules.ModuleAssignments)
+            {
+                var i0 = module.First();
+
+                double s = t * sr;
+                double r = (t - s) / (module.Count - 1);
+            
+                foreach (var i in module)
+                    mat[i, i0] = r;
+                mat[i0, i0] = s;
+            }
+            return mat;
+        }
+    }
+
+    public class CliMiscPlotExpsBeam : ICliMiscPlotter
+    {
+        public string Key => "ExpsBeam";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var configs = clips.Get("expfiles", s => s.Split(',').Select(CliPlotHelpers.LoadDenseExperimentConfig)).ToArray();
+            var names = clips.Get("names", s => s.Split(',')).ToArray();
+            var legendTitle = clips.Get("legendTitle");
+            var modules = clips.Get("modules", Modular.MultiModulesStringParser.Instance.Parse);
+
+            var moduleSize = modules.ModuleAssignments[0].Count;
+            if (modules.ModuleAssignments.Any(m => m.Count != moduleSize))
+                console.WriteLine("Modules have different sizes; Beam has little value in this instance.");
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+            var context = new ModelExecutionContext(rand);
+
+            int targetIdx = clips.Get("target", int.Parse, 0);
+
+            var model = new PlotModel { Title = !clips.IsSet("title") && title == "" ? "Beam" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Total Weight", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            model.LegendTitle = legendTitle;
+            int N = configs[0].Size;
+
+            double xmin = clips.Get("xmin", double.Parse, 0);
+            double xmax = clips.Get("xmax", double.Parse, 10);
+
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            double sr = clips.Get("sr", double.Parse, 1);
+
+            for (int ci = 0; ci < configs.Length; ci++)
+            {
+                var config = configs[ci];
+                var name = names[ci];
+
+                var target = config.Targets[targetIdx];
+
+                if (clips.Get("resetTarget", bool.Parse, false))
+                {
+                    int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+
+                    ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                    target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+                }
+
+                target.NextGeneration(rand, config.JudgementRules);
+
+                var g0 = ((VectorTarget)target).PreparePerfectP();
+
+                DenseIndividual create(double t)
+                {
+                    var b0 = CliMiscPlotExpBeam.PrepareHierarchicalMatrix(modules, t, sr);
+                    var genome = new DenseGenome(g0, b0);
+                    var individual = DenseIndividual.Develop(genome, context, config.DevelopmentRules, false);
+                    return individual;
+                }
+
+                double f(double t)
+                {
+                    var individual = create(t);
+                    return individual.Judge(config.JudgementRules, target).CombinedFitness;
+                }
+
+                double[] ts = Enumerable.Range(0, resolution + 1).Select(i => xmin + ((double)i / resolution) * (xmax - xmin)).ToArray();
+                IEnumerable<DataPoint> toDataPoints(double[] xs, double[] ys) => xs.Zip(ys, (x, y) => new DataPoint(x, y));
+
+                var beam = ts.Select(t => f(t)).ToArray();
+                var line = new LineSeries() { Title = name };
+                line.Points.AddRange(toDataPoints(ts, beam));
+                model.Series.Add(line);
+
+                if (clips.IsSet("maxes"))
+                {
+                    var idx = beam.IndexMax(x => x);
+                    model.Annotations.Add(new LineAnnotation() { Text = name, Type = LineAnnotationType.Vertical, X = ts[idx] });
+                }
+            }
+
+            return model;
+        }
+    }
+    
+    public class CliMiscPlotExpReconfigBeam : ICliMiscPlotter
+    {
+        public string Key => "ExpReconfigBeam";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var config = clips.Get("expfile", CliPlotHelpers.LoadDenseExperimentConfig);
+            var reconfigTargets = clips.Get("reconfigtargets", s => s.Split(';')).ToArray();
+            var reconfigValues = reconfigTargets.Select(rt => clips.Get("values:" + rt, s => s.Split(';')).ToArray()).ToArray();
+            var legendTitle = clips.Get("legendTitle");
+            var modules = clips.Get("modules", Modular.MultiModulesStringParser.Instance.Parse);
+            
+            var moduleSize = modules.ModuleAssignments[0].Count;
+            if (modules.ModuleAssignments.Any(m => m.Count != moduleSize))
+                console.WriteLine("Modules have different sizes; Beam has little value in this instance.");
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+            var context = new ModelExecutionContext(rand);
+
+            int targetIdx = clips.Get("target", int.Parse, 0);
+
+            var model = new PlotModel { Title = !clips.IsSet("title") && title == "" ? "Beam" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Total Weight", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            model.LegendTitle = legendTitle;
+            int N = config.Size;
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 10);
+            
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            int srresolution = clips.Get("srresolution", int.Parse, 1);
+
+            bool showBest = clips.Get("showBest", bool.Parse, true);
+            bool saveBest = clips.Get("saveBest", bool.Parse, true);
+
+            double sr = clips.Get("sr", double.Parse, 1.0 / N); // dense by default
+
+            for (int vi = 0; vi < reconfigValues[0].Length; vi++)
+            {
+                var reconfigClips = new CliParams();
+                for (int ri = 0; ri < reconfigTargets.Length; ri++)
+                    reconfigClips.Set(reconfigTargets[ri], reconfigValues[ri][vi]);
+                var reconfigedExp = CliReconfig.Reconfig(console, clips, config);
+                var target = reconfigedExp.Targets[targetIdx];
+                
+                if (clips.Get("resetTarget", bool.Parse, false))
+                {
+                    int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+
+                    ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                    target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+                }
+                
+                target.NextGeneration(rand, config.JudgementRules);
+
+                var g0 = ((VectorTarget)target).PreparePerfectP();
+
+                DenseIndividual create(double t)
+                {
+                    var b0 = CliMiscPlotExpBeam.PrepareHierarchicalMatrix(modules, t, sr);
+                    var genome = new DenseGenome(g0, b0);
+                    var individual = DenseIndividual.Develop(genome, context, config.DevelopmentRules, false);
+                    return individual;
+                }
+
+                double f(double t)
+                {
+                    var individual = create(t);
+                    return individual.Judge(config.JudgementRules, target).CombinedFitness;
+                }
+
+                double[] ts = Enumerable.Range(0, resolution + 1).Select(i => xmin + ((double)i / resolution) * (xmax - xmin)).ToArray();
+                IEnumerable<DataPoint> toDataPoints(double[] xs, double[] ys) => xs.Zip(ys, (x, y) => new DataPoint(x, y));
+
+                var beam = ts.Select(t => f(t)).ToArray();
+                var line = new LineSeries() { Title = "" + vi };
+                line.Points.AddRange(toDataPoints(ts, beam));
+                model.Series.Add(line);
+            }
+
+            return model;
+        }
+    }
+
+    public class BenefitPlotting
+    {
+        public static PlotModel Plot(TextWriter console, CliParams clips, string title, Func<double, double> eta, double[] steps, OxyEx.CustomAxisLabel[] xlabels, OxyEx.CustomAxisLabel[] ylabels)
+        {
+            steps = steps ?? new double[0];
+
+            var model = new PlotModel { Title = title };
+            var xaxis = new OxyEx.CustomLabelLinearAxis { Position = AxisPosition.Bottom, Title = "Sum over Module", Key = "x" };
+            var yaxis = new OxyEx.CustomLabelLinearAxis { Position = AxisPosition.Left, Title = "Module Benefit", Key = "y", IncludeTick = true };
+            model.Axes.Add(xaxis);
+            model.Axes.Add(yaxis);
+
+            if (xlabels != null)
+                xaxis.CustomAxisLabels.AddRange(xlabels);
+            if (ylabels != null)
+                yaxis.CustomAxisLabels.AddRange(ylabels);
+
+            model.LegendTitle = "";
+            
+            double xmin = clips.Get("xmin", double.Parse, -1);
+            double xmax = clips.Get("xmax", double.Parse, 1);
+            double delta = clips.Get("delta", double.Parse, 0.001);
+
+            double bonus = clips.Get("bonus", double.Parse, 1.5);
+            
+            console.WriteLine(steps.Length);
+            steps = (double[])steps.Clone();
+
+            List<OxyPlot.DataPoint> points = new List<DataPoint>();
+
+            int si = 0;
+
+            // clear step before xmin
+            while (si < steps.Length && steps[si] <= xmin)
+                si++;
+
+            double sd = delta / 10.0;
+
+            for (double x = xmin; ; x += delta)
+            {
+                if (x >= xmax)
+                    x = xmax;
+
+                while (x <= xmax && si < steps.Length && steps[si] < x + sd)
+                {
+                    points.Add(new DataPoint(steps[si], eta(steps[si]-sd)));
+                    points.Add(new DataPoint(double.NaN, double.NaN));
+                    points.Add(new DataPoint(steps[si], eta(steps[si]+sd)));
+                    
+                    if (steps[si] > x - sd)
+                        x += delta;
+
+                    si++;
+                }
+
+                if (x >= xmax)
+                    break;
+
+                points.Add(new DataPoint(x, eta(x)));
+
+                if (x >= xmax)
+                    break;
+            }
+
+            var ls = new OxyPlot.Series.LineSeries();
+            ls.Points.AddRange(points);
+            ls.BrokenLineStyle = LineStyle.Dash;
+            ls.BrokenLineColor = ls.Color;
+            ls.BrokenLineThickness = ls.StrokeThickness / 2.0;
+            model.Series.Add(ls);
+
+            return model;
+        }
+    }
+
+    public class CliMiscPlotCbbnkStepModel : ICliMiscPlotter
+    {
+        public string Key => "CbbnkStepModel";
+        
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            double omega = clips.Get("omega", double.Parse);
+            double phi = clips.Get("phi", double.Parse);
+            double xi = clips.Get("xi", double.Parse);
+
+            double eta(double x) => x > omega
+                ? Math.Abs(x) * phi + xi
+                : Math.Abs(x) * phi;
+
+            var plot = BenefitPlotting.Plot(console, clips, title ?? "CbbnkStep", eta, new[] { omega }, new OxyEx.CustomAxisLabel[] { new OxyEx.CustomAxisLabel(omega, "ω = $$$") }, null);
+            return plot;
+        }
+    }
+
+    public class CliMiscPlotIvmcModel : ICliMiscPlotter
+    {
+        public string Key => "IvmcModel";
+        
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var cplus = clips.Get("bonus", double.Parse, 1.5);
+            var cminus = 1.0;
+
+            double sqr(double x) => x * x;
+            double eta(double x) => cplus * sqr((x + 1) / 2.0) + cminus * sqr((1 - x) / 2.0);
+
+            var plot = BenefitPlotting.Plot(console, clips, title ?? "Ivmc", eta, null, null, new OxyEx.CustomAxisLabel[] { new OxyEx.CustomAxisLabel(cplus, "c^{+} = $$$"), new OxyEx.CustomAxisLabel(cminus, "c^{-} = $$$") });
+            return plot;
+        }
+    }
+
+    public class CliMiscPlotIvmcSwitchness : ICliMiscPlotter
+    {
+        public string Key => "IvmcSwitchness";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            bool diff = clips.IsSet("diff");
+
+            var model = new PlotModel { Title = title == "" ? "Ivmc Switchness" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "s/(s+3r)", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = diff ? "Change in Fitness" : "Fitness", Key = "y" });
+
+            model.LegendTitle="Total Weight";
+            int N = clips.Get("n", int.Parse, 16);
+            int sN = N / 4;
+            
+            double wmin=clips.Get("min", double.Parse, 0);
+            double wmax=clips.Get("max", double.Parse, 5);
+            double delta=clips.Get("delta", double.Parse, 1.0);
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 1);
+            
+            double lambda = clips.Get("lambda", double.Parse, 0.0);
+            double bonus = clips.Get("bonus", double.Parse, 1.5);
+            model.Title = model.Title + $" (lambda = {lambda})";
+            
+            // IvmcSwitchness, ys=1 is high
+            double j(double ys0, double yr0, double s, double r, double l)
+            {
+                double ys=ys0,yr=yr0;
+
+                for (int i=0;i<10;i++)
+                {
+                    yr += -0.2*yr+Math.Tanh(0.5*ys*s + 3*0.5*yr*r); // 'columns' topology
+                    ys += -0.2*ys+Math.Tanh(0.5*ys*s + 3*0.5*yr*r);
+                }
+                
+                ys/=5;
+                yr/=5;
+
+                double acc = (ys+yr*3) / 4; // average (signed) weight
+
+                double hi = 0.5 + acc * 0.5;
+                double lo = 0.5 - acc * 0.5;
+                
+                var b=0.5 * (1 + (hi*hi*bonus + lo*lo)*sN/N);
+                var c=(Math.Abs(s)+Math.Abs(r)*3)*sN/(N*N);
+                return b-l*c;
+            }
+
+            double f(double ys0, double yr0, double t,double x,double l)
+            {
+                return j(ys0, yr0, t*x, (t-t*x)/3, l);
+            }
+
+            if (diff)
+            {
+                for (double w = wmin; w <= wmax; w += delta)
+                {
+                    var col = OxyColor.Interpolate(OxyColors.Red, OxyColors.Blue, (w - wmin) / (wmax - wmin));
+                    var d = new FunctionSeries(x => f(1.0, -1.0, w, x, lambda) - f(-1.0, -1.0, w, x, lambda), xmin, xmax, 1000) { Title = w.ToString("0.0"), Color = col };
+                    model.Series.Add(d);
+                }
+            }
+            else
+            {
+                for (double w = wmin; w <= wmax; w += delta)
+                {
+                    var col = OxyColor.Interpolate(OxyColors.Red, OxyColors.Blue, (w - wmin) / (wmax - wmin));
+                    var ll = new FunctionSeries(x => f(-1.0, -1.0, w, x, lambda), xmin, xmax, 1000) { Title = "Negtv " + w.ToString("0.0"), Color = col };
+                    var hl = new FunctionSeries(x => f(1.0, -1.0, w, x, lambda), xmin, xmax, 1000) { Title = "Swtch " + w.ToString("0.0"), Color = col, LineStyle = LineStyle.Dash };
+                    model.Series.Add(ll);
+                    model.Series.Add(hl);
+                }
+            }
+
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = 0.25, Color = OxyColors.Gray });
+
+            return model;
+        }
+    }
+    
+    public class CliMiscPlotIvmcProperSwitchness : ICliMiscPlotter
+    {
+        public string Key => "IvmcProperSwitchness";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            bool diff = clips.IsSet("diff");
+
+            int N = clips.Get("n", int.Parse, 16);
+            int sN = N / 4;
+            
+            double wmin=clips.Get("min", double.Parse, 0);
+            double wmax=clips.Get("max", double.Parse, 5);
+            double delta=clips.Get("delta", double.Parse, 1.0);
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 1);
+
+            double ymin=clips.Get("ymin", double.Parse, double.NaN);
+            double ymax=clips.Get("ymax", double.Parse, double.NaN);
+            
+            double lambda = clips.Get("lambda", double.Parse, 0.0);
+            double ch = clips.Get("CH", double.Parse, 1.0);
+            double cl = clips.Get("CL", double.Parse, 0.7);
+            
+            var model = new PlotModel { Title = title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "s/(s+3r)", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = diff ? "Change in Fitness" : "Fitness", Key = "y", Maximum = ymax, Minimum = ymin });
+
+            model.LegendTitle="Total Weight";
+            
+            // IvmcSwitchness, ys=1 is high
+            double j(double ys0, double yr0, double s, double r, double l)
+            {
+                double ys=ys0,yr=yr0;
+
+                for (int i=0;i<10;i++)
+                {
+                    yr += -0.2*yr+Math.Tanh(0.5*ys*s + 3*0.5*yr*r); // 'columns' topology
+                    ys += -0.2*ys+Math.Tanh(0.5*ys*s + 3*0.5*yr*r);
+                }
+                
+                ys/=5;
+                yr/=5;
+
+                double acc = (ys+yr*3) / 4; // average (signed) weight
+
+                double hi = 0.5 + acc * 0.5;
+                double lo = 0.5 - acc * 0.5;
+
+                var b=0.5 * (1 + (hi*hi*ch + lo*lo*cl)); // module cost is already mean
+                var c=(Math.Abs(s)+Math.Abs(r)*3)*sN/(N*N);
+                return b-l*c;
+            }
+
+            double f(double ys0, double yr0, double t,double x,double l)
+            {
+                return j(ys0, yr0, t*x, (t-t*x)/3, l);
+            }
+
+            if (diff)
+            {
+                for (double w = wmin; w <= wmax; w += delta)
+                {
+                    var col = OxyColor.Interpolate(OxyColors.Red, OxyColors.Blue, (w - wmin) / (wmax - wmin));
+                    var d = new FunctionSeries(x => f(1.0, -1.0, w, x, lambda) - f(-1.0, -1.0, w, x, lambda), xmin, xmax, 1000) { Title = w.ToString("0.0"), Color = col };
+                    model.Series.Add(d);
+                }
+            }
+            else
+            {
+                for (double w = wmin; w <= wmax; w += delta)
+                {
+                    var col = OxyColor.Interpolate(OxyColors.Red, OxyColors.Blue, (w - wmin) / (wmax - wmin));
+                    var ll = new FunctionSeries(x => f(-1.0, -1.0, w, x, lambda), xmin, xmax, 1000) { Title = "Negtv " + w.ToString("0.0"), Color = col };
+                    var hl = new FunctionSeries(x => f(1.0, -1.0, w, x, lambda), xmin, xmax, 1000) { Title = "Swtch " + w.ToString("0.0"), Color = col, LineStyle = LineStyle.Dash };
+                    model.Series.Add(ll);
+                    model.Series.Add(hl);
+                }
+            }
+
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = 0.25, Color = OxyColors.Gray });
+            if (diff)
+            {
+                model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = 0, Color = OxyColors.Gray });
+            }
+
+            return model;
+        }
+    }
+
+    public class CliMiscPlotPitOfDespair : ICliMiscPlotter
+    {
+        public string Key => "PitOfDespair";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var model = new PlotModel { Title = title == "" ? "Pit of Despair" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Total Weight", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            model.LegendTitle="Lambda";
+            int N = clips.Get("n", int.Parse, 16);
+            int sN = N / 4;
+            
+            double lmin=clips.Get("min", double.Parse, 0);
+            double lmax=clips.Get("max", double.Parse, 2);
+            double delta=clips.Get("delta", double.Parse, 0.2);
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 0.1);
+
+            double y0=1;
+
+            // Husky (Beam)
+            double j(double s, double r, double l)
+            {
+                double ys=y0,yr=y0;
+
+                for (int i=0;i<10;i++)
+                {
+                    yr += -0.2*yr+Math.Tanh(0.5*ys*r);
+                    ys += -0.2*ys+Math.Tanh(0.5*ys*s);
+                }
+
+                ys/=5;
+                yr/=5;
+
+                var b=0.5 * (1 + (ys+yr*3)*sN/N);
+                var c=(s+r*3)*sN/(N*N);
+                return b-l*c;
+            }
+
+            double f(double t,double x,double l)
+            {
+                return j(t*x, (t-t*x)/3, l);
+            }
+
+            for(double t=lmin;t<=lmax;t+=delta)
+                model.Series.Add(new FunctionSeries(x => f(x, 1, t), xmin, xmax, 1000){Title=t.ToString("0.0")});
+
+            return model;
+        }
+    }
+
+    public class CliMiscPlotDevTraj : ICliMiscPlotter
+    {
+        public string Key => "DevTraj";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var model = new PlotModel { Title = title == "" ? "Development Trajectories" : title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Developmental Steps", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Trait Expression", Key = "y" });
+
+            model.LegendTitle="Train";
+            int N = clips.Get("n", int.Parse, 4);
+            
+            double lmin=clips.Get("min", double.Parse, 0);
+            double lmax=clips.Get("max", double.Parse, 2);
+            double delta=clips.Get("delta", double.Parse, 0.2);
+
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 0.1);
+            
+            int T = clips.Get("T", int.Parse, 10);
+            double sqw(double x) => Math.Tanh(x / 2);
+
+            double[][] d(double g, double r, double s) // b twiddle
+            {
+                double[][] res = new double[T + 1][];
+                double ys = 1, yr = 1;
+                res[0] = new[] { ys, yr };
+                for (int i = 0; i < T; i++)
+                {
+                    double dys = -0.2 * ys + sqw(ys * s + 3 * yr * g);
+                    double dyr = -0.2 * yr + sqw(ys * r + 3 * yr * g);
+                    ys += dys;
+                    yr += dyr;
+                    res[i + 1] = new[] { ys, yr };
+                }
+                return res;
+            }
+            double L = 1.18, ror = 0.3;//~optimal
+
+            double[][] k(double x, double t, double l)
+            {
+                x = 1 - x;
+                double g0 = t / 16, s0 = g0, r0 = g0;
+                double g1 = 0, s1 = ror * t, r1 = (t - s1) / 3;
+                return d(x * g0 + (1 - x) * g1, x * r0 + (1 - x) * r1, x * s0 + (1 - x) * s1);
+            }
+
+            for (int z = 0; z < 2; z++)
+            {
+                var t = 5;
+                double[][] fox = k(0, t, L);
+                double[][] husky = k(1, t, L);
+                model.Series.Add(new FunctionSeries(x => fox[(int)x][z], 0, T, T + 1) { Title = $"Fox{z}@{t}" });
+                model.Series.Add(new FunctionSeries(x => husky[(int)x][z], 0, T, T + 1) { Title = $"Husky{z}@{t}" });
+            }
+
+            return model;
+        }
+    }
+    
+    public class CliMiscTransition : ICliMiscPlotter
+    {
+        public string Key => "Transition";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            int seed = clips.Get("seed", int.Parse, 1);
+
+            var config = CliPlotHelpers.LoadExperimentConfig(clips.GetOrCreate("expfile", s=>s, () => clips.Get("config")));
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+            var context = new ModelExecutionContext(rand);
+            //var rand = context.DisableRandom();
+
+            var model = new PlotModel { Title = title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Positive Initial Proportion", Key = "x" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            int na = clips.Get("na", int.Parse, 1);
+            int nb = clips.Get("nb", int.Parse, 1);
+
+            int n = na + nb;
+
+            if (n != config.Size)
+                throw new Exception("Config size " + config.Size + " does not match na & nb");
+
+            double weight;
+            if (clips.IsSet("weight"))
+            {
+                weight = clips.Get("weight", double.Parse);
+            }
+            else if (clips.IsSet("expfile"))
+            {
+                var exp = PopulationExperiment<DenseIndividual>.Load(clips.Get("expfile"));
+                weight = exp.Population.ExtractAll()[0].Genome.TransMat.Enumerate().Sum(d => Math.Abs(d));
+                console.WriteLine("weight: " + weight);
+            }
+            else
+            {
+                throw new Exception("Total weight not specified; qualify expfile or weight");
+            }
+
+            DenseIndividual create(double a, double dba, double dbb)
+            {
+                var ba = (dba + a * weight) / (na * n);
+                var bb = (dbb + (1 - a) * weight) / (nb * n);
+                var g = Linear.CreateVector.Dense<double>(n, i => i < na ? +1.0 : -1.0);
+                var b = Linear.CreateMatrix.Dense<double>(n, n, (i, j) => j < na ? ba : bb);
+                
+                var genome = new DenseGenome(g, b);
+                var individual = DenseIndividual.Develop(genome, context, config.DevelopmentRules, false);
+
+                return individual;
+            }
+            
+            int targetIdx = clips.Get("target", int.Parse, 0);
+            var target = config.Targets[targetIdx];
+            
+            if (clips.Get("resetTarget", bool.Parse, false))
+            {
+                int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+            }
+            
+            target.NextGeneration(rand, config.JudgementRules);
+            
+            bool benefit = clips.IsSet("benefit");
+
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            
+            double xmin=clips.Get("xmin", double.Parse, 0);
+            double xmax=clips.Get("xmax", double.Parse, 1);
+            double ymin=clips.Get("ymin", double.Parse, double.NaN);
+            double ymax=clips.Get("ymax", double.Parse, double.NaN);
+            model.Axes[1].Minimum = ymin;
+            model.Axes[1].Maximum = ymax;
+            //model.Axes[0].Minimum = xmin;
+            //model.Axes[0].Maximum = xmax;
+
+            double deltaProp = clips.Get("da", double.Parse, 0.0000001);
+            double deltaAbs = clips.Get("dba", double.Parse, 0.0000001);
+            
+
+            int[] indicies = Enumerable.Range(0, resolution + 1).ToArray();
+            double[] aValues = indicies.Select(i => xmin + ((double)i / resolution) * (xmax - xmin)).ToArray();
+            
+            double[] compute(Func<double, double> f) => aValues.Select(f).ToArray();
+
+            double[] fValues = compute(a =>
+            {
+                var individual = create(a, 0.0, 0.0);
+                var ij = individual.Judge(config.JudgementRules, target);
+                return ij.CombinedFitness;
+            });
+
+            double[] dfdaValues = compute(a =>
+            { // note: da is not linear in ba or bb
+                var da = deltaProp;
+                var i0 = create(a, 0.0, 0.0);
+                var ij0 = i0.Judge(config.JudgementRules, target);
+                var i1 = create(a + da, 0.0, 0.0);
+                var ij1 = i1.Judge(config.JudgementRules, target);
+                return benefit
+                    ? (ij1.Benefit - ij0.Benefit) / da
+                    : (ij1.CombinedFitness - ij0.CombinedFitness) / da;
+            });
+            double[] dfdaMirrorValues = dfdaValues.Reverse().ToArray();
+
+            double[] dfdbaValues = compute(a =>
+            {
+                var dba = deltaAbs;
+                var i0 = create(a, 0.0, 0.0);
+                var ij0 = i0.Judge(config.JudgementRules, target);
+                var i1 = create(a, dba, 0.0);
+                var ij1 = i1.Judge(config.JudgementRules, target);
+                return benefit
+                    ? (ij1.Benefit - ij0.Benefit) / dba
+                    : (ij1.CombinedFitness - ij0.CombinedFitness) / dba;
+            });
+            double[] dfdbaMirrorValues = dfdbaValues.Reverse().ToArray();
+
+            double[] dfdbbValues = compute(a =>
+            {
+                var dbb = deltaAbs;
+                var i0 = create(a, 0.0, 0.0);
+                var ij0 = i0.Judge(config.JudgementRules, target);
+                var i1 = create(a, 0.0, dbb);
+                var ij1 = i1.Judge(config.JudgementRules, target);
+                return benefit
+                    ? (ij1.Benefit - ij0.Benefit) / dbb
+                    : (ij1.CombinedFitness - ij0.CombinedFitness) / dbb;
+            });
+            double[] dfdbbMirrorValues = dfdbbValues.Reverse().ToArray();
+
+            double[] ddiff = dfdbaValues.Zip(dfdbbValues, (a, b) => a - b).ToArray();
+            double[] ddiffMirror = ddiff.Reverse().ToArray();
+            
+            void plotLine(double[] data, string lineTitle, OxyColor color, LineStyle style)
+            {
+                var line = new LineSeries() { Title = lineTitle, RenderInLegend = true, Color = color, LineStyle = style };
+                for (int i = 0; i < aValues.Length; i++)
+                {
+                    line.Points.Add(new DataPoint(aValues[i], data[i]));
+                }
+                model.Series.Add(line);
+            }
+
+            plotLine(fValues, "fitness", OxyColors.Red, LineStyle.Solid);
+            //plotLine(dfdbaValues, benefit ? "db/dba" : "df/dba", benefit ? OxyColors.Orange : OxyColors.Red, LineStyle.Solid);
+            //plotLine(dfdbbValues, benefit ? "db/dba" : "df/dbb");
+            //plotLine(ddiff, benefit ? "db/dba - db/dbb" : "df/dba - df/dbb");
+            //plotLine(ddiffMirror, benefit ? "db/dba - db/dbb (mirror)" : "df/dba - df/dbb (mirror)");
+            if (clips.IsSet("dfdba"))
+            {
+                plotLine(dfdbaValues, benefit ? "df/dba" : "df/dba", OxyColors.SlateBlue, LineStyle.Solid);
+                plotLine(dfdbaMirrorValues, benefit ? "df/dba (mirror)" : "df/dba (mirror)", OxyColors.SlateBlue, LineStyle.Dash);
+            }
+            if (clips.IsSet("dfdbb"))
+            {
+                plotLine(dfdbbValues, benefit ? "df/dbb" : "df/dbb", OxyColors.SlateBlue, LineStyle.Solid);
+                plotLine(dfdbbMirrorValues, benefit ? "df/dbb (mirror)" : "df/dbb (mirror)", OxyColors.SlateBlue, LineStyle.Dash);
+            }
+            if (clips.IsSet("dfda"))
+            {
+                plotLine(dfdaValues, benefit ? "df/da" : "df/da", OxyColors.SlateBlue, LineStyle.Solid);
+                plotLine(dfdaMirrorValues, benefit ? "df/da (mirror)" : "df/da (mirror)", OxyColors.SlateBlue, LineStyle.Dash);
+            }
+            
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = 0.5, Color = OxyColors.Gray });
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = 0, Color = OxyColors.Gray });
+            model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Horizontal, Y = -config.JudgementRules.RegularisationFactor / (n * n), Color = OxyColors.Maroon });
+            
+            return model;
+        }
+    }
+
+    public class CliModularDevelopment : ICliMiscPlotter
+    {
+        public string Key => "modd";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            int seed = clips.Get("seed", int.Parse, 1);
+
+            var config = CliPlotHelpers.LoadExperimentConfig(clips.GetOrCreate("expfile", s => s, () => clips.Get("config")));
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+            var context = new ModelExecutionContext(rand);
+
+            var model = new PlotModel { Title = title };
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Positive Initial Proportion", Key = "B" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Fitness", Key = "y" });
+
+            var modules = clips.Get("modulesstring", Modular.MultiModulesStringParser.Instance.Parse);
+
+            double delta = clips.Get("delta", double.Parse, 1e-7);
+
+            int targetIdx = clips.Get("target", int.Parse, 0);
+            var target = config.Targets[targetIdx] as VectorTarget;
+
+            if (clips.Get("resetTarget", bool.Parse, false))
+            {
+                int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+            }
+
+            var g = target.PreparePerfectP();
+            var sTraitCount = modules.ModuleAssignments.Sum(m => m.Count * m.Count);
+            var rTraitCount = modules.ElementCount * modules.ElementCount - sTraitCount;
+            DenseIndividual create(double s, double r)
+            {
+                var b = GenomeHelpers.CreateDense(g, modules, s / sTraitCount, r / rTraitCount);
+
+                var genome = new DenseGenome(g, b);
+                var individual = DenseIndividual.Develop(genome, context, config.DevelopmentRules, false);
+
+                return individual;
+            }
+
+            target.NextGeneration(rand, config.JudgementRules);
+
+            double judge2(double s, double r, bool benefit2)
+            {
+                var individual = create(s, r);
+                var judgement = MultiMeasureJudgement.Judge(individual.Genome, individual.Phenotype, config.JudgementRules, target);
+
+                if (benefit2)
+                {
+                    return judgement.Benefit;
+                }
+                else
+                {
+                    return judgement.CombinedFitness;
+                }
+            }
+
+            var deltas = clips.IsSet("deltas");
+            var deltar = clips.IsSet("deltar");
+
+            if (deltas && deltar)
+            {
+                throw new Exception("Cannot plot deltas and deltar at the same time");
+            }
+
+            bool benefit = clips.IsSet("benefit");
+            double judge3(double s, double r) => judge2(s, r, benefit);
+
+            double judge(double s, double r)
+            {
+                if (deltas)
+                {
+                    return (judge3(s + delta, r) - judge3(s, r)) / delta;
+                }
+                else if (deltar)
+                {
+                    return (judge3(s, r + delta) - judge3(s, r)) / delta;
+                }
+                else
+                {
+                    return judge3(s, r);
+                }
+            }
+
+            var mode = clips.Get("mode", "hms");
+            switch (mode)
+            {
+                case "hms":
+                    {
+                        var maxmag = clips.Get("maxmag", double.Parse, 10);
+                        var maxs = clips.Get("maxs", double.Parse, maxmag);
+                        var mins = clips.Get("mins", double.Parse, 0.0);
+                        var maxr = clips.Get("maxr", double.Parse, maxmag);
+                        var minr = clips.Get("minr", double.Parse, 0.0);
+                        var sampleCount = clips.Get("sampleCount", int.Parse, 50);
+
+                        var samples = new double[sampleCount + 1, sampleCount + 1];
+                        var rows = new double[sampleCount + 1];
+                        var cols = new double[sampleCount + 1];
+
+                        for (int i = 0; i <= sampleCount; i++)
+                        {
+                            var s = cols[i] = mins + (maxs - mins) * (i / (double)sampleCount);
+                            for (int j = 0; j <= sampleCount; j++)
+                            {
+                                var r = rows[j] = minr + (maxr - minr) * (j / (double)sampleCount);
+                                samples[i, j] = judge(s, -r);
+                            }
+                        }
+
+                        var plot = new PlotModel() { Title = "HMS" };
+
+                        var saxis = new LinearAxis() { Title = "s", Position = AxisPosition.Bottom, Key = "s" };
+                        var raxis = new LinearAxis() { Title = "r", Position = AxisPosition.Left, Key = "r" };
+                        plot.Axes.Add(saxis);
+                        plot.Axes.Add(raxis);
+
+                        var colorAxis = new LinearColorAxis() { Title = benefit ? "Benefit" : "Fitness", Position = AxisPosition.Right, Key = "c" };
+                        colorAxis.Palette = OxyPalettes.Gray(100);
+                        plot.Axes.Add(colorAxis);
+
+                        var hms = new HeatMapSeries();
+                        hms.X0 = 0;
+                        hms.X1 = maxs;
+                        hms.Y0 = 0;
+                        hms.Y1 = maxr;
+                        hms.RenderMethod = HeatMapRenderMethod.Rectangles;
+                        hms.CoordinateDefinition = HeatMapCoordinateDefinition.Center;
+                        hms.Data = samples;
+                        plot.Series.Add(hms);
+
+                        var cs = new ContourSeries();
+                        cs.Data = samples;
+                        cs.RowCoordinates = rows;
+                        cs.ColumnCoordinates = cols;
+                        plot.Series.Add(cs);
+
+                        return plot;
+                    }
+
+                case "varysr":
+                    {
+                        // first, find optimum weight (doesn't need to be perfect)
+                        var weight = clips.GetOrCreate("weight", double.Parse, () => Misc.BinaryArgMax(w => judge2(w, 0, false), new Misc.Range(delta, 1.0 / delta), delta)); // may be incorrect if there is a pit of despair
+                        var sampleCount = clips.Get("sampleCount", int.Parse, 500);
+                        var deltasr = clips.IsSet("deltasr");
+
+                        var plot = new PlotModel() { Title = "VaryB", Subtitle = $"Max Weight = {weight}" };
+
+                        var sraxis = new LinearAxis() { Title = "(s / (s + r))", Position = AxisPosition.Bottom, Key = "srratio" };
+                        var faxis = new LinearAxis() { Title = benefit ? "Benefit" : "Fitness", Position = AxisPosition.Left, Key = "f" };
+                        plot.Axes.Add(sraxis);
+                        plot.Axes.Add(faxis);
+
+                        var cls = new LineSeries() { Title = "Off-block correct", RenderInLegend = true };
+                        var ils = new LineSeries() { Title = "Off-block incorrect", RenderInLegend = true };
+                        var zls = new LineSeries() { Title = "Off-block zeroed", RenderInLegend = true };
+                        var ols = new LineSeries() { Title = "Off-block only", RenderInLegend = true };
+
+                        for (int i = 0; i <= sampleCount; i++)
+                        {
+                            var ratio = (double)i / sampleCount;
+
+                            if (deltasr)
+                            {
+                                var deltaRatio = ratio + delta;
+                                cls.Points.Add(new DataPoint(ratio, (judge(weight * deltaRatio, weight * (1 - deltaRatio)) - judge(weight * ratio, weight * (1 - ratio))) / delta));
+                                ils.Points.Add(new DataPoint(ratio, (judge(weight * deltaRatio, -weight * (1 - deltaRatio)) - judge(weight * ratio, -weight * (1 - ratio))) / delta));
+                                zls.Points.Add(new DataPoint(ratio, (judge(weight * deltaRatio, 0.0) - judge(weight * ratio, 0.0)) / delta));
+                                ols.Points.Add(new DataPoint(ratio, (judge(0.0, weight * (1 - deltaRatio)) - judge(0.0, weight * (1 - ratio))) / delta));
+                            }
+                            else
+                            {
+                                cls.Points.Add(new DataPoint(ratio, judge(weight * ratio, weight * (1 - ratio))));
+                                ils.Points.Add(new DataPoint(ratio, judge(weight * ratio, -weight * (1 - ratio))));
+                                zls.Points.Add(new DataPoint(ratio, judge(weight * ratio, 0.0)));
+                                ols.Points.Add(new DataPoint(ratio, judge(0.0, weight * (1 - ratio))));
+                            }
+                        }
+
+                        plot.Series.Add(cls);
+                        plot.Series.Add(ils);
+                        plot.Series.Add(zls);
+                        plot.Series.Add(ols);
+
+                        return plot;
+                    }
+
+                default:
+                    throw new Exception($"Unrecognised mode {mode}; try one of hms, varysr");
+            }
+        }
+    }
+
+    public class CliSaturationAnalysis : ICliMiscPlotter
+    {
+        public string Key => "satan";
+         
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var outfile = clips.Get("out", "report.txt");
+
+            var config = clips.Get("config", CliPlotHelpers.LoadExperimentConfig);
+            var moduleSize = clips.Get("moduleSize", int.Parse, config.Size);
+
+            var cmin = clips.Get("cmin", double.Parse);
+            var cmax = clips.Get("cmax", double.Parse);
+            var cresolution = clips.Get("cresolution", int.Parse);
+
+            var lambdamin = clips.Get("lambdamin", double.Parse, config.JudgementRules.RegularisationFactor);
+            var lambdamax = clips.Get("lambdamax", double.Parse, config.JudgementRules.RegularisationFactor);
+            var lambdaresolution = clips.Get("lambdaresolution", int.Parse, 0);
+
+            var cmajor = clips.Get("cmajor", bool.Parse, false);
+
+            var rand = new CustomMersenneTwister(1);
+            var context = new ModelExecutionContext(rand);
+
+            var plot = new PlotModel() { Title = title };
+
+            var cs = CliPlotHelpers.LinearSamples(cmin, cmax, cresolution).ToArray();
+            var lambdas = CliPlotHelpers.LinearSamples(lambdamin, lambdamax, lambdaresolution).ToArray();
+            
+            plot.Axes.Add(new LinearAxis() { Title = "Saturation Weight", Position = AxisPosition.Left, Key = "w" });
+
+            if (cmajor)
+            {
+                plot.Axes.Add(new LinearAxis() { Title = "Regularisation Factor (lambda)", Position = AxisPosition.Bottom, Key = "lambda" });
+
+                foreach (var c in cs)
+                {
+                    var ss = lambdas.Select(lambda => SaturationInfo.Measure(context, config.DevelopmentRules, c, lambda, moduleSize).SaturationPoint);
+
+                    var ls = new LineSeries() { Title = $"c = {c}" };
+                    ls.Points.AddRange(lambdas.Zip(ss, (x, y) => new DataPoint(x, y)));
+                    plot.Series.Add(ls);
+                }
+            }
+            else
+            {
+                plot.Axes.Add(new LinearAxis() { Title = "Benefit Curve (c)", Position = AxisPosition.Bottom, Key = "c" });
+
+                foreach (var lambda in lambdas)
+                {
+                    var ss = cs.Select(c => SaturationInfo.Measure(context, config.DevelopmentRules, c, lambda, moduleSize).SaturationPoint);
+
+                    var ls = new LineSeries() { Title = $"lambda = {lambda}" };
+                    ls.Points.AddRange(cs.Zip(ss, (x, y) => new DataPoint(x, y)));
+                    plot.Series.Add(ls); 
+                }
+            }
+
+            return plot;
+        }
+    }
+
+    public class CliMiscMcPhenotypeTransition : ICliMiscPlotter
+    {
+        public string Key => "McPhenotypeTransition";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("McPhenotypeTransition Options:\n" +
+                    " - n (number of traits)\n" +
+                    " - omega (ω, slow cut-off)\n" +
+                    " - psi (ψ, slow gradient)\n" +
+                    " - xi (ξ, step)\n" +
+                    " - q (linear share)\n" +
+                    " - a (MMSO shape)\n" +
+                    " - bprob (default 1.0)\n" +
+                    " - bex (default false)\n" +
+                    " - MG (M_G, default 0.6)\n" +
+                    " - MB (M_G, default 1/15N^2)\n" +
+                    " - K (default 1000)\n" +
+                    " - epochs (default 100000)\n" +
+                    " - expfile\n" +
+                    " - devtemplate");
+            }
+
+            int n, k, a, b;
+            string terpMode;
+
+            // target
+            ITarget target;
+            ExperimentConfiguration config = null;
+            if (clips.IsSet("expfile"))
+            {
+                config = CliPlotHelpers.LoadDenseExperimentConfig(clips.Get("expfile"));
+                target = config.Targets[0];
+
+                k = clips.Get("k", int.Parse, 1);
+                n = clips.Get("n", int.Parse, config.Size / k);
+
+                a = clips.Get("a", int.Parse, 0); // number of modules with which we disagree at start
+                b = clips.Get("b", int.Parse, n * k);
+                terpMode = clips.Get("terpMode", "One");
+
+                title = clips.Get("title", $"PhenotypeTransition{terpMode}N{n}K{k}A{a}");
+            }
+            else
+            {
+                k = clips.Get("k", int.Parse);
+                n = clips.Get("n", int.Parse);
+
+                a = clips.Get("a", int.Parse, 0); // number of modules with which we disagree at start
+                b = clips.Get("b", int.Parse, n * k);
+                terpMode = clips.Get("terpMode", "All");
+
+                var p = clips.Get("p", double.Parse);
+
+                target = new Epistatics.MCTargetPlainOnPlain(n, k, p);
+
+                title = clips.Get("title", $"McPhenotypeTransition{terpMode}N{n}K{k}P{p}A{a}");
+            }
+
+            // development template
+            DenseGenome devTemplate = null;
+            if (clips.IsSet("devtemplate"))
+            {
+                if (config == null)
+                    throw new InvalidOperationException("Must specify an expfile (may be a config) to use development templates");
+
+                devTemplate = CliPlotHelpers.LoadDenseGenome(clips.Get("devtemplate"));
+            }
+
+            var perfectp = clips.GetOrCreate(
+                "perfectp",
+                s => Misc.ParseExtremesVector(s, -1.0, 1.0, 0.0),
+                () =>
+                {
+                    Console.WriteLine(target.GetType().FullName);
+                    if (target is IPerfectPhenotypeTarget ppt)
+                        return ppt.PreparePerfectP();
+                    else
+                        return CreateVector.Dense(target.Size, 1.0);
+                });
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new CustomMersenneTwister(seed);
+            var ctx = new ModelExecutionContext(rand);
+
+            ExposureInformation exposureInformation = new ExposureInformation(1); // ignored
+            target.NextExposure(rand, new JudgementRules(0.0, null, 0.0), 0, ref exposureInformation);
+            target.NextGeneration(rand, new JudgementRules(0.0, null, 0.0));
+
+            Phenotype make(double x)
+            {
+                Linear.Vector<double> vec;
+                if (terpMode.SoftEquals("all"))
+                {
+                    vec = Linear.CreateVector.Dense<double>(
+                            n * k,
+                            i => i < (a * k) ? 1
+                                : i < (b * k) ? x / (b - a) * 2 - 1
+                                : -1
+                        );
+                }
+                else if (terpMode.SoftEquals("one"))
+                {
+                    vec = Linear.CreateVector.Dense<double>(
+                            n * k,
+                            i => i < (a * k) ? 1
+                                : i < (b * k) ? Misc.Clamp(((x * k - (i - a * k)) * 2 - 1), -1, +1)
+                                : -1
+                        );
+                }
+                else if (terpMode.SoftEquals("module"))
+                {
+                    vec = Linear.CreateVector.Dense<double>(
+                            n * k,
+                            i => i < (a * k) ? 1
+                                : i < (b * k) ? Misc.Clamp(((x - ((i / k) - a)) * 2 - 1), -1, +1)
+                                : -1
+                        );
+                }
+                else
+                {
+                    throw new Exception("Unrecognised Interpolation Mode: " + terpMode);
+                }
+
+                vec.PointwiseMultiply(perfectp, vec);
+
+                if (devTemplate != null)
+                {
+                    var g = devTemplate.Clone(ctx, vec, null);
+                    return g.Develop(ctx, config.DevelopmentRules);
+                }
+                return new Phenotype(vec);
+            }
+
+            if (clips.IsSet("target"))
+            {
+                return CliExpPlotter.PlotCorrelationMatrix(clips, ((Epistatics.CorrelationMatrixTarget)target).CorrelationMatrix, "Target");
+            }
+
+            if (clips.IsSet("x"))
+            {
+                double x = clips.Get("x", double.Parse);
+                return CliGenomePlotter.PlotExpressionVector(clips, make(x).Vector, ""+x, null);
+            }
+
+            var plot = new PlotModel() { Title = title };
+
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Progress", Key = "x" });
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Benefit", Key = "y" });
+
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            var data = Misc.DRange(0.0, b-a, 1.0 / resolution).Select(x => new DataPoint(x, target.Judge(make(x)))).ToArray();
+
+            if (clips.Get("showminimum", bool.Parse, true))
+            {
+                double minimum = Misc.ArgMin(data, dp => dp.Y).X;
+                plot.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = minimum, Text = $"Minimum at {minimum}" });
+            }
+
+            plot.Series.Add(new LineSeries() { ItemsSource = data });
+
+            return plot;
+        }
+    }
+
+    public class CliTopologyOptimisation : ICliMiscPlotter
+    {
+        public string Key => "TopologyOptimisation";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            int seed = clips.Get("seed", int.Parse, 1);
+            
+            var exp = PopulationExperiment<DenseIndividual>.Load(clips.Get("expfile"));
+            var config = exp.PopulationConfig.ExperimentConfiguration;
+            var rand = new MathNet.Numerics.Random.MersenneTwister(seed, false);
+            var context = new ModelExecutionContext(rand);
+
+            int targetIdx = clips.Get("target", int.Parse, 0);
+            var target = config.Targets[targetIdx];
+            
+            if (clips.Get("resetTarget", bool.Parse, false))
+            {
+                int exposureEpoch = clips.Get("exposureepoch", int.Parse, 1);
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+            }
+
+            target.NextGeneration(rand, config.JudgementRules);
+
+            var template = exp.Population.PeekAll()[0];
+            var templateGenome = template.Genome;
+            
+            var maxWeight = clips.GetOrCreate("maxweight", double.Parse, () => template.Genome.TransMat.Enumerate().Sum());
+            var resolution = clips.Get("resolution", int.Parse, 10);
+
+            var threshold = template.Genome.TransMat.Enumerate().Select(Math.Abs).Max() / 10;
+            console.WriteLine("threshold: " + threshold);
+            var paramEntries = templateGenome.TransMat.EntriesWhere(x => Math.Abs(x) > threshold).ToArray();
+            foreach (var e in paramEntries)
+                console.WriteLine(e);
+            var optimised = Optimise(context, templateGenome, paramEntries, config.DevelopmentRules, target, config.JudgementRules, maxWeight, resolution);
+            
+            // plot
+            var plot = new PlotModel { Title = title };
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Total Weight", Key = "x", Minimum = 0 });
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Regulatory Assignments", Key = "y", Minimum = 0 });
+
+            LineSeries[] ls = new LineSeries[paramEntries.Length];
+            for (int i = 0; i < paramEntries.Length; i++)
+            {
+                var e = paramEntries[i];
+                ls[i] = new LineSeries() { Title = e.ToString() };
+                foreach (var vk in optimised)
+                {
+                    ls[i].Points.Add(new DataPoint(vk.Key, vk.Value.TransMat[e.Row, e.Col]));
+                }
+                plot.Series.Add(ls[i]);
+            }
+
+            return plot;
+        }
+
+        public static Dictionary<double, DenseGenome> Optimise(ModelExecutionContext context, DenseGenome template, IReadOnlyList<MatrixEntryAddress> paramEntries, DevelopmentRules drules, ITarget target, JudgementRules jrules, double maxTotalWeight, int resolution)
+        {
+            var results = new Dictionary<double, DenseGenome>();
+
+            var paramCount = paramEntries.Count;
+            var genome = template.Clone(context);
+            var bestGenome = template.Clone(context);
+            var phenotype = genome.Develop(context, drules); // really need to develop; just an easy way to get an appropriate Phenotype
+            var bestFitness = double.NegativeInfinity;
+            
+            var paramArray = new double[paramCount];
+            var maxes = Misc.Create(paramCount, _ => resolution * paramCount + 1);
+            var signs = paramEntries.Select(e => Math.Sign(template.TransMat[e.Row, e.Col])).ToArray();
+            for (double total = maxTotalWeight / resolution; total <= maxTotalWeight; total += maxTotalWeight / resolution)
+            {
+                foreach (var p in Combinatorics.EnumerateTotalUnorderedParameterisations(paramCount, total, resolution))
+                {
+                    for (int i = 0; i < paramCount; i++)
+                    {
+                        genome.TransMat[paramEntries[i].Row, paramEntries[i].Col] = p[i];
+                    }
+
+                    genome.DevelopInto(phenotype, context, drules);
+                    var j = MultiMeasureJudgement.Judge(genome, phenotype, jrules, target);
+                    var f = j.CombinedFitness;
+
+                    if (f > bestFitness)
+                    {
+                        // swap them around
+                        var t = bestGenome;
+                        bestGenome = genome;
+                        bestFitness = f;
+                        genome = t;
+                    }
+                }
+
+                results.Add(total, bestGenome.Clone(context));
+                bestFitness = double.NegativeInfinity;
+            }
+
+            return results;
+        }
+    }
+
+    public class CliQ : ICliMiscPlotter
+    {
+        public string Key => "Q";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var expFile = clips.Get("expfile", PopulationExperiment<DenseIndividual>.Load, null);
+
+            int Q;
+            int size;
+            double mg;
+            double gmin;
+            double gmax;
+            NoiseType mgType = NoiseType.Uniform; // make customisable
+            if (expFile != null)
+            {
+                var rrules = expFile.PopulationConfig.ExperimentConfiguration.ReproductionRules;
+                var config = expFile.PopulationConfig.ExperimentConfiguration;
+
+                mg = clips.Get("mg", double.Parse, rrules.InitialStateMutationSize);
+                gmin = clips.Get("gmin", double.Parse, rrules.InitialStateClamping.Min);
+                gmax = clips.Get("gmax", double.Parse, rrules.InitialStateClamping.Max);
+                mgType = rrules.InitialStateMutationType;
+                
+                size = clips.Get("size", int.Parse, config.Size);
+
+                if (expFile.PopulationConfig.PopulationResetOperation is NeutralMutationPopulationResetOperation nmpro)
+                {
+                    Q = clips.Get("Q", Misc.ParseInt, nmpro.NeutralMutationCount);
+                }
+                else
+                {
+                    Q = clips.Get("Q", Misc.ParseInt);
+                }
+            }
+            else
+            {
+                Q = clips.Get("Q", Misc.ParseInt);
+                mg = clips.Get("mg", double.Parse);
+                gmin = clips.Get("gmin", double.Parse);
+                gmax = clips.Get("gmax", double.Parse);
+                size = clips.Get("n", int.Parse);
+            }
+
+            int sampleCount = clips.Get("count", int.Parse, 1000);
+            int subSize = clips.Get("subsize", int.Parse);
+            int binCount = clips.Get("binCount", int.Parse, 20);
+            int seed = clips.GetOrCreate("seed", int.Parse, () => CliExp.SeedSource.Next());
+
+            var rnd = new Random(seed);
+
+            var samples = Enumerable.Range(0, sampleCount).Select(_ => Sample(rnd, Q, mg, gmin, gmax, mgType, size)).Select(s => s.Take(subSize).Average()).ToArray();
+            var hs = new HistogramSeries();
+            var items = HistogramHelpers.Collect(samples, HistogramHelpers.CreateUniformBins(gmin, gmax, binCount), new BinningOptions(BinningOutlierMode.RejectOutliers, BinningIntervalType.InclusiveLowerBound, BinningExtremeValueMode.IncludeExtremeValues));
+            hs.Items.AddRange(items);
+
+            var mean = MathNet.Numerics.Statistics.Statistics.Mean(samples);
+            var variance = MathNet.Numerics.Statistics.Statistics.Variance(samples);
+
+            var plot = new PlotModel();
+            plot.Title = title;
+            plot.Subtitle = $"Mean={mean} Variance={variance}";
+
+            var slices = clips.Get("slices", CliPlotHelpers.ParseDoubleList, Array.Empty<double>());
+
+            foreach (var slice in slices)
+            {
+                var gprop = samples.Count(x => x > slice) / (double)samples.Length;
+
+                var la = new LineAnnotation() { Text = $"{slice:0.000}: {gprop*100:00.00}%", Type = LineAnnotationType.Vertical, X = slice };
+                plot.Annotations.Add(la);
+            }
+
+            var interactiveSlice = new LineAnnotation() { Text = null, Type = LineAnnotationType.Vertical, X = -1000, Color = OxyColors.Blue, TextVerticalAlignment = VerticalAlignment.Top  };
+            plot.MouseMove += (s, e) =>
+            {
+                plot.Annotations.Remove(interactiveSlice);
+                
+                var slice = plot.DefaultXAxis.InverseTransform(e.Position.X);
+                var gprop = samples.Count(x => x > slice) / (double)samples.Length;
+                if (slice >= gmin && slice <= gmax)
+                {
+                    interactiveSlice.Text = $"{slice:0.000}: {gprop * 100:00.00}%";
+                    interactiveSlice.X = slice;
+                    plot.Annotations.Add(interactiveSlice);
+                    plot.InvalidatePlot(false);
+                }
+            };
+            plot.MouseDown += (s, e) =>
+            {
+                if (e.ChangedButton == OxyMouseButton.Left)
+                {
+                    interactiveSlice = new LineAnnotation() { Text = null, Type = LineAnnotationType.Vertical, X = -1000, Color = OxyColors.Blue };
+                }
+            };
+
+            if (clips.IsSet("mctmins"))
+            {
+                var mcinfo = clips.Get("mctmins", CliPlotHelpers.ParseDoubleList);
+                bool tmsMeanAdjusted = clips.Get("tmsMeanAdjusted", bool.Parse, true);
+                var mcn = (int)mcinfo[0];
+                var mck = (int)mcinfo[1];
+                var mcp = mcinfo[2];
+
+                for (int a = 0; a < mcn; a++)
+                {
+                    double T = mck;
+                    double R = (mcn - 2 * a - 1) * (tmsMeanAdjusted ? -mean : 1.0);
+                    double tms = -mcp * R;
+                    var gprop = samples.Count(x => x > tms) / (double)samples.Length;
+
+                    var la = new LineAnnotation() { Text = $"MC{a} at {tms:0.0000}: {gprop*100:00.00}%", Type = LineAnnotationType.Vertical, X = tms, Color = OxyColors.Red };
+                    plot.Annotations.Add(la);
+                }
+            }
+
+            plot.Series.Add(hs);
+            return plot;
+        }
+
+        public static double[] Sample(Random rnd, int Q, double mg, double gMin, double gMax, NoiseType mgType, int size)
+        {
+            var x = Misc.Create(size, gMin);
+
+            for (int i = 0; i < Q; i++)
+            {
+                int r = rnd.Next(size);
+                x[r] = Misc.Clamp(x[r] + Misc.NextNoise(rnd, mg, mgType), gMin, gMax);
+            }
+
+            return x;
+        }
+    }
+
+    public class CliDevStream : ICliMiscPlotter
+    {
+        public string Key => "DevStream";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var expFile = clips.Get("expfile", PopulationExperiment<DenseIndividual>.Load, null);
+            var sampleCount = clips.Get("sampleCount", int.Parse, 10);
+
+            var config = expFile.PopulationConfig.ExperimentConfiguration;
+            var grange = config.InitialStateResetRange;
+
+            int g0 = clips.Get("g0", int.Parse, 0);
+            int g1 = clips.Get("g1", int.Parse, 1);
+
+            var plot = new PlotModel() { Title = "DevStreams" };
+            var template = expFile.Population.ExtractAll()[0];
+
+            var rand = new CustomMersenneTwister(1);
+            var ctx = new ModelExecutionContext(rand);
+
+            double[][] trajectories = null;
+
+            for (int i = 0; i <= sampleCount; i++)
+            {
+                var t0 = grange.Min + (grange.Max - grange.Min) * (i / (double)sampleCount);
+                for (int j = 0; j <= sampleCount; j++)
+                {
+                    var t1 = grange.Min + (grange.Max - grange.Min) * (j / (double)sampleCount);
+
+                    var g = CreateVector.Dense<double>(config.Size);
+
+                    g[g0] = t0;
+                    g[g1] = t1;
+
+                    var individual = template.Clone(ctx);
+                    individual.Genome.CopyOverInitialState(g);
+                    individual.Genome.DevelopWithTrajectories(rand, config.DevelopmentRules, ref trajectories);
+                }
+            }
+
+
+
+            return plot;
+        }
+    }
+
+    public class CliHi : ICliMiscPlotter
+    {
+        public string Key => "Hi";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var config = clips.Get("expfile", CliPlotHelpers.LoadDenseExperimentConfig);
+            var modules = clips.Get("modules", Modular.MultiModulesStringParser.Instance.Parse);
+
+            int targetIdx = clips.Get("target", int.Parse, 0);
+            var target = config.Targets[targetIdx];
+
+            var perfectp = clips.GetOrCreate(
+                "perfectp",
+                s => Misc.ParseExtremesVector(s, -1.0, 1.0, 0.0),
+                () =>
+                {
+                    if (target is IPerfectPhenotypeTarget ppt)
+                        return ppt.PreparePerfectP();
+                    else
+                        return CreateVector.Dense(target.Size, 1.0);
+                });
+
+            var k = clips.Get("k", int.Parse, 1);
+            var n = clips.Get("n", int.Parse, config.Size / k);
+            
+            var a = clips.Get("a", int.Parse, 0); // number of modules with which we disagree at start
+            var b = clips.Get("b", int.Parse, n * k);
+            var terpMode = clips.Get("terpMode", "One");
+
+            int seed = clips.Get("seed", int.Parse, 1);
+            var rand = new CustomMersenneTwister(seed);
+            var ctx = new ModelExecutionContext(rand);
+
+            var output = clips.Get("output", "Fitness");
+
+            ExposureInformation exposureInformation = new ExposureInformation(1); // ignored
+            target.NextExposure(rand, new JudgementRules(0.0, null, 0.0), 0, ref exposureInformation);
+            target.NextGeneration(rand, new JudgementRules(0.0, null, 0.0));
+
+            var parentMap = modules.GetDefaultLeaderMap();
+            var correls = Misc.Correlate(perfectp);
+
+            DenseGenome makeGenome(double x)
+            {
+                Linear.Matrix<double> mat = Linear.CreateMatrix.Dense<double>(n * k, n * k);
+                if (terpMode.SoftEquals("one"))
+                {
+                    for (int i = 0; i < parentMap.Length; i++)
+                    {
+                        var j = parentMap[i];
+                        if (i == j)
+                        {
+                            mat[i, i] = 1.0;
+                        }
+                        else
+                        {
+                            mat[i, i] = Misc.Clamp((x * k - (i - a * k)), 0, +1);
+                            mat[i, j] = Misc.Clamp(1.0 - (x * k - (i - a * k)), 0, +1);
+                        }
+                    }
+                }
+                else
+                {
+                    throw new Exception("Unrecognised Interpolation Mode: " + terpMode);
+                }
+
+                mat.PointwiseMultiply(correls);
+
+                var genome = new DenseGenome(perfectp, mat);
+                return genome;
+            }
+
+            double judge(double x)
+            {
+                var genome = makeGenome(x);
+                var phenotype = genome.Develop(ctx, config.DevelopmentRules);
+                var mmq = MultiMeasureJudgement.Judge(genome, phenotype, config.JudgementRules, target);
+
+                if (output == "Fitness")
+                    return mmq.CombinedFitness;
+                else if (output == "Benefit")
+                    return mmq.Benefit;
+                else if (output == "Cost")
+                    return mmq.Cost;
+                else
+                    throw new Exception("Unrecognised output: " + output);
+            }
+
+            if (clips.IsSet("target"))
+            {
+                return CliExpPlotter.PlotCorrelationMatrix(clips, ((Epistatics.CorrelationMatrixTarget)target).CorrelationMatrix, "Target");
+            }
+
+            if (clips.IsSet("x"))
+            {
+                double x = clips.Get("x", double.Parse);
+                var genome = makeGenome(x);
+                Analysis.SaveGenome($"genome_{x}.dat", genome);
+                return CliGenomePlotter.PlotDtm(clips, genome.TransMat, "" + x);
+            }
+
+            var plot = new PlotModel() { Title = title };
+
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Progress", Key = "x" });
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = output, Key = "y" });
+
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            var data = Misc.DRange(0.0, b - a, 1.0 / resolution).Select(x => new DataPoint(x, judge(x))).ToArray();
+
+            double minimum = Misc.ArgMin(data, dp => dp.Y).X;
+            plot.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = minimum, Text = $"Minimum at {minimum}" });
+
+            plot.Series.Add(new LineSeries() { ItemsSource = data });
+
+            return plot;
+        }
+    }
+
+    public class CliSquash : ICliMiscPlotter
+    {
+        public string Key => "squash";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var config = clips.Get("expfile", CliPlotHelpers.LoadDenseExperimentConfig);
+
+            var plot = new PlotModel() { Title = title };
+
+            var xmin = clips.Get("xmin", double.Parse, -config.DevelopmentRules.UpdateRate / config.DevelopmentRules.DecayRate);
+            var xmax = clips.Get("xmax", double.Parse, +config.DevelopmentRules.UpdateRate / config.DevelopmentRules.DecayRate);
+
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "Input", Key = "x" });
+            plot.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Output", Key = "y" });
+
+            int resolution = clips.Get("resolution", int.Parse, 1000);
+            var data = Misc.DRange(xmin, xmax, 1.0 / resolution).Select(x => new DataPoint(x, config.DevelopmentRules.Squash.Squash(x))).ToArray();
+
+            plot.Series.Add(new LineSeries() { ItemsSource = data });
+
+            return plot;
+        }
+    }
+
+    public class CliCoins : ICliMiscPlotter
+    {
+        public string Key => "coins";
+
+        public PlotModel Plot(TextWriter console, CliParams clips, string title)
+        {
+            var rand = new CustomMersenneTwister(1);
+
+            // TODO: add Z support
+
+            // generate random strings
+            // take hebbian integral
+            // plot distribution of inter-module correlations (they are all spurious)
+            // what we really want to know is the standard deviation 
+
+            if (clips.IsSet("log") || clips.IsSet("linear"))
+            {
+                var ns = clips.Get("n", Misc.ParseIntList).OrderBy(x => x); // number of modules
+                var es = clips.Get("e", Misc.ParseIntList); // number of epochs / samples
+
+                var plot = new PlotModel();
+                var xaxis = clips.IsSet("log")
+                    ? (Axis)new LogarithmicAxis() { Title = "n", Position = AxisPosition.Bottom }
+                    : (Axis)new LinearAxis() { Title = "n", Position = AxisPosition.Bottom };
+                var yaxis = new LinearAxis() { Title = "Std-dev", Position = AxisPosition.Left };
+                plot.Axes.Add(xaxis);
+                plot.Axes.Add(yaxis);
+
+                foreach (var e in es)
+                {
+                    var ls = new LineSeries() { Title = $"e = {e}" };
+                    foreach (var n in ns)
+                    {
+                        var samples = QuickCoinSample(rand, n, e);
+                        var variance = MathNet.Numerics.Statistics.Statistics.Variance(samples);
+                        var stddev = Math.Sqrt(variance);
+                        ls.Points.Add(new DataPoint(n, stddev));
+                    }
+                    plot.Series.Add(ls);
+                }
+
+                return plot;
+            }
+            else
+            {
+                var n = clips.Get("n", int.Parse); // number of modules
+                var e = clips.Get("e", int.Parse); // number of epochs / samples
+
+                var binCount = clips.Get("bincount", int.Parse, e * 2 + 1);
+
+                var plot = QuickCoinHistogram(e, QuickCoinSample(rand, n, e), binCount);
+                plot.Title = title;
+                return plot;
+            }
+        }
+
+        public static PlotModel QuickCoinHistogram(int e, IEnumerable<double> samples, int binCount)
+        {
+            var hs = new HistogramSeries();
+            var items = HistogramHelpers.Collect(samples, HistogramHelpers.CreateUniformBins(-e -0.5, +e + 0.5, binCount), new BinningOptions(BinningOutlierMode.RejectOutliers, BinningIntervalType.InclusiveLowerBound, BinningExtremeValueMode.IncludeExtremeValues));
+            hs.Items.AddRange(items);
+
+            var mean = MathNet.Numerics.Statistics.Statistics.Mean(samples);
+            var variance = MathNet.Numerics.Statistics.Statistics.Variance(samples);
+            var stddev = Math.Sqrt(variance);
+
+            var xaxis = new LinearAxis() { Title = "Value", Position = AxisPosition.Bottom };
+            var yaxis = new LinearAxis() { Title = "Frequency", Position = AxisPosition.Left };
+
+            var plot = new PlotModel();
+            plot.Subtitle = $"Mean={mean} Variance={variance}";
+            plot.Series.Add(hs);
+            plot.Axes.Add(xaxis);
+            plot.Axes.Add(yaxis);
+            return plot;
+        }
+
+        public static IReadOnlyList<double> QuickCoinSample(Random rand, int n, int e)
+        {
+            var mat = CreateMatrix.Dense<double>(n, n);
+            var temp = CreateMatrix.Dense<double>(n, n);
+            var v = CreateVector.Dense<double>(n);
+
+            for (int i = 0; i < e; i++)
+            {
+                Misc.FillRandom(v, rand, 1, NoiseType.Binary);
+                v.OuterProduct(v, temp);
+                mat.Add(temp, mat);
+            }
+
+            var samples = new List<double>();
+            for (int i = 0; i < n; i++)
+            {
+                for (int j = i + 1; j < n; j++)
+                {
+                    samples.Add(mat[i, j]);
+                }
+            }
+
+            return samples;
+        }
+    }
+
+    public static class GenomeHelpers
+    {
+        public static Func<double, Linear.Matrix<double>> PrepareDtmCreater(string dtm, int N)
+        {
+            return dtm == "identity" ? (Func<double, Linear.Matrix<double>>)(weight => GenomeHelpers.CreateIdentity(N, 1, weight / N)) :
+                dtm == "dense" ? (Func<double, Linear.Matrix<double>>)(weight => GenomeHelpers.CreateDense(N, 1, weight / (N * N))) :
+                dtm == "husky" ? (Func<double, Linear.Matrix<double>>)(weight => GenomeHelpers.CreateHusky(N, 1, weight / N)) :
+                throw new Exception("Unrecognised dtm, try one of 'identity, dense, husky'");
+        }
+
+        public static Linear.Matrix<double> CreateIdentity(int moduleSize, int moduleCount, double commonWeight)
+        {
+            int n = moduleCount * moduleSize;
+            Linear.Matrix<double> dtm = Linear.CreateMatrix.Dense(n, n, (i, j) => i == j ? commonWeight : 0.0);
+            return dtm;
+        }
+
+        public static Linear.Matrix<double> CreateDense(int moduleSize, int moduleCount, double commonWeight)
+        {
+            int n = moduleCount * moduleSize;
+            Linear.Matrix<double> dtm = Linear.CreateMatrix.Dense(n, n, (i, j) => i / moduleSize == j / moduleSize ? commonWeight : 0.0);
+            return dtm;
+        }
+
+        public static Linear.Matrix<double> CreateHusky(int moduleSize, int moduleCount, double commonWeight)
+        {
+            int n = moduleCount * moduleSize;
+            Linear.Matrix<double> dtm = Linear.CreateMatrix.Dense(n, n, (i, j) => i / moduleSize == j / moduleSize && j % moduleSize == 0 ? commonWeight : 0.0);
+            return dtm;
+        }
+
+        public static Linear.Matrix<double> CreateDense(Linear.Vector<double> g, Modular.Modules modules, double onblock, double offblock)
+        {
+            int n = modules.ElementCount;
+            Linear.Matrix<double> dtm = Linear.CreateMatrix.Dense(n, n, offblock);
+            foreach (var module in modules.ModuleAssignments)
+            {
+                foreach (var i in module)
+                {
+                    foreach (var j in module)
+                    {
+                        dtm[i, j] = onblock * Math.Sign(g[i] * g[j]);
+                    }
+                }
+            }
+            return dtm;
+        }
+
+        public static Linear.Matrix<double> CreateHusky(Modular.Modules modules, double commonWeight)
+        {
+            int n = modules.ElementCount;
+            Linear.Matrix<double> dtm = Linear.CreateMatrix.Dense(n, n, 0.0);
+            foreach (var module in modules.ModuleAssignments)
+            {
+                foreach (var i in module)
+                {
+                    dtm[i, module[0]] = commonWeight;
+                }
+            }
+            return dtm;
+        }
+
+        /// <summary>
+        /// Enumerates all vectors of length <paramref name="size"/> where each entry is either <c>1</c> or <c>-1</c>.
+        /// </summary>
+        /// <param name="size">The length of the vectors.</param>
+        /// <returns></returns>
+        public static IEnumerable<Linear.Vector<double>> EnumerateAllBinary(int size)
+        {
+            double[] c = new double[size];
+
+            for (int i = 0; i < size; i++)
+            {
+                c[i] = -1;
+            }
+
+            while (true)
+            {
+                yield return Linear.CreateVector.DenseOfArray(c);
+
+                int i = 0;
+                for (; i < size; i++)
+                {
+                    if (c[i] == -1)
+                    {
+                        c[i] = 1;
+                        break;
+                    }
+                    else // c[i] == 1
+                    {
+                        c[i] = -1;
+                    }
+                }
+
+                if (i == size)
+                    yield break;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliPresets.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliPresets.cs
new file mode 100644
index 0000000..93086d0
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliPresets.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace M4M
+{
+    public class CliPresets
+    {
+        public string Key { get; }
+        
+        public Dictionary<string, string> Presets { get; }
+
+        public CliPresets(string key, Dictionary<string, string> presets)
+        {
+            Key = key;
+            Presets = presets;
+        }
+
+        public CliPresets(string key, bool caseSensitive = false)
+        {
+            Key = key;
+            Presets = new Dictionary<string, string>(caseSensitive ? StringComparer.InvariantCulture : StringComparer.InvariantCultureIgnoreCase);
+        }
+
+        public void Apply(TextWriter console, CliParams clips)
+        {
+            var presets = clips.Get(Key, null);
+
+            if (presets != null)
+            {
+                foreach (var preset in StringHelpers.Split(presets))
+                {
+                    if (Presets.TryGetValue(preset, out var presetArgs))
+                    {
+                        clips.ConsumeLine(presetArgs);
+                    }
+                    else
+                    {
+                        throw new Exception("Unrecognise " + Key + " preset '" + preset + "'");
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliProject.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliProject.cs
new file mode 100644
index 0000000..6cade70
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliProject.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public class CliProject : ICliProvider
+    {
+        public string Key => "project";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Projects stuff from other stuff");
+                console.WriteLine(" - project, the source file to project from (e.g. wholesamples)");
+            }
+
+            string source = clips.Get("project");
+            string fname = System.IO.Path.GetFileName(source);
+
+            if (fname.StartsWith("wholesamples"))
+            {
+                ProjectFromWholeSamples(console, clips);
+            }
+        }
+
+        // will assume the experiment type for the experiment type
+        public void ProjectFromWholeSamples(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Must provide the following to extract from a wholesamples:");
+                console.WriteLine(" - project, the wholesamples source file to project from");
+                console.WriteLine(" - expfile, the experiment save file to reconfigure");
+                console.WriteLine(" - outdir, the experiment output directory");
+                console.WriteLine(" - start, the start epoch (optional)");
+                console.WriteLine(" - end, the start epoch (optional)");
+                console.WriteLine(" - postfix, the output postfix");
+                console.WriteLine(" - noNextExposure, disables the calls to nextExposure (default false)");
+            }
+
+            string wholeSamplesSource = clips.Get("project");
+            int? start = clips.Get("start", s => (int?)int.Parse(s), null);
+            int? end = clips.Get("end", s => (int?)int.Parse(s), null);
+            string expFile = clips.Get("expfile");
+            string outdir = clips.Get("outdir", System.IO.Path.GetDirectoryName(wholeSamplesSource));
+            string postfix = clips.Get("postfix");
+            bool noNextExposure = clips.Get("noNextExposure", bool.Parse, false);
+            ProjectionMode projectionMode = clips.Get("projectionmode", ParseProjectionMode);
+
+            string outfile = System.IO.Path.Combine(outdir, "wholesamples" + postfix + ".dat");
+
+            // load the experiment - inferring its type - and give us an IExperimentExtractor to work with
+            var expExtractor = PopulationExperimentHelpers.LoadUnknownType(expFile, new ExperimentExtractorExperimentReceiver());
+
+            var projectionPreparer = new BasicExtractorWholeSampleProjectorPreparer(projectionMode, noNextExposure);
+            expExtractor.ProjectFromWholeSamples(wholeSamplesSource, start, end, outfile, projectionPreparer);
+        }
+
+        public static ProjectionMode ParseProjectionMode(string str)
+        {
+            if (str == null)
+            {
+                return ProjectionMode.Spin;
+            }
+            else if (str.Equals("spin", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return ProjectionMode.Spin;
+            }
+            else if (str.Equals("evalonly", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return ProjectionMode.EvalOnly;
+            }
+
+            throw new ArgumentException("Unrecognised ProjectionMode: \"" + str + "\"");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliReconfig.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliReconfig.cs
new file mode 100644
index 0000000..6f0385e
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliReconfig.cs
@@ -0,0 +1,297 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public class CliReconfig : ICliProvider
+    {
+        public static readonly CliPresets DefaultPresets = new CliPresets("reconfigpresets", new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
+        {
+            ["disableB"] = "rrules.mb=0", // we leave rb alone
+            ["hillcimber"] = "pop.size=1 hillclimbermode=true elitecount=1", // we leave rb alone
+        });
+
+        public string Key => "reconfig";
+
+        public CliPresets Presets { get; }
+
+        public CliReconfig(CliPresets presets)
+        {
+            Presets = presets;
+        }
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            Presets.Apply(console, clips);
+
+            var rand = new MathNet.Numerics.Random.MersenneTwister(false);
+
+            var targetAddress = clips.Get("reconfig");
+            var outdir = clips.Get("outdir", "");
+            var postfix = clips.Get("postfix");
+            
+            var target = PopulationExperiment<DenseIndividual>.Load(targetAddress);
+            
+            var popExp = Reconfig(console, clips, target, rand, outdir + postfix);
+            
+            if (!clips.IsSet("quiet"))
+                console.WriteLine("Writing reconfiged to " + popExp.FileStuff.OutDir);
+
+            PopulationExperimentRunners.WriteOutInitials(rand, popExp);
+            popExp.Save("starter", false);
+        }
+
+        public static DevelopmentRules Reconfig(CliParams clips, DevelopmentRules drules)
+        {
+            var timeSteps = clips.Get("drules.T", int.Parse, drules.TimeSteps);
+            var updateRate = clips.Get("drules.tau1", double.Parse, drules.UpdateRate);
+            var decayRate = clips.Get("drules.tau2", double.Parse, drules.DecayRate);
+            var rescaleScale = clips.Get("drules.rescaleScale", double.Parse, drules.RescaleScale);
+            var rescaleOffset = clips.Get("drules.rescaleOffset", double.Parse, drules.RescaleOffset);
+            var squash = drules.Squash;
+            drules = new DevelopmentRules(timeSteps, updateRate, decayRate, squash, rescaleScale, rescaleOffset);
+
+            return drules;
+        }
+
+        public static ReproductionRules Reconfig(CliParams clips, ReproductionRules rrules)
+        {
+            var initialTraitUpdates = clips.Get("rrules.CG", int.Parse, rrules.InitialTraitUpdates);
+            var exclusiveBMutation = clips.Get("rrules.bex", bool.Parse, rrules.ExclusiveBMutation);
+            var dtmMutationRate = clips.Get("rrules.RB", double.Parse, rrules.DevelopmentalTransformationMatrixRate);
+            var dtmMutationSize = clips.Get("rrules.MB", double.Parse, rrules.DevelopmentalTransformationMatrixMutationSize);
+            var isMutationSize = clips.Get("rrules.MG", double.Parse, rrules.InitialStateMutationSize);
+            var gNoiseType = clips.Get("rrules.MGtype", ExperimentParsing.ParseNoiseType, rrules.InitialStateMutationType);
+            var bNoiseType = clips.Get("rrules.MBtype", ExperimentParsing.ParseNoiseType, rrules.DevelopmentalTransformationMatrixMutationType);
+            rrules = new ReproductionRules(isMutationSize, dtmMutationSize, dtmMutationRate, exclusiveBMutation, initialTraitUpdates, rrules.InitialStateClamping, gNoiseType, bNoiseType);
+
+            return rrules;
+        }
+        
+        public static JudgementRules Reconfig(CliParams clips, JudgementRules jrules)
+        {
+            var lambda = clips.Get("jrules.lambda", double.Parse, jrules.RegularisationFactor);
+            var kappa = clips.Get("jrules.kappa", double.Parse, jrules.NoiseFactor);
+            var regularisationFunction = clips.Get("jrules.regfunc", ExperimentParsing.ParseRegFunction, jrules.RegularisationFunction);
+            jrules = new JudgementRules(lambda, regularisationFunction, kappa);
+
+            return jrules;
+        }
+
+        public static ITarget[] Retarget(CliParams clips, ITarget[] targets)
+        {
+            targets = targets.ToArray(); // soft copy
+
+            if (clips.IsSet("SaturateTargets"))
+            {                
+                var satMin = clips.Get("satMin", double.Parse, -1.0);
+                var satThreshold = clips.Get("satThreshold", double.Parse, 0.0);
+                var satMax = clips.Get("satMax", double.Parse, +1.0);
+                
+                for (int i = 0; i < targets.Length; i++)
+                {
+                    if (targets[i] is SaturationTarget st)
+                    {
+                       targets[i] = st.Target;
+                    }
+
+                    targets[i] = new SaturationTarget(targets[i], satMin, satThreshold, satMax);
+                }
+            }
+
+            return targets;
+        }
+
+        public static ExperimentConfiguration Reconfig(TextWriter console, CliParams clips, ExperimentConfiguration config)
+        {
+            // drules
+            var drules = config.DevelopmentRules;
+            drules = Reconfig(clips, drules);
+            
+            // rrules
+            var rrules = config.ReproductionRules;
+            rrules = Reconfig(clips, rrules);
+
+            // jrules
+            var jrules = config.JudgementRules;
+            jrules = Reconfig(clips, jrules);
+
+            // config
+            var epochs = clips.Get("epochs", int.Parse, config.Epochs);
+            var K = clips.Get("K", int.Parse, config.GenerationsPerTargetPerEpoch);
+            var initialStateResetProbability = clips.Get("initialStateResetProbability", double.Parse, config.InitialStateResetProbability);
+            var targets = clips.GetOrCreate("targets", targetsFile => ExperimentParsing.ParseEpistaticTargetPackage(console, clips, "default", targetsFile).Targets, () => Retarget(clips, config.Targets.ToArray()));
+            if (clips.IsSet("targetpackage"))
+                targets = ExperimentComposition.Typical.DefaultDenseExperimentComposer.TargetPackageComposer.Compose(console, clips).Targets.ToArray();
+            var targetCycler = clips.Get("targetCycler", targetsFile => ExperimentExtractorExperimentReceiver.LoadExtractorFromFile(targetsFile).ExperimentConfiguration.TargetCycler, config.TargetCycler);
+            config = new ExperimentConfiguration(config.Size, targets, targetCycler, epochs, K, initialStateResetProbability, config.InitialStateResetRange, false, drules, rrules, jrules);
+
+            return config;
+        }
+
+        public static PopulationExperimentConfig<DenseIndividual> Reconfig(TextWriter console, CliParams clips, PopulationExperimentConfig<DenseIndividual> popConfig)
+        {
+            // config
+            var config = popConfig.ExperimentConfiguration;
+            config = Reconfig(console, clips, config);
+
+            // popConfig
+            var eliteCount = clips.Get("elitecount", int.Parse, popConfig.EliteCount);
+            var hillclimberMode = clips.Get("hillclimberMode", bool.Parse, popConfig.HillclimberMode);
+            var endTargetOperation = popConfig.PopulationEndTargetOperation;
+            var spinner = clips.Get("spinner", ExperimentComposition.DefaultPopulationSpinnerComposer<DenseIndividual>.Parse, popConfig.CustomPopulationSpinner);
+            var selectorPreparer = clips.Get("selectorpreparer", ExperimentParsing.ParseSelectorPreparer<DenseIndividual>, popConfig.SelectorPreparer);
+            var resetOperation = clips.Get("resetoperation", ExperimentParsing.ParsePopulationResetOperation, popConfig.PopulationResetOperation);
+            
+            popConfig = new PopulationExperimentConfig<DenseIndividual>(config, selectorPreparer, eliteCount, hillclimberMode, endTargetOperation, resetOperation, spinner);
+
+            return popConfig;
+        }
+
+        public static Population<DenseIndividual> Reconfig(CliParams clips, Population<DenseIndividual> population, MathNet.Numerics.Random.RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig)
+        {
+            var config = popConfig.ExperimentConfiguration;
+
+            if (clips.IsSet("population"))
+            {
+                population = clips.Get("population", populationFile => PopulationExperiment<DenseIndividual>.Load(populationFile).Population);
+            }
+            
+            if (clips.IsSet("genome"))
+            {
+                var genome = clips.Get("genome", genomeFile => Analysis.LoadGenome(genomeFile));
+                var context = new ModelExecutionContext(rand);
+                var drules = config.DevelopmentRules;
+
+                var newPopulation = new List<DenseIndividual>();
+                foreach (var oi in population.ExtractAll())
+                {
+                    oi.Genome.CopyOverInitialState(genome.InitialState);
+                    oi.Genome.CopyOverTransMat(genome.TransMat);
+                    newPopulation.Add(DenseIndividual.Develop(oi.Genome, context, drules, oi.Epigenetic));
+                }
+                
+                population = new Population<DenseIndividual>(newPopulation);
+            }
+
+            if (clips.IsSet("g0"))
+            {
+                var context = new ModelExecutionContext(rand);
+                var drules = config.DevelopmentRules;
+
+                var g0string = clips.Get("g0");
+                var g0 = Misc.ParseExtremesVector(g0string, config.ReproductionRules.InitialStateClamping.Min, config.ReproductionRules.InitialStateClamping.Max, 0.0);
+
+                var newPopulation = new List<DenseIndividual>();
+                foreach (var oi in population.ExtractAll())
+                {
+                    oi.Genome.CopyOverInitialState(g0);
+                    newPopulation.Add(DenseIndividual.Develop(oi.Genome, context, drules, oi.Epigenetic));
+                }
+                
+                population = new Population<DenseIndividual>(newPopulation);
+            }
+
+            if (clips.IsSet("b0"))
+            {
+                var context = new ModelExecutionContext(rand);
+                var drules = config.DevelopmentRules;
+
+                var b0string = clips.Get("b0");
+                var b0 = ExperimentParsing.ParseMatrix(b0string);
+
+                if (b0 == null && clips.IsSet("templategenome"))
+                {
+                    var templateGenome = Analysis.LoadGenome(clips.Get("templategenome"));
+                    b0 = templateGenome.TransMat;
+                }
+
+                if (clips.IsSet("rndnormalb"))
+                {
+                    var mag = clips.Get("rndnormalb", double.Parse, 1.0);
+                    var nrmMat = MathNet.Numerics.LinearAlgebra.CreateMatrix.Random<double>(config.Size, config.Size, new MathNet.Numerics.Distributions.Normal(0.0, mag, context.Rand));
+
+                    if (b0 != null)
+                        b0.PointwiseMultiply(nrmMat, b0);
+                    else
+                        b0 = nrmMat;
+                }
+
+                if (clips.IsSet("rnduniformb"))
+                {
+                    var mag = clips.Get("rnduniformb", double.Parse, 1.0);
+                    var uniformMat = MathNet.Numerics.LinearAlgebra.CreateMatrix.Random<double>(config.Size, config.Size, new MathNet.Numerics.Distributions.ContinuousUniform(-mag, mag, context.Rand));
+
+                    if (b0 != null)
+                        b0.PointwiseMultiply(uniformMat, b0);
+                    else
+                        b0 = uniformMat;
+                }
+
+                var newPopulation = new List<DenseIndividual>();
+                foreach (var oi in population.ExtractAll())
+                {
+                    oi.Genome.CopyOverTransMat(b0);
+                    newPopulation.Add(DenseIndividual.Develop(oi.Genome, context, drules, oi.Epigenetic));
+                }
+                
+                population = new Population<DenseIndividual>(newPopulation);
+            }
+            
+            if (clips.IsSet("transmatmutator"))
+            {
+                ITransMatMutator transMatMutator = GenomeParsing.GetTransMatMutator(clips.Get("transmatmutator", "default"), config.Size);
+                
+                foreach (var oi in population.PeekAll())
+                    oi.Genome.SetCustomTransmatMutator(transMatMutator);
+            }
+
+            if (clips.IsSet("initialstatemutator"))
+            {
+                IInitialStateMutator initialStateMutator = GenomeParsing.GetInitialStateMutator(clips.Get("initialstatemutator", "default"), config.Size);
+                
+                foreach (var oi in population.PeekAll())
+                    oi.Genome.SetCustomInitialStateMutator(initialStateMutator);
+            }
+
+            int popSize = clips.Get("pop.size", int.Parse, population.Count);
+            var extracted = population.ExtractAll();
+            Misc.ShuffleInplace(rand, extracted);
+            population = new Population<DenseIndividual>(TakeLoop(extracted, popSize)); // apparently I don't need to clone... suits me
+
+            return population;
+        }
+
+        public static PopulationExperiment<DenseIndividual> Reconfig(TextWriter console, CliParams clips, PopulationExperiment<DenseIndividual> target, MathNet.Numerics.Random.RandomSource rand, string outdir)
+        {
+            // popConfig
+            var popConfig = target.PopulationConfig;
+            popConfig = Reconfig(console, clips, popConfig);
+
+            // population
+            var population = target.Population;
+            population = Reconfig(clips, population, rand, popConfig);
+
+            bool timestamp = clips.IsSet("timestamp");
+
+            // population experiment
+            var fileStuff = FileStuff.CreateNow(outdir, "", "", true, timestamp);
+            var popExp = new PopulationExperiment<DenseIndividual>(population, popConfig, fileStuff);
+
+            return popExp;
+        }
+
+        public static IEnumerable<T> TakeLoop<T>(IReadOnlyList<T> source, int count)
+        {
+            int i = 0;
+            for (int j = 0; j < count; j++)
+            {
+                yield return source[i];
+                i = i + 1 % source.Count;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliSaturationAnalysis.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliSaturationAnalysis.cs
new file mode 100644
index 0000000..4b679fc
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliSaturationAnalysis.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class SaturationInfo
+    {
+        public SaturationInfo(double saturationPoint)
+        {
+            SaturationPoint = saturationPoint;
+        }
+
+        public double SaturationPoint { get; }
+
+        public static SaturationInfo Measure(ModelExecutionContext context, DevelopmentRules drules, double c, double lambda, int moduleSize)
+        {
+            var g = Linear.CreateVector.Dense<double>(moduleSize, 1.0);
+            var zero = Linear.CreateMatrix.Dense<double>(moduleSize, moduleSize, 0.0);
+            var b = Linear.CreateMatrix.Dense<double>(moduleSize, moduleSize, 0.0);
+            var p = new Phenotype(g.Clone());
+            var genome = new DenseGenome(g, b);
+
+            double fitness(double w)
+            {
+                zero.Add(w, b);
+                genome.DevelopInto(p, context, drules);
+                var f = p.Vector[0] * c - w * lambda / moduleSize;
+                return f;
+            }
+
+            var saturationPoint = Misc.ArgMax(fitness, new Misc.Range(0, 10), 0.001);
+
+            return new SaturationInfo(saturationPoint);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliSparseSwitchers.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliSparseSwitchers.cs
new file mode 100644
index 0000000..fa1d0e3
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliSparseSwitchers.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class CliSparseSwitchers : ICliProvider
+    {
+        public string Key => "sparseswitchers";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("SparseSwitchers finds all the sparse networks which are switchable by switching the zeroth node when it has a weight slight weight advantage");
+                console.WriteLine("Available parameters:");
+                console.WriteLine(" - sparseswitchers, the config to use");
+                console.WriteLine(" - module, the list of traits in the module, default (all)");
+                console.WriteLine(" - target, the index of the target to use, default 0");
+                console.WriteLine(" - epoch, target reset exposure epoch, implies reset target if set");
+                console.WriteLine(" - resettarget, resets the target (flag)");
+                console.WriteLine(" - seed, random seed");
+                console.WriteLine(" - proper, enables/disables cplus and cminus (flag)");
+                console.WriteLine(" - cplus, list of c_plus values for target modules");
+                console.WriteLine(" - cminus, list of c_minus values for target modules");
+                console.WriteLine(" - T, alternative developmental step count");
+            }
+
+            var config = clips.Get("sparseswitchers", M4M.CliPlotHelpers.LoadExperimentConfig);
+            var rand = new CustomMersenneTwister(clips.Get("seed", int.Parse, 0));
+            var context = new ModelExecutionContext(rand);
+
+            var targetIdx = clips.Get("target", int.Parse, 0);
+            var target = config.Targets[targetIdx];
+
+            if (clips.IsSet("resettarget") || clips.IsSet("epoch"))
+            {
+                var epoch = clips.Get("epoch", int.Parse, 0);
+
+                ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+                target.NextExposure(rand, config.JudgementRules, epoch, ref exposureInformation);
+            }
+
+            target.NextGeneration(rand, config.JudgementRules);
+
+            if (clips.IsSet("proper"))
+            {
+                if (target is M4M.Epistatics.IIvmcProperTarget ipt)
+                {
+                    double[] cplus = clips.Get("cplus", s => s.Split(';').Select(double.Parse).ToArray(), null);
+                    double[] cminus = clips.Get("cminus", s => s.Split(';').Select(double.Parse).ToArray(), null);
+                    if (cplus != null)
+                    {
+                        cplus.CopyTo(ipt.Proper.Cplus, 0);
+                    }
+                    if (cminus != null)
+                    {
+                        cminus.CopyTo(ipt.Proper.Cminus, 0);
+                    }
+                }
+                else
+                {
+                    console.WriteLine("Ignoring proper parameter, as target is no an IIvmcProperTarget");
+                }
+            }
+
+            var moduleTraits = clips.Get("module", CliPlotHelpers.ParseIntList, Enumerable.Range(0, target.Size)).ToArray();
+            var module = new HashSet<int>(moduleTraits);
+
+            // just hope this doesn't find the Pit Of Despair
+            var baseWeight = FindIdealIdentityWeight(rand, config, target, moduleTraits);
+            console.WriteLine($"Base weight: {baseWeight}");
+            
+            var gm = Linear.CreateVector.Dense<double>(target.Size, i => module.Contains(i) ? config.InitialStateResetRange.Min : 0);
+            var gp = Linear.CreateVector.Dense<double>(target.Size, i => i == moduleTraits[0] ? config.InitialStateResetRange.Max : module.Contains(i) ? config.InitialStateResetRange.Min : 0);
+            
+            var T = clips.Get("t", int.Parse, config.DevelopmentRules.TimeSteps);
+            var drules = new DevelopmentRules(T, config.DevelopmentRules.UpdateRate, config.DevelopmentRules.DecayRate, config.DevelopmentRules.Squash);
+            config = new ExperimentConfiguration(config.Size, config.Targets, config.TargetCycler, config.Epochs, config.GenerationsPerTargetPerEpoch, config.InitialStateResetProbability, config.InitialStateResetRange, false, drules, config.ReproductionRules, config.JudgementRules);
+
+            foreach (var s in Combinatorics.EnumerateCombinations(Enumerable.Repeat(moduleTraits.Length, moduleTraits.Length).ToArray()))
+            {
+                var entries = s.Select((j, i) => new MatrixEntryAddress(moduleTraits[i], moduleTraits[j])).OrderBy(e => e.Col).ToList(); // put more weight near the leader
+
+                var weightDropoff = 0.001;
+                var b = CreateSparse(target.Size, baseWeight, weightDropoff, entries);
+                
+                var genomem = new DenseGenome(gm, b);
+                var pm = genomem.Develop(context, config.DevelopmentRules);
+                var jm = MultiMeasureJudgement.Judge(genomem, pm, config.JudgementRules, target);
+
+                var genomep = new DenseGenome(gp, b);
+                var pp = genomep.Develop(context, config.DevelopmentRules);
+                var jp = MultiMeasureJudgement.Judge(genomem, pp, config.JudgementRules, target);
+
+                if (jp.CombinedFitness > jm.CombinedFitness)
+                {
+                    console.WriteLine(string.Join(", ", entries));
+                }
+            }
+        }
+
+        public static Linear.Matrix<double> CreateSparse(int n, double baseWeight, double weightDropoff, IEnumerable<MatrixEntryAddress> entries)
+        {
+            var mat = Linear.CreateMatrix.Dense<double>(n, n);
+            foreach (var e in entries)
+            {
+                mat[e.Row, e.Col] = baseWeight;
+                baseWeight -= weightDropoff;
+            }
+
+            return mat;
+        }
+
+        public static double FindIdealIdentityWeight(MathNet.Numerics.Random.RandomSource rand, ExperimentConfiguration config, ITarget target, int[] moduleTraits, double wmin = 0, double wAdd = 128, double wEpsilon = 0.00001)
+        {
+            var module = new HashSet<int>(moduleTraits);
+
+            var context = new ModelExecutionContext(rand);
+
+            var g = Linear.CreateVector.Dense<double>(target.Size, i => module.Contains(i) ? config.InitialStateResetRange.Max : 0);
+            var b = Linear.CreateMatrix.Dense<double>(target.Size, target.Size, 0.0);
+            var genome = new DenseGenome(g, b);
+            var p = genome.Develop(context, config.DevelopmentRules);
+            
+            double Judge(double w)
+            {
+                // refit b
+                foreach (var i in moduleTraits)
+                    b[i, i] = w;
+
+                // develop
+                genome.DevelopInto(p, context, config.DevelopmentRules);
+
+                // evalutate
+                var j = MultiMeasureJudgement.Judge(genome, p, config.JudgementRules, target);
+                var f = j.CombinedFitness;
+
+                return f;
+            }
+
+            return Optimisation.MaximiseUnary(Judge);
+        }
+
+        public void Run(CliParams clips, string filename)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public static class Optimisation
+    {
+        /// <summary>
+        /// Optimises a function with a single plateau
+        /// Returns a value w within wEpsilon of a value that locally maximises func(w)
+        /// </summary>
+        /// <param name="func">The function to optimise</param>
+        /// <param name="wStart">Where to start</param>
+        /// <param name="wAdd">The initial increment to try</param>
+        /// <param name="wEpsilon">The minimum increment to try</param>
+        /// <returns>The value w what maximises func(w)</returns>
+        public static double MaximiseUnary(Func<double, double> func, double wStart = 0, double wAdd = 128, double wEpsilon = 0.00001)
+        {
+            double w = wStart;
+            double wLast = wStart;
+            double fLast = double.MinValue;
+            double dw = wAdd;
+
+            bool broken = false;
+
+            while (Math.Abs(dw) > wEpsilon)
+            {
+                var f = func(w);
+
+                // move
+                if (f > fLast)
+                { // we improved, so keep going that way
+                    fLast = f;
+                    wLast = w;
+
+                    if (!broken)
+                        dw *= 2.0;
+                }
+                else
+                { // we got worse, so step-back, reduce dw, and try the other way
+                    w = wLast;
+                    dw = -dw / 2.0;
+                    broken = true;
+                }
+
+                w += dw;
+            }
+            
+            return wLast;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliSpit.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliSpit.cs
new file mode 100644
index 0000000..555ec7b
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliSpit.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    public class CliSpit : ICliProvider
+    {
+        public string Key => "spit";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Spits out config files");
+                console.WriteLine(" - spit, the expfile wherefor to spit out a config listing");
+                console.WriteLine(" - out, where to save the config");
+            }
+            
+            string expFile = clips.Get("spit");
+            string outFile = clips.Get("out", "config.txt");
+            
+            // load the experiment - inferring its type - and give us an IExperimentExtractor to work with
+            var expExtractor = PopulationExperimentHelpers.LoadUnknownType(expFile, new ExperimentExtractorExperimentReceiver());
+
+            expExtractor.WriteOutConfig(outFile);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliSwitchingModules.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliSwitchingModules.cs
new file mode 100644
index 0000000..96a7842
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliSwitchingModules.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using System.IO;
+
+namespace M4M
+{
+    public class CliSwitchingModules : ICliProvider
+    {
+        public string Key => "switchingmodules";
+
+        public void Run(TextWriter console, CliParams clips)
+        {
+            var outdir = clips.Get("outdir");
+            bool appendTimestamp = clips.Get("appendTimestamp", bool.Parse, true);
+
+            var rand = CliPlotHelpers.ParseRand(clips);
+            var context = new ModelExecutionContext(rand);
+
+            var exp = clips.Get("expfile", PopulationExperiment<DenseIndividual>.Load);
+            var popConfig = exp.PopulationConfig;
+            var config = popConfig.ExperimentConfiguration;
+            var target = CliPlotHelpers.ParseTarget(console, clips, config.Targets, config.Targets[0]);
+            CliPlotHelpers.PrepTarget(rand, clips, config, target, true, 1);
+
+            bool saturateInitialStates = clips.Get("saturateis", bool.Parse, true);
+            double saturationMin = clips.Get("satmin", double.Parse, -1);
+            double saturationThreshold = clips.Get("satthreshold", double.Parse, 0);
+            double saturationMax = clips.Get("satmax", double.Parse, +1);
+
+            var spinner = exp.PopulationConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<DenseIndividual>.Instance;
+
+            int count = clips.Get("count", int.Parse, 1000);
+
+            var counts = new Dictionary<Linear.Vector<double>, int>();
+            var stuff = FileStuff.CreateNow(outdir, "", "", true, appendTimestamp);
+
+            var sw = new System.Diagnostics.Stopwatch();
+            sw.Restart();
+
+            // simulate lots of epochs
+            for (int i = 0; i < count; i++)
+            {
+                if (sw.ElapsedMilliseconds > 30000)
+                {
+                    console.WriteLine($"{i}/{count}");
+                    sw.Restart();
+                }
+
+                var pop = exp.Population.Clone(context);
+
+                popConfig.PopulationResetOperation.Reset(context, pop, config.InitialStateResetRange, config.DevelopmentRules, config.ReproductionRules, target);
+
+                var ijs = spinner.SpinPopulation(pop, context, config.ReproductionRules, config.DevelopmentRules, config.JudgementRules, target, popConfig.SelectorPreparer, config.GenerationsPerTargetPerEpoch, null, popConfig.EliteCount, popConfig.HillclimberMode);
+                var result = ijs[0].Individual.Genome.InitialState;
+                if (saturateInitialStates)
+                    result = Misc.Saturate(result, saturationMin, saturationThreshold, saturationMax);
+
+                if (counts.TryGetValue(result, out int c))
+                {
+                    counts[result] = c + 1;
+                }
+                else
+                {
+                    counts[result] = 1;
+                }
+            }
+
+            var report = new StringBuilder();
+            report.AppendLine($"# SwitchingModules {Misc.NowTime}");
+            report.AppendLine();
+            report.AppendLine($"Index\tCount");
+
+            int idx = 0;
+            var templateIndividual = exp.Population.PeekAll()[0];
+            foreach (var vk in counts.OrderBy(c => c.Value))
+            {
+                var exportGenome = templateIndividual.Genome.Clone(context, newInitialState: vk.Key);
+                var exportIndividual = DenseIndividual.Develop(exportGenome, context, config.DevelopmentRules, templateIndividual.Epigenetic);
+                var exportPop = new Population<DenseIndividual>(new[] { exportIndividual });
+                var exportExp = new PopulationExperiment<DenseIndividual>(exportPop, popConfig, stuff, 0, 0);
+                exportExp.Save($"u{idx}", false);
+
+                report.AppendLine($"{idx}\t{vk.Value}");
+
+                idx++;
+            }
+
+            System.IO.File.WriteAllText(stuff.File("report.txt"), report.ToString());
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CLI/CliTraces.cs b/M4MCode/M4M_MkI/M4M.New/CLI/CliTraces.cs
new file mode 100644
index 0000000..27db78d
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CLI/CliTraces.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{   
+    public class CliTracee : ICliProvider
+    {
+        public string Key => "tracee";
+        
+        public void Run(TextWriter console, CliParams clips)
+        {
+            string expAddress = clips.Get("tracee");
+            string topdir = clips.Get("topdir", System.IO.Path.GetDirectoryName(expAddress));
+            if (string.IsNullOrEmpty(topdir))
+                topdir = ".";
+            string cliPrefix = clips.Get("q", "");
+            if (cliPrefix != "")
+                cliPrefix += " ";
+
+            RunTracee(console, clips, cliPrefix, expAddress, topdir);
+        }
+
+        public void RunTracee(TextWriter console, CliParams clips, string cliPrefix, string expAddress, string topdir)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Tracee parameters include:\n" +
+                    "seed\n" +
+                    "postfix\n" + 
+                    "targets\n" +
+                    "samplePeriod\n" +
+                    "cycles\n" +
+                    "exposureEpoch");
+            }
+
+            bool quiet = clips.IsSet("quiet");
+            int? seed = clips.Get("seed", s => (int?)int.Parse(s), null);
+            string postfix = clips.Get("postfix", "");
+            int samplePeriod = clips.Get("samplePeriod", int.Parse, 1);
+            int[] targetIndexes = clips.Get("targets", s => s.Replace(" ", "").Split(new [] { ',', ';' }).Select(int.Parse).ToArray(), new int[] { 0 });
+
+            int cycles = clips.Get("cycles", int.Parse, 1);
+            targetIndexes = Enumerable.Repeat(targetIndexes, cycles).SelectMany(l => l).ToArray();
+
+            int startEpoch = clips.Get("exposureEpoch", int.Parse, PopulationExperiment<DenseIndividual>.Load(expAddress).Epoch);
+            int epochs = clips.Get("epochs", int.Parse, 1);
+
+            RunTracee(console, cliPrefix, expAddress, topdir, postfix, samplePeriod, seed, targetIndexes, quiet, startEpoch, epochs);
+        }
+
+        public void RunTracee(TextWriter console, string cliPrefix, string expAddress, string topdir, string postfix, int samplePeriod, int? seed, int[] targetIndexes, bool quiet, int startEpoch, int epochs)
+        {
+            console.WriteLine(cliPrefix + "Running tracee from prepackaged DenseIndividual experiment");
+            console.WriteLine(cliPrefix + "Definition at " + expAddress);
+            console.WriteLine(cliPrefix + "Running into  " + topdir);
+            console.WriteLine(cliPrefix + "Target sequence is  " + String.Join(", ", targetIndexes));
+
+            var popExperiment = PopulationExperiment<DenseIndividual>.Load(expAddress);
+            
+            var targets = targetIndexes.Select(ti => popExperiment.PopulationConfig.ExperimentConfiguration.Targets[ti]).ToArray();
+
+            var rand = seed != null
+                ? new MathNet.Numerics.Random.MersenneTwister(seed.Value, false)
+                : new MathNet.Numerics.Random.MersenneTwister(false);
+            var context = new ModelExecutionContext(rand);
+            
+            var trace = PopulationTrace.RunTrace(console, context, popExperiment.Population, popExperiment.PopulationConfig, targets, samplePeriod, startEpoch, epochs);
+            
+            Misc.EnsureDirectory(topdir);
+            var outfile = System.IO.Path.Combine(topdir, "tracee" + postfix + ".dat");
+            if (!quiet)
+                console.WriteLine("Writing to " + outfile);
+            trace.Save(outfile);
+        }
+    }
+
+    public class CliTraces : ICliProvider
+    {
+        public string Key => "traces";
+        
+        public void Run(TextWriter console, CliParams clips)
+        {
+            string expAddress = clips.Get("traces");
+            string topdir = clips.Get("topdir", System.IO.Path.GetDirectoryName(expAddress));
+            if (string.IsNullOrEmpty(topdir))
+                topdir = ".";
+            string cliPrefix = clips.Get("q", "");
+            if (cliPrefix != "")
+                cliPrefix += " ";
+
+            RunTraces(console, clips, cliPrefix, expAddress, topdir);
+        }
+
+        public void RunTraces(TextWriter console, CliParams clips, string cliPrefix, string expAddress, string topdir)
+        {
+            bool quiet = clips.IsSet("quiet");
+            int? seed = clips.Get("seed", s => (int?)int.Parse(s), null);
+            int count = clips.Get("count", int.Parse, 1000);
+            string postfix = clips.Get("postfix", "");
+            int samplePeriod = clips.Get("samplePeriod", int.Parse, 1);
+            int[] targetIndexes = clips.Get("targets", s => s.Replace(" ", "").Split(',', ';').Select(int.Parse).ToArray(), new int[] { 0 });
+            
+            int cycles = clips.Get("cycles", int.Parse, 1);
+            targetIndexes = Enumerable.Repeat(targetIndexes, cycles).SelectMany(l => l).ToArray();
+
+            int startEpoch = clips.Get("exposureEpoch", int.Parse, PopulationExperiment<DenseIndividual>.Load(expAddress).Epoch);
+            int epochs = clips.Get("epochs", int.Parse, 1);
+
+            RunTraces(console, cliPrefix, expAddress, topdir, postfix, count, samplePeriod, seed, targetIndexes, quiet, startEpoch, epochs);
+        }
+
+        public void RunTraces(TextWriter console, string cliPrefix, string expAddress, string topdir, string postfix, int count, int samplePeriod, int? seed, int[] targetIndexes, bool quiet, int exposureEpoch, int epochs)
+        {
+            console.WriteLine(cliPrefix + "Running traces from prepackaged DenseIndividual experiment");
+            console.WriteLine(cliPrefix + "Definition at " + expAddress);
+            console.WriteLine(cliPrefix + "Running into  " + topdir);
+            console.WriteLine(cliPrefix + "Target sequence is  " + String.Join(", ", targetIndexes));
+
+            var popExperiment = PopulationExperiment<DenseIndividual>.Load(expAddress);
+            
+            var targets = targetIndexes.Select(ti => popExperiment.PopulationConfig.ExperimentConfiguration.Targets[ti]).ToArray();
+
+            var rand = seed != null
+                ? new MathNet.Numerics.Random.MersenneTwister(seed.Value, false)
+                : new MathNet.Numerics.Random.MersenneTwister(false);
+            var context = new ModelExecutionContext(rand);
+            
+            var traces = PopulationTrace.RunTraces(console, context, popExperiment.Population, popExperiment.PopulationConfig, targets, count, samplePeriod, exposureEpoch, epochs);
+            
+            Misc.EnsureDirectory(topdir);
+            var outfile = System.IO.Path.Combine(topdir, "traces" + postfix + ".dat");
+            if (!quiet)
+                console.WriteLine("Writing to " + outfile);
+            traces.Save(outfile);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/CommonPopulationFeedback.cs b/M4MCode/M4M_MkI/M4M.New/CommonPopulationFeedback.cs
new file mode 100644
index 0000000..87ee30e
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/CommonPopulationFeedback.cs
@@ -0,0 +1,471 @@
+using M4M.State;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using static M4M.Analysis;
+
+namespace M4M
+{
+    /// <summary>
+    /// Does sampling with a given period, and allows some automatic file-output stuff (genomes and rcses and more)
+    /// </summary>
+    public class CommonDensePopulationExperimentFeedback : IDensePopulationExperimentFeedback
+    {
+        public static DensePopulationExperimentFeedbackFactory SimpleCommonDensePopulationExperimentFeedback(TextWriter console, bool disableTransientIO = false, bool disableAllIO = false, int sampleMax = 2000, int savePeriod = 1000, int plotPeriod = 2000, int wholeSamplePeriod = 10, int wholeSampleWritePeriod = 2000, int tracePeriod = 0, int traceDuration = 1, int traceSamplePeriod = 1, bool rememberTraces = false, bool noRcs = false)
+        {
+            return (rand, popConfig, feedback) => new CommonDensePopulationExperimentFeedback(console, rand, popConfig, disableTransientIO, disableAllIO, savePeriod, plotPeriod, feedback, Math.Max(1, popConfig.ExperimentConfiguration.Epochs / sampleMax), wholeSamplePeriod, wholeSampleWritePeriod, tracePeriod, traceDuration, traceSamplePeriod, rememberTraces, noRcs);
+        }
+
+        public PopulationExperimentConfig<DenseIndividual> PopulationConfig { get; }
+        public ExperimentConfiguration Config { get; }
+
+        /// <summary>
+        /// The PopulationExperimentFeedback this instance uses
+        /// </summary>
+        public PopulationExperimentFeedback<DenseIndividual> Feedback { get; }
+
+        protected List<int> _sampleEpochs { get; }  = new List<int>();
+        protected List<double> _fitness { get; } = new List<double>();
+        protected List<IndividualJudgement<DenseIndividual>> _bestSamples { get; } = new List<IndividualJudgement<DenseIndividual>>();
+        
+        protected List<WholeSample<DenseIndividual>> _wholeSamples { get; } = new List<WholeSample<DenseIndividual>>();
+
+        protected List<TraceInfo<DenseIndividual>> _traces { get; } = new List<TraceInfo<DenseIndividual>>();
+
+        public IReadOnlyList<IndividualJudgement<DenseIndividual>> BestSamples => _bestSamples;
+        public IReadOnlyList<int> SampleEpochs => _sampleEpochs;
+
+        public int SamplePeriod { get; }
+        public int WholeSamplePeriod { get; }
+        public int TraceSamplePeriod { get; }
+        public bool NoRcs { get; }
+        protected int N { get; }
+
+        public CommonDensePopulationExperimentFeedback(TextWriter console, RandomSource rand, PopulationExperimentConfig<DenseIndividual> populationConfig, bool disableTransientIO, bool disableAllIO, int genomeSavePeriod, int partialPlotPeriod = 100, PopulationExperimentFeedback<DenseIndividual> feedback = null, int samplePeriod = 1, int wholeSamplePeriod = 10, int wholeSampleWritePeriod = 2000, int tracePeriod = 0, int traceDuration = 1, int traceSamplePeriod = 1, bool rememberTraces = false, bool noRcs = false)
+        {
+            disableTransientIO = disableTransientIO | disableAllIO;
+
+            // if not set, make a new empty one
+            Feedback = feedback ?? new PopulationExperimentFeedback<DenseIndividual>();
+            
+            ITarget lastTarget = null;
+            
+            PopulationConfig = populationConfig;
+            Config = populationConfig.ExperimentConfiguration;
+            N = Config.Size;
+            SamplePeriod = samplePeriod;
+            WholeSamplePeriod = wholeSamplePeriod;
+            TraceSamplePeriod = traceSamplePeriod;
+            NoRcs = noRcs;
+
+            PopulationTraceRecorder<DenseIndividual> currentTraceRecorder = null;
+            int traceAge = -1;
+            void startTrace()
+            {
+                currentTraceRecorder = new PopulationTraceRecorder<DenseIndividual>(TraceSamplePeriod);
+                traceAge = 0;
+                Feedback.Judged.Register(currentTraceRecorder.Recorder);
+            }
+
+            void endTrace(FileStuff stuff, int endEpoch)
+            {
+                if (currentTraceRecorder != null)
+                {
+                    // save
+                    var traceInfo = new TraceInfo<DenseIndividual>(currentTraceRecorder.TraceWholeSamples);
+
+                    if (rememberTraces)
+                        _traces.Add(traceInfo);
+
+                    SaveTrace(stuff, "e" + endEpoch, traceInfo);
+
+                    // remove trace
+                    Feedback.Judged.Unregister(currentTraceRecorder.Recorder);
+                    currentTraceRecorder = null;
+                    traceAge = -1;
+                }
+            }
+
+            void sample(FileStuff stuff, Population<DenseIndividual> population, int epoch, IReadOnlyList<IndividualJudgement<DenseIndividual>> opj)
+            {
+                if (epoch % SamplePeriod == 0)
+                {
+                    _sampleEpochs.Add(epoch);
+
+                    if (opj != null)
+                    {
+                        _fitness.Add(opj.Average((IndividualJudgement<DenseIndividual> ij) => ij.Judgement.CombinedFitness));
+                        _bestSamples.Add(opj.ArgMax((IndividualJudgement<DenseIndividual> ij) => ij.Judgement.CombinedFitness));
+                    }
+                }
+
+                if (epoch % genomeSavePeriod == 0 || epoch == populationConfig.ExperimentConfiguration.Epochs)
+                {
+                    DenseIndividual di = population.PeekRandom(rand);
+                    DenseGenome g = di.Genome;
+                    Phenotype p = di.Phenotype;
+
+                    // save genome (this is transient..., but it's important... and relatively cheap (and no nil crossover, etc.))
+                    if (!disableAllIO)
+                    {
+                        SaveGenome(stuff.File("genome" + epoch + ".dat"), g);
+                    }
+                }
+                
+                if (epoch > 0 && epoch % partialPlotPeriod == 0)
+                {
+                    if (!disableTransientIO)
+                    {
+                        if (!NoRcs)
+                            SaveRcs(stuff, epoch + "(best)");
+                        SaveIst(stuff, epoch + "(best)");
+                        SavePst(stuff, epoch + "(best)");
+                        SaveFitness(stuff, epoch + "(best)");
+                    }
+                }
+            }
+            
+            void wholeSample(FileStuff stuff, long generationCount, int epoch, ITarget target, IReadOnlyList<IndividualJudgement<DenseIndividual>> opj)
+            {
+                if (WholeSamplePeriod > 0)
+                {
+                    if (epoch % WholeSamplePeriod == 0)
+                    {
+                        _wholeSamples.Add(new WholeSample<DenseIndividual>(generationCount, epoch, target, opj));
+                    }
+                }
+            }
+
+            Feedback.EndTarget.Register((stuff, population, target, epoch, generationCount, oldPopulationJudgements) =>
+            {
+                // record last Target
+                lastTarget = target;
+                
+                wholeSample(stuff, generationCount, epoch, target, oldPopulationJudgements);
+            });
+            
+            Feedback.Started.Register((stuff, population) =>
+            {
+                // TODO: this doesn't make a whole lot of sense... infact, we probably ought to be using our own target for all samples, so that we know it is consistent...
+                // ... but there is something nice about using the target we 'just saw', only such a target doesn't exist here
+                var epoch0target = Config.Targets[Config.Targets.Count - 1];
+                epoch0target.NextGeneration(rand, Config.JudgementRules);
+
+                var opj = population.PeekAndJudgeAll(Config.JudgementRules, epoch0target);
+                sample(stuff, population, 0, opj);
+                wholeSample(stuff, 0, 0, epoch0target, opj);
+
+                // trace the first ones, if we are tracing at all
+                if (tracePeriod > 0)
+                {
+                    startTrace();
+                }
+            });
+
+            // feedback
+            Feedback.EndEpoch.Register((stuff, population, epoch, generationCount, opj) =>
+            {
+                sample(stuff, population, epoch, opj);
+                   
+                if (wholeSamplePeriod > 0 && wholeSampleWritePeriod > 0 &&
+                    epoch > 0 && epoch % wholeSampleWritePeriod == 0)
+                {
+                    SaveWholeSamples(stuff, "e" + epoch.ToString());
+                    _wholeSamples.Clear();
+                }
+                
+                if (traceAge >= 0)
+                    traceAge++;
+                if (traceAge >= traceDuration)
+                    endTrace(stuff, epoch); // end a running trace ...
+                if (tracePeriod > 0 && (traceAge == -1 && ((epoch + traceDuration) % tracePeriod == 0 || epoch == Config.Epochs - traceDuration))) // ... and start a new one if we should
+                {
+                    startTrace();
+                }
+            });
+
+            Feedback.Finished.Register((stuff, population, epoch, generationCount) =>
+            {
+                console.WriteLine("Finished run; Collating results...");
+
+                if (!disableAllIO) // we usually want the terminals
+                {
+                    DenseIndividual di = population.PeekRandom(rand);
+                    DenseGenome g = di.Genome;
+                    Phenotype p = di.Phenotype;
+                    
+                    // save genome & rcs
+                    SaveGenome(stuff.File("terminalgenome.dat"), g); // backwards compatibility
+                    SaveGenome(stuff.File("genome_t.dat"), g); // (prefered)
+                    if (!NoRcs)
+                        SaveRcs(stuff, "_t(best)");
+                    SaveIst(stuff, "_t(best)");
+                    SavePst(stuff, "_t(best)");
+                    SaveFitness(stuff, "_t(best)");
+
+                    if (WholeSamplePeriod > 0 && _wholeSamples.Count > 0)
+                    {
+                        SaveWholeSamples(stuff, "e" + epoch.ToString());
+                        _wholeSamples.Clear();
+                    }
+                }
+            });
+        }
+
+        public double[][] GetRcsData()
+        {
+            return Enumerable.Range(0, N * N).Select(i => _bestSamples.Select(ij => ij.Individual.Genome.TransMat[i / N, i % N]).ToArray()).ToArray();
+        }
+        
+        public void SaveRcs(FileStuff stuff, string name)
+        {
+            double[][] rcs = GetRcsData();
+            SaveTrajectories(stuff.File("rcs" + name + ".dat"), rcs, SamplePeriod);
+        }
+
+        public double[][] GetIstData()
+        {
+            return Enumerable.Range(0, N).Select(i => _bestSamples.Select(ij => ij.Individual.Genome.InitialState[i]).ToArray()).ToArray();
+        }
+
+        public double[][] GetPstData()
+        {
+            return Enumerable.Range(0, N).Select(i => _bestSamples.Select(ij => ij.Individual.Phenotype[i]).ToArray()).ToArray();
+        }
+        
+        public void SaveIst(FileStuff stuff, string name)
+        {
+            double[][] ist = GetIstData();
+            SaveTrajectories(stuff.File("ist" + name + ".dat"), ist, SamplePeriod);
+        }
+        
+        public void SavePst(FileStuff stuff, string name)
+        {
+            double[][] pst = GetPstData();
+            SaveTrajectories(stuff.File("pst" + name + ".dat"), pst, SamplePeriod);
+        }
+
+        /// <summary>
+        /// f, b, c*lambda
+        /// </summary>
+        /// <returns></returns>
+        public double[][] GetFitnessData()
+        {
+            // TODO: maybe add trajectories for every target? (does that make any sense? (could be optional))
+            return new[] {
+                _bestSamples.Select(ij => ij.Judgement.CombinedFitness).ToArray(),
+                _bestSamples.Select(ij => ij.Judgement.Benefit).ToArray(),
+                _bestSamples.Select(ij => ij.Judgement.CombinedFitness - ij.Judgement.Benefit).ToArray() // cost * lambda
+                };
+        }
+        
+        public void SaveFitness(FileStuff stuff, string name)
+        {
+            double[][] fitness = GetFitnessData();
+            SaveTrajectories(stuff.File("fitness" + name + ".dat"), fitness, SamplePeriod);
+        }
+        
+        public void SaveWholeSamples(FileStuff stuff, string name)
+        {
+            using (var fs = stuff.CreateBin("wholesamples" + name + ".dat"))
+            {
+                GraphSerialisation.Write(_wholeSamples, fs);
+            }
+        }
+
+        public static double[][] ExtractRcs(IEnumerable<WholeSample<DenseIndividual>> wholeSamples, int N, int samplePeriod)
+        {
+            return ExtractTrajectories(wholeSamples, N * N, ws => ws.Generations % samplePeriod == 0, (ws, i) => ws.Judgements[0].Individual.Genome.TransMat[i / N, i % N]);
+        }
+
+        public static double[][] ExtractTrajectories<T>(IEnumerable<WholeSample<T>> wholeSamples, int count, Predicate<WholeSample<T>> filter, Func<WholeSample<T>, int, double> extractor) where T : IIndividual<T>
+        {
+            List<double>[] trajectories = new List<double>[count];
+
+            for (int i = 0; i < count; i++)
+                trajectories[i] = new List<double>();
+
+            foreach (var ws in wholeSamples)
+            {
+                if (filter(ws))
+                {
+                    for (int i = 0; i < count; i++)
+                        trajectories[i].Add(extractor(ws, i));
+                }
+            }
+
+            return trajectories.Select(t => t.ToArray()).ToArray();
+        }
+
+        public static IReadOnlyList<string> DetectWholeSamples(string dir)
+        {
+            string trim(string path) => System.IO.Path.GetFileName(path);
+
+            var filenames = System.IO.Directory.GetFiles(dir);
+            var samplefiles = filenames.Where(fname => trim(fname).StartsWith("wholesamplese") && trim(fname).EndsWith(".dat"));
+            var sortedsamplefiles = samplefiles.OrderBy(fname => int.Parse(trim(fname).Substring("wholesamplese".Length, trim(fname).Length - "wholesamplese.dat".Length)));
+
+            return sortedsamplefiles.ToArray();
+        }
+        
+        public void SaveTrace<T>(FileStuff stuff, string name, TraceInfo<T> traceInfo) where T : IIndividual<T>
+        {
+            traceInfo.Save(stuff.File("trace" + name + ".dat"));
+        }
+    }
+
+    public class WholeSampleFeedback<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        public WholeSampleFeedback(PopulationExperimentFeedback<TIndividual> feedback, int wholeSamplePeriod, bool allowEarlyAccess = false)
+        {
+            Feedback = feedback ?? throw new ArgumentNullException(nameof(feedback));
+            WholeSamplePeriod = wholeSamplePeriod;
+            AllowEarlyAccess = allowEarlyAccess;
+            Finished = false;
+
+            Init();
+        }
+
+        /// <summary>
+        /// The PopulationExperimentFeedback this instance uses
+        /// </summary>
+        public PopulationExperimentFeedback<TIndividual> Feedback { get; }
+        public int WholeSamplePeriod { get; }
+        public bool AllowEarlyAccess { get; }
+        public bool Finished { get; set; }
+
+        private List<WholeSample<TIndividual>> _wholeSamples { get; } = new List<WholeSample<TIndividual>>();
+        public List<WholeSample<TIndividual>> WholeSamples
+        {
+            get
+            {
+                if (!Finished && !AllowEarlyAccess)
+                    throw new InvalidOperationException("Cannot access the unfinished wholesamples");
+
+                return _wholeSamples;
+            }
+        }
+
+        public void FinishEarly()
+        {
+            Finished = true;
+        }
+
+        private void Init()
+        {
+            // only sample end-of-epoch
+            Feedback.EndTarget.Register((stuff, population, target, epoch, generationCount, oldPopulationJudgements) =>
+            {
+                if (Finished)
+                    throw new InvalidOperationException("Cannot reuse an instance of " + nameof(WholeSampleFeedback<TIndividual>));
+
+                SampleWholeSample(generationCount, epoch, target, oldPopulationJudgements);
+            });
+
+            Feedback.Finished.Register((stuff, population, epoch, generationCount) =>
+            {
+                if (Finished)
+                    throw new InvalidOperationException("Cannot reuse an instance of " + nameof(WholeSampleFeedback<TIndividual>));
+
+                Finished = true;
+            });
+        }
+
+        private void SampleWholeSample(long generationCount, int epoch, ITarget target, IReadOnlyList<IndividualJudgement<TIndividual>> opj)
+        {
+            if (WholeSamplePeriod > 0)
+            {
+                if (epoch % WholeSamplePeriod == 0)
+                {
+                    _wholeSamples.Add(new WholeSample<TIndividual>(generationCount, epoch, target, opj));
+                }
+            }
+        }
+    }
+
+    public class ProjectedFitnessFeedback<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        public ProjectedFitnessFeedback(PopulationExperimentFeedback<TIndividual> feedback, IReadOnlyList<ITarget> targets, JudgementRules jrules, string postfix, int samplePeriod, int plotPeriod)
+        {
+            Feedback = feedback ?? throw new ArgumentNullException(nameof(feedback));
+            Targets = targets;
+            JudgementRules = jrules;
+            Postfix = postfix;
+            SamplePeriod = samplePeriod;
+            PlotPeriod = plotPeriod;
+            _samples = Enumerable.Range(0, targets.Count * 3).Select(_ => new List<double>()).ToArray();
+
+            Init();
+        }
+
+        /// <summary>
+        /// The PopulationExperimentFeedback this instance uses
+        /// </summary>
+        public PopulationExperimentFeedback<TIndividual> Feedback { get; }
+
+        /// <summary>
+        /// The targest this instance will use
+        /// </summary>
+        public IReadOnlyList<ITarget> Targets { get; }
+
+        public JudgementRules JudgementRules { get; }
+        public string Postfix { get; }
+        public int SamplePeriod { get; }
+        public int PlotPeriod { get; }
+        public bool AllowEarlyAccess { get; }
+        public bool Finished { get; set; }
+
+        private IReadOnlyList<List<double>> _samples { get; }
+        public IReadOnlyList<IReadOnlyList<double>> Samples
+        {
+            get
+            {
+                return _samples;
+            }
+        }
+
+        private void Init()
+        {
+            // only sample end-of-epoch
+            Feedback.EndTarget.Register((stuff, population, target, epoch, generationCount, oldPopulationJudgements) =>
+            {
+                SampleSample(generationCount, epoch, target, oldPopulationJudgements);
+                if (PlotPeriod > 0 && epoch % PlotPeriod == 0)
+                    SaveFitness(stuff, epoch + "(best)");
+            });
+
+            Feedback.Finished.Register((stuff, population, epoch, generationCount) =>
+            {
+                Finished = true;
+                SaveFitness(stuff, "_t(best)");
+            });
+        }
+
+        private void SampleSample(long generationCount, int epoch, ITarget oldTarget, IReadOnlyList<IndividualJudgement<TIndividual>> opj)
+        {
+            if (SamplePeriod > 0)
+            {
+                if (epoch % SamplePeriod == 0)
+                {
+                    for (int ti = 0; ti < Targets.Count; ti++)
+                    {
+                        var target = Targets[ti];
+                        var s = opj.Select(ij => ij.Individual.Judge(JudgementRules, target)).ArgMax(_s => _s.CombinedFitness);
+                        _samples[ti + 0 * Targets.Count].Add(s.CombinedFitness);
+                        _samples[ti + 1 * Targets.Count].Add(s.Benefit);
+                        _samples[ti + 2 * Targets.Count].Add(s.Cost);
+                    }
+                }
+            }
+        }
+
+        public void SaveFitness(FileStuff stuff, string name)
+        {
+            double[][] fitness = _samples.Select(t => t.ToArray()).ToArray();
+            SaveTrajectories(stuff.File("fitness" + Postfix + name + ".dat"), fitness, SamplePeriod);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/Evolvability.cs b/M4MCode/M4M_MkI/M4M.New/Evolvability.cs
new file mode 100644
index 0000000..0b1efa5
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/Evolvability.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using OxyPlot;
+using OxyPlot.Series;
+using OxyPlot.Axes;
+using System.Linq;
+using System.IO;
+
+namespace M4M
+{
+    public class ColourfulTraces
+    {
+        public ColourfulTraces(string name, OxyColor colour, IReadOnlyList<double[]> samples, int samplePeriod, long sampleOffset)
+        {
+            Name = name;
+            Colour = colour;
+            Samples = samples;
+            SamplePeriod = samplePeriod;
+            SampleOffset = SampleOffset;
+        }
+
+        public string Name { get; }
+        public OxyColor Colour { get; }
+        public IReadOnlyList<double[]> Samples { get; }
+        public int SamplePeriod { get; }
+        public long SampleOffset { get; }
+    }
+
+    public class TracePercentileInformation
+    {
+        public TracePercentileInformation(double low, double high, OxyColor colour, double strokeThickness, string label)
+        {
+            if (low < 0 || high > 1 || low > high)
+                throw new ArgumentException("Low and high must be in the range [0, 1], and low must be no more than high.");
+
+            Low = low;
+            High = high;
+            Colour = colour;
+            StrokeThickness = strokeThickness;
+            Label = label;
+        }
+
+        public TracePercentileInformation(double single, OxyColor colour, double strokeThickness, string label)
+            : this(single, single, colour, strokeThickness, label)
+        {
+            // nix
+        }
+
+        public double Low { get; }
+        public double High { get; }
+        public OxyColor Colour { get; }
+        public double StrokeThickness { get; }
+        public string Label { get; }
+
+        public bool IsSingle => Low == High;
+    }
+
+    public class EvolvabilityTraces
+    {
+        public static IReadOnlyList<TracePercentileInformation> DefaultTracePercentileInformations(string name, OxyColor colour)
+        {
+            return new []
+            {
+                new TracePercentileInformation(0.25, 0.75, colour.ChangeIntensity(0.8), 0.0, name + " 25-75"),
+                new TracePercentileInformation(0.1, 0.9, colour.ChangeIntensity(0.5), 0.0, name + " 10-90"),
+                new TracePercentileInformation(0.5, colour, 2.0, name + " median")
+            };
+        }
+
+        public static IReadOnlyList<TracePercentileInformation> SymetricTracePercentileInformations(string name, OxyColor colour, IEnumerable<int> lows, bool labelAreas, double strokeThickness)
+        {
+            return lows.Select(l => 
+            {
+                if (l != 50)
+                    return new TracePercentileInformation(l/100.0, 1.0 - l/100.0, colour.ChangeIntensity(l/100.0 + 0.5), 0.0, labelAreas ? $"{name} {l:00}-{100-l:00}" : null);
+                else
+                    return new TracePercentileInformation(0.5, colour, strokeThickness, labelAreas ? name + " median" : name);
+            }).ToArray();
+        }
+
+        public static ColourfulTraces[] RunTraces(TextWriter console, PopulationExperimentConfig<DenseIndividual> popConfig, IReadOnlyList<Population<DenseIndividual>> populations, IReadOnlyList<string> names, IReadOnlyList<OxyColor> colours, ITarget[] targets, int count, int samplePeriod, int startEpoch, int epochs)
+        {
+            var rand = new MathNet.Numerics.Random.MersenneTwister(false);
+            var context = new ModelExecutionContext(rand);
+
+            var results = new ColourfulTraces[populations.Count];
+            for (int i = 0; i < populations.Count; i++)
+            {
+                var traces = PopulationTrace.RunTraces(console, context, populations[i], popConfig, targets, count, samplePeriod, startEpoch, epochs);
+                results[i] = new ColourfulTraces(names[i], colours[i], traces.BestFitnesses, samplePeriod, 0);
+            }
+
+            return results;
+        }
+
+        /// <summary>
+        /// Do not store the results, they are liable to be modified
+        /// </summary>
+        public static IEnumerable<Percentile[]> Percentiles(IReadOnlyList<double[]> results, double[] positions, int step = 1)
+        {
+            for (int i = 0; i < results[0].Length; i += step)
+            {
+                double[] buffer = null;
+                Percentile[] percentiles = null;
+                Percentile.ComputePercentiles(positions, results.Select(r => r[i]), ref buffer, ref percentiles);
+                yield return percentiles;
+            }
+        }
+
+        /// <summary>
+        /// Plots an area with DefaultTracePercentileInformations
+        /// </summary>
+        public static void PlotArea(PlotModel model, ColourfulTraces traces, int resolution)
+        {
+            var colour = traces.Colour;
+            var name = traces.Name;
+
+            var tpis = DefaultTracePercentileInformations(name, colour);
+            PlotArea(model, traces, tpis, resolution);
+            return;
+        }
+
+        public static void PlotArea(PlotModel model, ColourfulTraces traces, IReadOnlyList<TracePercentileInformation> tpis, int resolution)
+        {
+            var positions = tpis.SelectMany(tpi => tpi.IsSingle ? new[] { tpi.Low } : new[] { tpi.Low, tpi.High }).ToArray();
+            var percentiles = Percentiles(traces.Samples, positions, resolution);
+
+            List<AreaSeries> areas = new List<AreaSeries>();
+            List<LineSeries> lines = new List<LineSeries>();
+            
+            foreach (var tpi in tpis)
+            {
+                if (!tpi.IsSingle)
+                {
+                    var s = new AreaSeries() { Color = tpi.Colour, LineStyle = LineStyle.Solid, StrokeThickness = tpi.StrokeThickness, Title = tpi.Label };
+                    areas.Add(s);
+                    model.Series.Add(s);
+                }
+                else
+                {
+                    var s = new LineSeries() { Color = tpi.Colour, LineStyle = LineStyle.Solid, StrokeThickness = tpi.StrokeThickness, Title = tpi.Label };
+                    lines.Add(s);
+                    model.Series.Add(s);
+                }
+            }
+
+            int i = 0;
+            foreach (var p in percentiles)
+            {
+                long generation = traces.SampleOffset + resolution * traces.SamplePeriod * i;
+                var pcts = p;
+
+                int j = 0;
+                int ai = 0;
+                int li = 0;
+
+                foreach (var tpi in tpis)
+                {
+                    if (!tpi.IsSingle)
+                    {
+                        areas[ai].Points.Add(new DataPoint(generation, pcts[j++].Value));
+                        areas[ai].Points2.Add(new DataPoint(generation, pcts[j++].Value));
+                        ai++;
+                    }
+                    else
+                    {
+                        lines[li].Points.Add(new DataPoint(generation, pcts[j++].Value));
+                        li++;
+                    }
+                }
+
+                i++;
+            }
+        }
+
+        public PlotModel PlotAreas(IEnumerable<ColourfulTraces> manyTraces, string title, int resolution)
+        {
+            PlotModel model = new PlotModel() { Title = title };
+
+            model.Axes.Add(new LinearAxis() { Title = "Generation", Position = AxisPosition.Bottom });
+            model.Axes.Add(new LinearAxis() { Title = "Fitness", Position = AxisPosition.Left });
+
+            foreach (var traces in manyTraces)
+            {
+                PlotArea(model, traces, resolution);
+            }
+            
+            return model;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ConfigurationComposer.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ConfigurationComposer.cs
new file mode 100644
index 0000000..844491c
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ConfigurationComposer.cs
@@ -0,0 +1,327 @@
+using M4M.Hebbian;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using static M4M.Misc;
+
+namespace M4M.ExperimentComposition
+{
+    public class BasicRegularisationFunctionComposer : IGenericComposer<IRegularisationFunction<IGenome>>
+    {
+        public PrefixedComposers<IRegularisationFunction<IGenome>> PrefixedComposers { get; } = new PrefixedComposers<IRegularisationFunction<IGenome>>();
+
+        public IRegularisationFunction<IGenome> Compose(string name, TextWriter console, CliParams clips)
+        {
+            if (PrefixedComposers.TryCompose(name, console, clips, out var done))
+            {
+                return done;
+            }
+
+            // basic reg-funcs
+            return ExperimentParsing.ParseRegFunction(name);
+        }
+    }
+
+    public interface IExperimentConfigurationComposer
+    {
+        ExperimentConfiguration Compose(CliParams clips, TextWriter console, ITargetPackage targetPackage);
+    }
+
+    public class DefaultDevelopmentRulesComposer : IGenericPrefixedComposer<DevelopmentRules>
+    {
+        public IGenericComposer<ISquash> SquashComposer { get; set; }
+
+        public string Prefix => "default";
+        public string Syntax => "";
+
+        public DevelopmentRules Compose(string name, TextWriter console, CliParams clips)
+        {
+            // drules
+            int developmentalTimeSteps = clips.Get("T", int.Parse, 10);
+            if (clips.IsSet("tau"))
+                throw new Exception("Parameter tau has been deprecated; use decayRate (tau2) or updateRate (tau1) instead");
+            double decayRate = clips.Get("decayRate", double.Parse, 0.2);
+            double updateRate = clips.Get("udpateRate", double.Parse, 1.0);
+            double rescaleScale = clips.Get("rescaleScale", double.Parse, 1.0);
+            double rescaleOffset = clips.Get("rescaleOffset", double.Parse, 0.0);
+
+            // g resets
+            ISquash squash = DevelopmentRules.TanhHalf;
+            if (clips.IsSet("squash"))
+            {
+                var squashStr = clips.Get("squash");
+                if (SquashComposer != null)
+                {
+                    squash = SquashComposer.Compose(squashStr, console, clips);
+                }
+                else
+                {
+                    if (squashStr.StartsWith("tanh:"))
+                    { // this shouldn't be here
+                        var factorStr = squashStr.Substring("tanh:".Length);
+                        double factor = double.Parse(factorStr);
+                        squash = new TanhSquash(factor, $"tanh({factorStr})");
+                    }
+                    else
+                    {
+                        squash = DevelopmentRules.CommonSquashes.First(s => s.Name.Equals(squashStr, StringComparison.InvariantCultureIgnoreCase));
+                    }
+                }
+            }
+
+            //
+            // Now, put it all together...
+            //
+
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(
+                squash: squash,
+                timeSteps: developmentalTimeSteps,
+                decayRate: decayRate,
+                updateRate: updateRate,
+                rescaleScale : rescaleScale,
+                rescaleOffset: rescaleOffset
+                );
+
+            return drules;
+        }
+    }
+
+    public class DefaultReproductionRulesComposer : IGenericPrefixedComposer<ReproductionRules>
+    {
+        public string Prefix => "default";
+        public string Syntax => "";
+
+        public ReproductionRules Compose(string name, TextWriter console, CliParams clips)
+        {
+            double gNoiseTerm = clips.Get("MG", double.Parse, 2.0);
+            double bNoiseTerm = clips.Get("MB", double.Parse, 2E-5);
+            
+            NoiseType gNoiseType = clips.Get("MGtype", ExperimentParsing.ParseNoiseType, NoiseType.Binary);
+            NoiseType bNoiseType = clips.Get("MBtype", ExperimentParsing.ParseNoiseType, NoiseType.Uniform);
+            
+            // b mutation weirdness
+            double bprob = clips.Get("bprob", double.Parse, 1.0);
+            bool bex = clips.Get("bex", bool.Parse, false);
+
+            int gUpdates = clips.Get("CG", int.Parse, 1);
+
+            double gMin = clips.Get("GMin", double.Parse, -1);
+            double gMax = clips.Get("GMax", double.Parse, +1);
+            double bMin = clips.Get("BMin", double.Parse, double.NegativeInfinity);
+            double bMax = clips.Get("BMax", double.Parse, double.PositiveInfinity);
+
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(
+                gMutationMagnitude: gNoiseTerm,
+                gMutationType: gNoiseType,
+                bMutationMagnitude: bNoiseTerm,
+                bMutationProbability: bprob,
+                bMutationType: bNoiseType,
+                exclusiveBMutation: bex,
+                gUpdates: gUpdates,
+                gClamping: new Range(gMin, gMax),
+                bClamping: new Range(bMin, bMax)
+                );
+
+            return rrules;
+        }
+    }
+
+    public class DefaultJudgementRulesComposer : IGenericPrefixedComposer<JudgementRules>
+    {
+        public IGenericComposer<IRegularisationFunction<IGenome>> RegularisationFunctionComposer { get; } = new BasicRegularisationFunctionComposer();
+
+        public string Prefix => "default";
+        public string Syntax => "";
+
+        public JudgementRules Compose(string name, TextWriter console, CliParams clips)
+        {
+            double lambda = clips.Get("lambda", double.Parse);
+            double noisefactor = clips.Get("kappa", double.Parse, 0.0);
+
+            IRegularisationFunction<IGenome> regFunc = RegularisationFunctionComposer.Compose(clips.Get("regfunc", "L1"), console, clips);
+            
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(
+                regularisationFactor: lambda,
+                regularisationFunction: regFunc,
+                noiseFactor: noisefactor
+                );
+
+            return jrules;
+        }
+    }
+
+    public class BasicExperimentConfigurationComposer<TGenome> : IExperimentConfigurationComposer where TGenome : IGenome<TGenome>
+    {
+        public PrefixedComposers<DevelopmentRules> DevelopmentRulesComposers { get; set; } = new PrefixedComposers<DevelopmentRules>(new[] { new DefaultDevelopmentRulesComposer() });
+        public PrefixedComposers<ReproductionRules> ReproductionRulesComposers { get; set; } = new PrefixedComposers<ReproductionRules>(new[] { new DefaultReproductionRulesComposer() });
+        public PrefixedComposers<JudgementRules> JudgementRulesComposers { get; set; } = new PrefixedComposers<JudgementRules>(new[] { new DefaultJudgementRulesComposer() });
+
+        public ExperimentConfiguration Compose(CliParams clips, TextWriter console, ITargetPackage targetPackage)
+        {   
+            var drules = DevelopmentRulesComposers.Compose(clips.Get("drules", "default"), console, clips);
+            var rrules = ReproductionRulesComposers.Compose(clips.Get("rrules", "default"), console, clips);
+            var jrules = JudgementRulesComposers.Compose(clips.Get("jrules", "default"), console, clips);
+            
+            int epochs = clips.Get("epochs", Misc.ParseInt, 100000);
+            int K = clips.Get("K", Misc.ParseInt, 1000);
+
+            double gResetProb = clips.Get("gresetprob", double.Parse, 0.0);
+
+            double gResetMin = clips.Get("gresetmin", double.Parse, -1);
+            double gResetMax = clips.Get("gresetmax", double.Parse, +1);
+
+            int N = targetPackage.TargetSize;
+
+            var cycler = Cyclers.GetStandardCycler(clips.Get("cycler", "loop"));
+            var shuffleTargets = clips.Get("shuffleTargets", bool.Parse, false);
+
+            //
+            // Now, put it all together...
+            // TODO: put this in its own Composer?
+            //
+
+            var config = TypicalConfiguration.CreateConfig(
+                N: N,
+                targets: targetPackage.Targets,
+                targetCycler: cycler,
+                epochs: epochs,
+                generationsPerTargetPerEpoch: K,
+                gResetProb: gResetProb,
+                gResetRange: new Range(gResetMin, gResetMax),
+                shuffleTargets: shuffleTargets,
+                drules: drules,
+                rrules: rrules,
+                jrules: jrules
+                );
+
+            return config;
+        }
+    }
+
+    public interface IPopulationExperimentConfigurationComposer<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        PopulationExperimentConfig<TIndividual> Compose(TextWriter console, CliParams clips, ExperimentConfiguration experimentConfiguration);
+    }
+
+    public class DefaultPopulationSpinnerComposer<TIndividual> : IGenericPrefixedComposer<IPopulationSpinner<TIndividual>> where TIndividual : IIndividual<TIndividual>
+    {
+        public string Prefix => "default:";
+        public string Syntax => "type";
+
+        public IPopulationSpinner<TIndividual> Compose(string name, TextWriter console, CliParams clips)
+        {
+            return Parse(name);
+        }
+
+        public static IPopulationSpinner<TIndividual> Parse(string name)
+        {
+            if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return DefaultPopulationSpinner<TIndividual>.Instance;
+            }
+            else if (name.Equals("paired", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PairedPopulationSpinner<TIndividual>.Instance;
+            }
+            else if (name.Equals("trios", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return RecombinantTriosPopulationSpinner<TIndividual>.Instance;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised default population spinner type: {name}; consider one of default/paired/trios");
+            }
+        }
+    }
+
+    public class DensePopulationSpinnerComposer : IGenericPrefixedComposer<IPopulationSpinner<DenseIndividual>>
+    {
+        public string Prefix => "dense:";
+        public string Syntax => "type";
+
+        public IPopulationSpinner<DenseIndividual> Compose(string name, TextWriter console, CliParams clips)
+        {
+            return Parse(name, clips);
+        }
+
+        public static IPopulationSpinner<DenseIndividual> Parse(string name, CliParams clips)
+        {
+            if (name.Equals("deltarule", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var deltaRuleFitnessPowerFactor = clips.Get("deltarulefitnesspowerfactor", double.Parse, 0.0);
+                return new DeltaRule.DeltaRuleSpinner(deltaRuleFitnessPowerFactor);
+            }
+            else if (name.Equals("hebbian", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var deltaRuleFitnessPowerFactor = clips.Get("hebbianfitnesspowerfactor", double.Parse, 0.0);
+                var hebbianType = clips.Get("hebbiantype", ExperimentParsing.ParseHebbianType, Hebbian.HebbianType.Standard);
+                var signedHebbian = clips.Get("signedHebbian", bool.Parse, false);
+                var innerSpinner = clips.Get("innerSpinner", s => Parse(s, clips), null);
+                var noDiagonal = clips.Get("hebbianNoDiagonal", bool.Parse, false);
+                var matrixNormalisation = clips.Get("hebbianMatrixNormalisation", ExperimentParsing.ParseMatrixNormalisation, MatrixNormalisation.None);
+                var fitnessRescale = clips.Get("hebbianFitnessRescale", double.Parse, 1.0);
+                var fitnessOffset = clips.Get("hebbianFitnessOffset", double.Parse, 0.0);
+                return new Hebbian.HebbianSpinner(deltaRuleFitnessPowerFactor, hebbianType, signedHebbian, innerSpinner, noDiagonal, matrixNormalisation, fitnessRescale, fitnessOffset);
+            }
+            else if (name.Equals("hopfield", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var squashStr = clips.Get("squash", "(Tanh(x/2)+1)/2");
+                var updateRate = clips.Get("hopfieldupdaterate", double.Parse);
+                var decayRate = clips.Get("hopfielddecayrate", double.Parse, updateRate);
+                var squash = DevelopmentRules.CommonSquashes.First(s => s.Name.Equals(squashStr, StringComparison.InvariantCultureIgnoreCase)); // deficient
+                var biasType = clips.Get("hopfieldBiasType", ExperimentParsing.ParseBiasType, Hopfield.BiasType.Constant);
+                var interactionType = clips.Get("hopfieldInteractionType", ExperimentParsing.ParseInteractionType, Hopfield.InteractionType.Linear);
+                var interactionMatrixSource = clips.Get("hopfieldInteractionMatrixSource", ExperimentParsing.ParseInteractionMatrixSource, Hopfield.InteractionMatrixSource.Genome);
+                var biasVectorSource = clips.Get("hopfieldBiasVectorSource", ExperimentParsing.ParseBiasVectorSource, Hopfield.BiasVectorSource.Genome);
+                return new Hopfield.HopfieldSpinner(squash, updateRate, decayRate, biasType, interactionType, interactionMatrixSource, biasVectorSource);
+            }
+            else if (name.Equals("lotkavolterra", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var m = clips.Get("growthrate", double.Parse);
+                return new LotkaVolterra.LotkaVolterraSpinner(m);
+            }
+            else
+            {
+                throw new Exception($"Unrecognised Dense population spinner type: {name}; consider deltarule");
+            }
+        }
+    }
+
+    public class BasicDensePopulationExperimentConfigurationComposer : IPopulationExperimentConfigurationComposer<DenseIndividual>
+    {
+        public PrefixedComposers<IPopulationSpinner<DenseIndividual>> SpinnerComposer = new PrefixedComposers<IPopulationSpinner<DenseIndividual>>();
+
+        public BasicDensePopulationExperimentConfigurationComposer(IEnumerable<IGenericPrefixedComposer<IPopulationSpinner<DenseIndividual>>> spinnerComposers, string defaultPopulationSpinnerComposer)
+        {
+            SpinnerComposer = new PrefixedComposers<IPopulationSpinner<DenseIndividual>>(spinnerComposers);
+            DefaultPopulationSpinnerComposer = defaultPopulationSpinnerComposer;
+        }
+
+        public string DefaultPopulationSpinnerComposer { get; set; } = null;
+
+        public PopulationExperimentConfig<DenseIndividual> Compose(TextWriter console, CliParams clips, ExperimentConfiguration experimentConfiguration)
+        {
+            bool binaryGResets = clips.Get("gresetbinary", bool.Parse, experimentConfiguration.ReproductionRules.InitialStateMutationType == NoiseType.Binary);
+            var resetIndividualInitialStateOperation = clips.Get("resetoperation", ExperimentParsing.ParsePopulationResetOperation, experimentConfiguration.InitialStateResetProbability == 0 ? PopulationResetOperations.None : (binaryGResets ? PopulationResetOperations.RandomBinaryIndependent : PopulationResetOperations.RandomIndependent));
+
+            // hill-climber by default
+            bool hillclimberMode = clips.Get("hc", bool.Parse, true);
+            int eliteCount = clips.Get("eliteCount", int.Parse, 1);
+            var spinner = SpinnerComposer.Compose(clips.Get("spinner", DefaultPopulationSpinnerComposer), console, clips);
+            var selectorPreparer = clips.Get("selectorpreparer", ExperimentParsing.ParseSelectorPreparer<DenseIndividual>, SelectorPreparers<DenseIndividual>.Ranked);
+
+            var popConfig = TypicalConfiguration.CreatePopulationConfig(
+                config: experimentConfiguration,
+                eliteCount: eliteCount,
+                hillclimberMode: hillclimberMode,
+                resetIndividualInitialStateOperation: resetIndividualInitialStateOperation,
+                customPopulationSpinner : spinner,
+                selector: selectorPreparer
+                );
+
+            return popConfig;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ExperimentComposer.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ExperimentComposer.cs
new file mode 100644
index 0000000..fe77d32
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/ExperimentComposer.cs
@@ -0,0 +1,294 @@
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using static M4M.Misc;
+
+namespace M4M.ExperimentComposition
+{
+    public interface IGenericComposer<T>
+    {
+        T Compose(string name, TextWriter console, CliParams clips);
+    }
+
+    public interface IPrefixed
+    {
+        string Prefix { get; }
+        string Syntax { get; }
+    }
+
+    public delegate TResult PrefixedOperation<TPrefixed, TResult>(TPrefixed prefixed, string subName);
+
+    public class Prefixeds<TPrefixed> where TPrefixed : IPrefixed
+    {
+        private readonly List<TPrefixed> Table;
+        
+        public Prefixeds(IEnumerable<TPrefixed> prefixeds)
+        {
+            Table = new List<TPrefixed>(prefixeds);
+        }
+        
+        public Prefixeds()
+        {
+            Table = new List<TPrefixed>();
+        }
+
+        public void Add(TPrefixed prefixed)
+        {
+            Table.Add(prefixed);
+        }
+
+        public void Remove(TPrefixed prefixed)
+        {
+            Table.Remove(prefixed);
+        }
+        
+        public TResult Perform<TResult>(string name, PrefixedOperation<TPrefixed, TResult> operation)
+        {
+            if (TryPerform(name, operation, out var result))
+            {
+                return result;
+            }
+            else
+            {
+                throw new Exception($"No composers applicable to '{name}', consider one of the following:\n{StringOfComposers()}");
+            }
+        }
+        
+        public bool TryPerform<TResult>(string name, PrefixedOperation<TPrefixed, TResult> operation, out TResult result)
+        {
+            var prefixed = Table.FirstOrDefault(c => name.StartsWith(c.Prefix));
+
+            if (prefixed == null)
+            {
+                result = default(TResult);
+                return false;
+            }
+            
+            string subName = name.Substring(prefixed.Prefix.Length);
+            result = operation(prefixed, subName);
+            return true;
+        }
+
+        public string StringOfComposers(string prefix = " - ")
+        {
+            StringBuilder sb = new StringBuilder();
+
+            foreach (var c in Table)
+            {
+                sb.AppendLine(prefix + c.Prefix + c.Syntax);
+            }
+
+            return sb.ToString();
+        }
+    }
+
+    public interface IGenericPrefixedComposer<T>
+    {
+        string Prefix { get; }
+        string Syntax { get; }
+        T Compose(string name, TextWriter console, CliParams clips);
+    }
+
+    public class PrefixedComposers<T> : IGenericComposer<T>
+    {
+        private readonly List<IGenericPrefixedComposer<T>> Composers;
+        
+        public PrefixedComposers(IEnumerable<IGenericPrefixedComposer<T>> composers)
+        {
+            Composers = new List<IGenericPrefixedComposer<T>>(composers);
+        }
+        
+        public PrefixedComposers()
+        {
+            Composers = new List<IGenericPrefixedComposer<T>>();
+        }
+
+        public void Add(IGenericPrefixedComposer<T> composer)
+        {
+            Composers.Add(composer);
+        }
+
+        public void Remove(IGenericPrefixedComposer<T> composer)
+        {
+            Composers.Remove(composer);
+        }
+
+        public T Compose(string name, TextWriter console, CliParams clips)
+        {
+            if (name == null)
+                throw new Exception($"No composer indicated; consider one of the following:\n{StringOfComposers()}");
+
+            var composer = Composers.FirstOrDefault(c => name.StartsWith(c.Prefix, StringComparison.InvariantCultureIgnoreCase));
+
+            if (composer == null)
+                throw new Exception($"No composers applicable to '{name}'; consider one of the following:\n{StringOfComposers()}");
+
+            return composer.Compose(name.Substring(composer.Prefix.Length), console, clips);
+        }
+
+        public bool TryCompose(string name, TextWriter console, CliParams clips, out T result)
+        {
+            var composer = Composers.FirstOrDefault(c => name.StartsWith(c.Prefix));
+
+            if (composer == null)
+            {
+                result = default(T);
+                return false;
+            }
+            
+            result = composer.Compose(name.Substring(composer.Prefix.Length), console, clips);
+            return true;
+        }
+
+        public string StringOfComposers(string prefix = " - ")
+        {
+            StringBuilder sb = new StringBuilder();
+
+            foreach (var c in Composers)
+            {
+                sb.AppendLine(prefix + c.Prefix + c.Syntax);
+            }
+
+            return sb.ToString();
+        }
+    }
+
+    public delegate T GenericComposerDelegate<T>(string name, TextWriter console, CliParams clips);
+
+    public class DelegatePrefixedComposer<T> : IGenericPrefixedComposer<T>
+    {
+        public GenericComposerDelegate<T> Delegate { get; }
+        public string Prefix { get; }
+        public string Syntax { get; }
+
+        public DelegatePrefixedComposer(GenericComposerDelegate<T> @delegate, string prefix, string syntax)
+        {
+            Delegate = @delegate;
+            Prefix = prefix;
+            Syntax = syntax;
+        }
+
+        public T Compose(string name, TextWriter console, CliParams clips)
+        {
+            return Delegate(name, console, clips);
+        }
+    }
+
+    public interface IPopulationExperimentConfig<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        IPopulationExperimentConfig<TIndividual> Compose(string topdir, CliParams clips);
+    }
+
+    public interface IExperimentComposer<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        PopulationExperiment<TIndividual> Compose(string topdir, TextWriter console, CliParams clips);
+    }
+
+    public class DefaultExperimentComposer<TIndividual, TGenome> : IExperimentComposer<TIndividual> where TIndividual : IIndividual<TIndividual> where TGenome : IGenome<TGenome>
+    {
+        public DefaultExperimentComposer(ITargetPackageComposer targetPackageComposer, IExperimentConfigurationComposer experimentConfigurationComposer, IPopulationExperimentConfigurationComposer<TIndividual> populationExperimentConfigurationComposer, IPopulationComposer<TIndividual> populationComposer)
+        {
+            TargetPackageComposer = targetPackageComposer;
+            ExperimentConfigurationComposer = experimentConfigurationComposer;
+            PopulationExperimentConfigurationComposer = populationExperimentConfigurationComposer;
+            PopulationComposer = populationComposer;
+        }
+
+        public ITargetPackageComposer TargetPackageComposer { get; set; }
+        public IExperimentConfigurationComposer ExperimentConfigurationComposer { get; set; }
+        public IPopulationExperimentConfigurationComposer<TIndividual> PopulationExperimentConfigurationComposer { get; set; }
+        public IPopulationComposer<TIndividual> PopulationComposer { get; set; }
+
+        public PopulationExperiment<TIndividual> Compose(string topdir, TextWriter console, CliParams clips)
+        {
+            //
+            // create ModelExecutionContext
+            //
+
+            int? seed = clips.Get("seed", s => int.Parse(s), (int?)null);
+            var rand = seed.HasValue ? new MersenneTwister(seed.Value, false) : new MersenneTwister(false);
+            var context = new ModelExecutionContext(rand);
+            
+            //
+            // compose everything
+            //
+
+            var targetPackage = TargetPackageComposer.Compose(console, clips);
+            var config = ExperimentConfigurationComposer.Compose(clips, console, targetPackage);
+            var popConfig = PopulationExperimentConfigurationComposer.Compose(console, clips, config); 
+            var pop = PopulationComposer.Compose(console, clips, context, popConfig);
+
+            //
+            // build path
+            //
+            
+            string name = clips.Get("name");
+            bool appendTimestamp = clips.IsSet("appendTimestamp");
+            topdir = System.IO.Path.Combine(topdir, name);
+            
+            //
+            // prepare the experiment, and return (no IO)
+            //
+            
+            var popExperiment = PopulationExperimentRunners.PrepareExperiment<TIndividual>(pop, popConfig, topdir, "", name, true, appendTimestamp);
+            return popExperiment;
+        }
+
+        public Action<TextWriter, string, CliParams> Gen => (console, topdir, clips) =>
+            {
+                // might need these
+                int? ioSeed = clips.Get("ioSeed", s => int.Parse(s), (int?)null);
+                var ioRand = ioSeed.HasValue ? new MersenneTwister(ioSeed.Value, false) : new MersenneTwister(false);
+
+                // compose
+                var popExperiment = Compose(topdir, console, clips);
+
+                // save
+                popExperiment.FileStuff.CreateDir();
+                popExperiment.Save("starter", false);
+
+                // write out other
+                if (!clips.IsSet("noinitials"))
+                {
+                    PopulationExperimentRunners.WriteOutInitials(ioRand, popExperiment); // mosty for config.txt, so that a human/GradStudent can look it over
+                }
+            };
+    }
+
+    public static class Typical
+    {
+        public static ITargetPackageComposer PrepareDefaultTargetPackageComposer()
+        {
+            var btpc = new BasicTargetPackageComposer(new IGenericPrefixedComposer<ITargetPackage>[]
+            {
+                new ExpTargetPackageComposer(),
+                new IvmcTargetPackageComposer(),
+                new CorrelationMatrixTargetPackageComposer(),
+                new McTargetPackageComposer(),
+                new ClassicTargetPackageComposer(ClassicTargetPackageComposer.DefaultCustomJudgerPrepares),
+                new BinaryPuzzleTargetPackageComposer(),
+            }, null);
+
+            var ftpc = new FileTargetPackageComposer(btpc);
+            btpc.PrefixedComposers.Add(ftpc);
+
+            return btpc;
+        }
+
+        public static readonly DefaultExperimentComposer<DenseIndividual, DenseGenome> DefaultDenseExperimentComposer = PrepareDefaultDenseExperimentComposer();
+
+        public static DefaultExperimentComposer<DenseIndividual, DenseGenome> PrepareDefaultDenseExperimentComposer() => new DefaultExperimentComposer<DenseIndividual, DenseGenome>(
+                PrepareDefaultTargetPackageComposer(),
+                new BasicExperimentConfigurationComposer<DenseGenome>(),
+                new BasicDensePopulationExperimentConfigurationComposer(new IGenericPrefixedComposer<IPopulationSpinner<DenseIndividual>> []
+                    {
+                        new DefaultPopulationSpinnerComposer<DenseIndividual>(),
+                        new DensePopulationSpinnerComposer()
+                    }, "default:default"),
+                new BasicPopulationComposer<DenseIndividual>(new IPrefixedPopulationComposer<DenseIndividual>[] { new BasicDensePopulationComposer(), new DenseGenomePopulationComposer(), new ExpPopulationComposer<DenseIndividual>() }, "dense:hillclimber")
+            );
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/PopulationComposer.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/PopulationComposer.cs
new file mode 100644
index 0000000..4730cf4
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/PopulationComposer.cs
@@ -0,0 +1,257 @@
+using M4M.Epistatics;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M.ExperimentComposition
+{
+    public interface IPopulationComposer<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        Population<TIndividual> Compose(TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<TIndividual> populationExperimentConfiguration);
+    }
+    
+    public interface IPrefixedPopulationComposer<TIndividual> : IPrefixed where TIndividual : IIndividual<TIndividual>
+    {
+        Population<TIndividual> Compose(string populationType, TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<TIndividual> populationExperimentConfiguration);
+    }
+
+    public class BasicPopulationComposer<TIndividual> : IPopulationComposer<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        public BasicPopulationComposer(IReadOnlyList<IPrefixedPopulationComposer<TIndividual>> prefixedComposers, string defaultPopulationType)
+        {
+            PrefixedComposers = new Prefixeds<IPrefixedPopulationComposer<TIndividual>>(prefixedComposers);
+            DefaultPopulationType = defaultPopulationType;
+        }
+
+        public Prefixeds<IPrefixedPopulationComposer<TIndividual>> PrefixedComposers { get; } = new Prefixeds<IPrefixedPopulationComposer<TIndividual>>();
+        public string DefaultPopulationType { get; set; }
+
+        public Population<TIndividual> Compose(TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<TIndividual> populationExperimentConfiguration)
+        {
+            return PrefixedComposers.Perform(clips.Get("pop", DefaultPopulationType), (pc, subName) => pc.Compose(subName, console, clips, context, populationExperimentConfiguration));
+        }
+    }
+
+    public class BasicDensePopulationComposer : IPrefixedPopulationComposer<DenseIndividual>
+    {
+        public string Prefix => "dense:";
+        public string Syntax => "populationtype";
+        
+        public Population<DenseIndividual> Compose(string populationType, TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<DenseIndividual> populationExperimentConfiguration)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("DenseHillclimber parameters include:\n" +
+                    " - popSize\n" +
+                    " - initialstatemutator\n" +
+                    " - transmatmutator\n" +
+                    " - initialstatecombiner\n" +
+                    " - transmatcombiner\n" +
+                    " - transmatopenentries\n" +
+                    " - g0string (extreme value string describing the initial initial-state vector\n" +
+                    " - b0string, columns\n" +
+                    " - templateGenome, a template genome file\n" +
+                    " - rndnormalb, produces a random b with the given stddev around 0, default is 1; multiplies by the otherwise specified matrix if already qualified\n" +
+                    " - rnduniformb, produces a random b with a uniform distribution with the given maximum magnitute; multiplies by the otherwise specified matrix if already qualified");
+            }
+            
+            var popConfig = populationExperimentConfiguration;
+            var config = popConfig.ExperimentConfiguration;
+
+            int popSize = clips.Get("popSize", int.Parse, 1);
+            IInitialStateMutator initialStateMutator = GenomeParsing.GetInitialStateMutator(clips.Get("initialstatemutator", "default"), config.Size);
+            ITransMatMutator transMatMutator = GenomeParsing.GetTransMatMutator(clips.Get("transmatmutator", "default"), config.Size);
+            IInitialStateCombiner initialStateCombiner = GenomeParsing.GetInitialStateCombiner(clips.Get("initialstatecombiner", "default"), config.Size);
+            ITransMatCombiner transMatCombiner = GenomeParsing.GetTransMatCombiner(clips.Get("transmatcombinerr", "default"), config.Size);
+            
+            var gOpenEntries = GenomeParsing.GetInitialStateOpenEntries(clips.Get("initialstateopenentries", "default"), config.Size);
+            var bOpenEntries = GenomeParsing.GetTransMatOpenEntries(clips.Get("transmatopenentries", "default"), config.Size);
+
+            var g0 = ComposeG0(clips, context, config);
+            var b0 = ComposeB0(clips, context, config);
+            var biasVector0 = ComposeBiasVector0(clips, context, config);
+
+            var epigenetic = clips.Get("epigenetic", bool.Parse, false);
+
+            switch (populationType)
+            {
+                case "hillclimber":
+                    return TypicalConfiguration.CreatePopulation(context, popSize, config.Size, config.DevelopmentRules, initialStateMutator: initialStateMutator, transMatMutator: transMatMutator, g0: g0, b0: b0, gOpenEntries: gOpenEntries, bOpenEntries: bOpenEntries, epigenetic: epigenetic, biasVector0: biasVector0);
+            }
+
+            throw new Exception("Unrecognised dense population type: " + populationType);
+        }
+
+        public static MathNet.Numerics.LinearAlgebra.Vector<double> ComposeG0(CliParams clips, ModelExecutionContext context, ExperimentConfiguration config)
+        {
+            // g0
+            var g0string = clips.Get("g0string", null);
+            var g0 = g0string == null ? null : Misc.ParseExtremesVector(g0string, config.ReproductionRules.InitialStateClamping.Min, config.ReproductionRules.InitialStateClamping.Max, 0.0);
+            
+            if (g0 == null && clips.IsSet("templategenome"))
+            {
+                var templateGenome = Analysis.LoadGenome(clips.Get("templategenome"));
+                g0 = templateGenome.InitialState;
+            }
+
+            return g0;
+        }
+
+        public static MathNet.Numerics.LinearAlgebra.Matrix<double> ComposeB0(CliParams clips, ModelExecutionContext context, ExperimentConfiguration config)
+        {
+            // b0
+            var b0string = clips.Get("b0string", null);
+            var b0 = b0string == null ? null : ExperimentParsing.ParseMatrix(b0string);
+
+            if (b0 == null && clips.IsSet("templategenome"))
+            {
+                var templateGenome = Analysis.LoadGenome(clips.Get("templategenome"));
+                b0 = templateGenome.TransMat;
+            }
+
+            if (clips.IsSet("cmtb0"))
+            {
+                var cmt = (config.Targets[0] as CorrelationMatrixTarget).CorrelationMatrix.Clone();
+                b0 = cmt;
+            }
+
+            if (clips.IsSet("rndnormalb"))
+            {
+                var mag = clips.Get("rndnormalb", double.Parse, 1.0);
+                var mean = clips.Get("rndnormalbmean", double.Parse, 0.0);
+                var nrmMat = MathNet.Numerics.LinearAlgebra.CreateMatrix.Random<double>(config.Size, config.Size, new MathNet.Numerics.Distributions.Normal(mean, mag, context.Rand));
+
+                if (b0 != null)
+                    b0.PointwiseMultiply(nrmMat, b0);
+                else
+                    b0 = nrmMat;
+            }
+
+            if (clips.IsSet("rnduniformb"))
+            {
+                var mag = clips.Get("rnduniformb", double.Parse, 1.0);
+                var mean = clips.Get("rnduniformbmean", double.Parse, 0.0);
+                var uniformMat = MathNet.Numerics.LinearAlgebra.CreateMatrix.Random<double>(config.Size, config.Size, new MathNet.Numerics.Distributions.ContinuousUniform(mean - mag, mean + mag, context.Rand));
+
+                if (b0 != null)
+                    b0.PointwiseMultiply(uniformMat, b0);
+                else
+                    b0 = uniformMat;
+            }
+
+            return b0;
+        }
+
+        public static MathNet.Numerics.LinearAlgebra.Vector<double> ComposeBiasVector0(CliParams clips, ModelExecutionContext context, ExperimentConfiguration config)
+        {
+            // biasVector0
+            var biasVector0string = clips.Get("biasVector0", null);
+
+            MathNet.Numerics.LinearAlgebra.Vector<double> biasVector0;
+            if (biasVector0string != null && biasVector0string.StartsWith("flat", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var v = double.Parse(biasVector0string.Substring("flat".Length));
+                biasVector0 = MathNet.Numerics.LinearAlgebra.CreateVector.Dense<double>(config.Size, v);
+            }
+            else
+            {
+                biasVector0 = biasVector0string == null ? null : Misc.ParseExtremesVector(biasVector0string, config.ReproductionRules.InitialStateClamping.Min, config.ReproductionRules.InitialStateClamping.Max, 0.0);
+            }
+
+            if (biasVector0 == null && clips.IsSet("templategenome"))
+            {
+                var templateGenome = Analysis.LoadGenome(clips.Get("templategenome"));
+                biasVector0 = templateGenome.InitialState;
+            }
+
+            if (clips.IsSet("cmtbias"))
+            {
+                var cmtMul = clips.Get("cmtbias", x => x == null ? 1.0 : double.Parse(x));
+
+                var cmt = (config.Targets[0] as CorrelationMatrixTarget).ForcingVector.Clone();
+                if (biasVector0 == null)
+                    biasVector0 = cmt * cmtMul;
+                else
+                    biasVector0 += cmt * cmtMul;
+            }
+
+            return biasVector0;
+        }
+    }
+
+    public class DenseGenomePopulationComposer : IPrefixedPopulationComposer<DenseIndividual>
+    {
+        public string Prefix => "densegenome:";
+        public string Syntax => "filename";
+        
+        public Population<DenseIndividual> Compose(string filename, TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<DenseIndividual> populationExperimentConfiguration)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("DenseGenomePopulationComposer parameters include:\n" +
+                    " - popSize (default is 1)\n" +
+                    " - epigentic");
+            }
+            
+            var popConfig = populationExperimentConfiguration;
+            var config = popConfig.ExperimentConfiguration;
+            var epigentic = clips.Get("epigentic", bool.Parse);
+            
+            DenseGenome fileGenome;
+            try
+            {
+                fileGenome = M4M.Analysis.LoadGenome(filename);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("File did not contain a valid DenseGenome", ex);
+            }
+
+            DenseIndividual template = DenseIndividual.Develop(fileGenome, context, config.DevelopmentRules, epigentic);
+
+            int popSize = clips.Get("popSize", int.Parse, 1);
+
+            var population = new Population<DenseIndividual>(template, popSize);
+
+            return population;
+        }
+    }
+
+    public class ExpPopulationComposer<TIndividual> : IPrefixedPopulationComposer<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        public string Prefix => "exp:";
+        public string Syntax => "populationtype";
+        
+        public Population<TIndividual> Compose(string filename, TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<TIndividual> populationExperimentConfiguration)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("DenseExpPopulationComposer parameters include:\n" +
+                    " - popSize (default is to keep the existing population)");
+            }
+            
+            var popConfig = populationExperimentConfiguration;
+            var config = popConfig.ExperimentConfiguration;
+            
+            Population<TIndividual> filePopulation;
+            try
+            {
+                var exp = M4M.State.GraphSerialisation.Read<PopulationExperiment<TIndividual>>(filename);
+                filePopulation = exp.Population;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("File did not contain a valid PopulationExperiment of " + typeof(TIndividual).FullName, ex);
+            }
+
+            int popSize = clips.Get("popSize", int.Parse, filePopulation.Count);
+
+            var population = new Population<TIndividual>(CliReconfig.TakeLoop(filePopulation.ExtractAll(), popSize));
+
+            return population;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/TargetPackageComposer.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/TargetPackageComposer.cs
new file mode 100644
index 0000000..d111850
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentComposition/TargetPackageComposer.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M.ExperimentComposition
+{
+    public interface ITargetPackageComposer
+    {
+        ITargetPackage Compose(TextWriter console, CliParams clips);
+    }
+
+    public class BasicTargetPackageComposer : ITargetPackageComposer
+    {
+        public BasicTargetPackageComposer(IReadOnlyList<IGenericPrefixedComposer<ITargetPackage>> prefixedComposers, string defaultTargetPackage)
+        {
+            PrefixedComposers = new PrefixedComposers<ITargetPackage>(prefixedComposers);
+            DefaultTargetPackage = defaultTargetPackage;
+        }
+
+        public PrefixedComposers<ITargetPackage> PrefixedComposers { get; }
+        public string DefaultTargetPackage { get; set; }
+
+        public ITargetPackage Compose(TextWriter console, CliParams clips)
+        {
+            var targetPackageStr = DefaultTargetPackage != null ? clips.Get("targetPackage", DefaultTargetPackage) : clips.Get("targetPackage");
+            var targetPackage = PrefixedComposers.Compose(targetPackageStr, console, clips);
+
+            if (clips.IsSet("duties"))
+            {
+                var duties = CliPlotHelpers.ParseDoubleList(clips.Get("duties"));
+
+                if (duties.Count != targetPackage.Targets.Count + 1)
+                {
+                    throw new Exception($"Duty {duties.Count} count was not equal to one plut the target count {targetPackage.Targets.Count}");
+                }
+
+                var dutyTargets = new DutyTarget[targetPackage.Targets.Count];
+                for (int i = 0; i < dutyTargets.Length; i++)
+                {
+                    dutyTargets[i] = new DutyTarget(targetPackage.Targets[i], duties[i], duties[i + 1]);
+                }
+
+                targetPackage = new TargetPackage($"Duty({targetPackage.Name})", dutyTargets);
+            }
+
+            if (clips.Get("saturate", bool.Parse, false))
+            {
+                var satMin = clips.Get("satMin", double.Parse, -1.0);
+                var satThreshold = clips.Get("satThreshold", double.Parse, 0.0);
+                var satMax = clips.Get("satMax", double.Parse, +1.0);
+
+                targetPackage = new TargetPackage($"Sat({targetPackage.Name})", targetPackage.Targets.Select(t => new SaturationTarget(t, satMin, satThreshold, satMax)).ToArray());
+            }
+
+            return targetPackage;
+        }
+    }
+
+    public class ExpTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public string Prefix => "exp:";
+        public string Syntax => "expfile";
+
+        public ITargetPackage Compose(string fileName, TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("ExpTargetPackageComposer reads targets from the given expfile");
+            }
+
+            // read from an experiment file
+            var expConfig = CliPlotHelpers.LoadExperimentConfig(fileName);
+            return new Epistatics.EpistaticTargetPackage(fileName, expConfig.Targets.ToArray());
+        }
+    }
+
+    public class FileTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public FileTargetPackageComposer(ITargetPackageComposer superComposer)
+        {
+            SuperComposer = superComposer;
+        }
+
+        public string Prefix => "file:";
+        public string Syntax => "filename";
+
+        public ITargetPackageComposer SuperComposer { get; }
+
+        public ITargetPackage Compose(string fileName, TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("FileTargetPackageComposer reads multiple targets from the given file (line by line)");
+            }
+
+            // read many from a file
+            var lines = System.IO.File.ReadLines(fileName);
+
+            List<ITarget> targets = new List<ITarget>();
+            foreach (string line in lines)
+            {
+                if (string.IsNullOrEmpty(line) || line.StartsWith("//"))
+                    continue;
+
+                var cclips = clips.CloneCliParams();
+                cclips.ConsumeLine(line);
+                var innerTargets = SuperComposer.Compose(console, clips);
+                targets.AddRange(innerTargets.Targets);
+            }
+
+            return new Epistatics.EpistaticTargetPackage(fileName, targets.ToArray());
+        }
+    }
+
+    public class IvmcTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public string Prefix => "ivmc:";
+        public string Syntax => "ivmctargetclass";
+        
+        public ITargetPackage Compose(string name, TextWriter console, CliParams clips)
+        {
+            return ExperimentParsing.ParseIvmcTargetPackage(console, clips, name);
+        }
+    }
+
+    public class CorrelationMatrixTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public string Prefix => "correl:";
+        public string Syntax => "CorrelationMatrixTargetclass";
+
+        public ITargetPackage Compose(string name, TextWriter console, CliParams clips)
+        {
+            return ExperimentParsing.ParseCorrelationMatrixTargetPackage(clips, name);
+        }
+    }
+
+    public class McTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public string Prefix => "mc:";
+        public string Syntax => "mctargetclass";
+        
+        public ITargetPackage Compose(string name, TextWriter console, CliParams clips)
+        {
+            return ExperimentParsing.ParseMcTargetPackage(console, clips, name);
+        }
+    }
+
+    public class ClassicTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public static Dictionary<string, Func<CliParams, IVectorTargetJudger>> DefaultCustomJudgerPrepares = new Dictionary<string, Func<CliParams, IVectorTargetJudger>>()
+        {
+            ["mse"] = clips => new EuclideanTargetJudger(clips.Get("msefactor", double.Parse), clips.Get("msebias", double.Parse, 0.0))
+        };
+
+        public ClassicTargetPackageComposer(Dictionary<string, Func<CliParams, IVectorTargetJudger>> customJudgerPreparers)
+        {
+            CustomJudgerPreparers = customJudgerPreparers;
+        }
+
+        public ClassicTargetPackageComposer()
+        {
+            CustomJudgerPreparers = new Dictionary<string, Func<CliParams, IVectorTargetJudger>>();
+        }
+
+        public Dictionary<string, Func<CliParams, IVectorTargetJudger>> CustomJudgerPreparers { get; }
+
+        public string Prefix => "classic:";
+        public string Syntax => "classictargetclass";
+        
+        public ITargetPackage Compose(string targetPackageClass, TextWriter console, CliParams clips)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("ClassicTargetPackageComposer assembles classic (M4M/HMOD) target packages. Supported classes include:" +
+                    " - default\n" +
+                    " - modular\n" +
+                    " - orig4x4 and std4x4\n" +
+                    "Common parameters include:\n" +
+                    " - customerjudger, the custom target judger (one of " + string.Join(", ", CustomJudgerPreparers.Keys) + ")\n" +
+                    " - normalisecustomjudger, whether to normalise any custom judger\n" +
+                    " - targetstring, basic +/- sequence\n" +
+                    " - modulesstring, module assignments\n" +
+                    " - variations, modular variations ");
+            }
+
+            if (targetPackageClass.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var targetStrings = clips.Get("targetStrings", s => s.Split('|'));
+                var name = clips.Get("targetpackagename", "classic");
+                
+                IVectorTargetJudger judger = clips.Get("customjudger", s => CustomJudgerPreparers[s](clips), null);
+                bool normaliseCustomJudger = clips.Get("normalisecustomjudger", bool.Parse, true);
+
+                List<VectorTarget> targets = new List<VectorTarget>();
+
+                int i = 0;
+                foreach (var ts in targetStrings)
+                {
+                    var t = new VectorTarget(ts, "Target" + i, judger, normaliseCustomJudger);
+
+                    targets.Add(t);
+                    i++;
+                }
+
+                return new TargetPackage(name, targets);
+            }
+            else if (targetPackageClass.Equals("std4x4", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Modular.ModularTargetPackage.Standard4x4;
+            }
+            else if (targetPackageClass.Equals("orig4x4", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Modular.ModularTargetPackage.Original4x4;
+            }
+            else if (targetPackageClass.StartsWith("modular", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var modulesString = clips.Get("modulesString", targetPackageClass.Substring("modular".Length));
+                var modules = Modular.MultiModulesStringParser.Instance.Parse(modulesString);
+
+                var moduleVariations = clips.Get("variations", s => s.Split('|'));
+
+                var targetString = clips.Get("targetString", new string('+', modules.ElementCount));
+                var baseTargetVector = Misc.ParseExtremesVector(targetString, -1.0, 1.0, 0.0);
+
+                var name = clips.Get("targetpackagename", "classic");
+
+                IVectorTargetJudger judger = clips.Get("customjudger", s => CustomJudgerPreparers[s](clips), null);
+                bool normaliseCustomJudger = clips.Get("normalisecustomjudger", bool.Parse, true);
+
+                List<VectorTarget> targets = new List<VectorTarget>();
+
+                int i = 0;
+                foreach (var mv in moduleVariations)
+                {
+                    var extreme = Misc.ParseExtremesVector(mv, -1.0, 1.0, 0.0).ToArray();
+                    var expanded = MathNet.Numerics.LinearAlgebra.CreateVector.DenseOfArray(modules.Expand(extreme));
+                    expanded.PointwiseMultiply(baseTargetVector, expanded);
+
+                    var t = new VectorTarget(expanded, "ModularTarget" + i, judger, normaliseCustomJudger);
+
+                    targets.Add(t);
+                    i++;
+                }
+
+                return new TargetPackage(name, targets);
+            }
+            else
+            {
+                throw new ArgumentException($"Unrecognised Classic TargetPackageClass: {targetPackageClass}; consider one of:\n" +
+                    " - default\n" +
+                    " - std4x4\n" +
+                    " - orig4x4\n" +
+                    " - modular{modulesstring}", nameof(targetPackageClass));
+            }
+        }
+    }
+    public class BinaryPuzzleTargetPackageComposer : IGenericPrefixedComposer<ITargetPackage>
+    {
+        public string Prefix => "bin:";
+        public string Syntax => "bintargetclass";
+
+        public ITargetPackage Compose(string binaryPuzzleTargetClass, TextWriter console, CliParams clips)
+        {
+            if (binaryPuzzleTargetClass.Equals("square"))
+            {
+                return ExperimentParsing.ParseSquareBinaryPuzzle(clips);
+            }
+            else
+            {
+                throw new ArgumentException($"Unrecognised BinaryPuzzle TargetClass: {binaryPuzzleTargetClass}; consider one of:\n" +
+                    " - square{target}", nameof(binaryPuzzleTargetClass));
+            }
+        }
+    }
+
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/ExpInfo.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/ExpInfo.cs
new file mode 100644
index 0000000..c1077ac
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/ExpInfo.cs
@@ -0,0 +1,93 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace M4M.ExperimentGroups
+{
+    public class ExpSamples<TSample>
+    {
+        public ExpSamples(ExpInfo expInfo, IReadOnlyList<TSample> samples, string comment)
+        {
+            ExpInfo = expInfo ?? throw new ArgumentNullException(nameof(expInfo));
+            Samples = samples ?? throw new ArgumentNullException(nameof(samples));
+            Comment = comment;
+        }
+
+        public ExpInfo ExpInfo { get; }
+        public IReadOnlyList<TSample> Samples { get; }
+        public string Comment { get; }
+    }
+
+    public static class ExpSamplers
+    {
+        public static ExpSamples<double> SampleTrajectory(ExpInfo expInfo, string trajectoryFile, int trajectory, string comment, out int samplePeriod)
+        {
+            var fullpath = System.IO.Path.Combine(expInfo.Dir, trajectoryFile);
+            var samples = Analysis.LoadTrajectories(fullpath, out samplePeriod)[trajectory];
+            return new ExpSamples<double>(expInfo, samples, comment);
+        }
+    }
+
+    // shouldn't be a class, but I'm too lazy to update all the code that uses it
+    public class ExpWholeSamples<TIndividual> : ExpSamples<WholeSample<TIndividual>> where TIndividual : IIndividual<TIndividual>
+    {
+        public ExpWholeSamples(ExpInfo expInfo, IReadOnlyList<WholeSample<TIndividual>> samples, string comment)
+         : base(expInfo, samples, comment)
+        {
+        }
+
+        public static ExpWholeSamples<TIndividual> Sample(ExpInfo expInfo, string wholesampleFilename, string comment)
+        {
+            var fullpath = System.IO.Path.Combine(expInfo.Dir, wholesampleFilename);
+            var samples = WholeSample<TIndividual>.LoadWholeSamples(fullpath);
+            return new ExpWholeSamples<TIndividual>(expInfo, samples, comment);
+        }
+
+        public ExpSamples<TSample> Select<TSample>(Func<WholeSample<TIndividual>, TSample> selector, string comment)
+        {
+            return new ExpSamples<TSample>(ExpInfo, Samples.Select(selector).ToArray(), comment);
+        }
+    }
+
+    public class ExpInfo
+    {
+        public ExpInfo(string dir, string block, string run, int repeat, string fullBlockRunName)
+        {
+            Dir = dir ?? throw new ArgumentNullException(nameof(dir));
+            Block = block;
+            Run = run;
+            Repeat = repeat;
+            FullBlockRunName = fullBlockRunName;
+        }
+
+        public string Dir { get; }
+        public string Block { get; }
+        public string Run { get; }
+        public int Repeat { get; }
+        public string FullBlockRunName { get; }
+
+        /// <summary>
+        /// Enumerates any repeated experiments that can be found in the given directory based on the indicator filePattern.
+        /// Extracts information based on the pattern {blockPrefix}(block){blockPostfix}(run)/r(repeat)/.
+        /// </summary>
+        /// <param name="dir">The directory in which to recursively search.</param>
+        /// <param name="blockPrefix">The block prefix (e.g. name of the run group)</param>
+        /// <param name="filePattern">The file pattern to use as an indicator of a directory containing an appropriate experiment.</param>
+        /// <param name="blockPostfix">The block postfix (e.g. 'runs')</param>
+        /// <returns>Enumerates all the repeat experiments with the given </returns>
+        public static IEnumerable<ExpInfo> EnumerateExps(string dir, string blockPrefix, string filePattern, string blockPostfix)
+        {
+            var files = System.IO.Directory.GetFiles(dir, filePattern, System.IO.SearchOption.AllDirectories);
+            foreach (var f in files)
+            {
+                var block = Regex.Match(f, $@"(?<={blockPrefix})[^\\/]+(?={blockPostfix})").Groups[0].Value;
+                var run = Regex.Match(f, $@"(?<={blockPrefix}{block}{blockPostfix})[^\\/]+(?=[\\/])").Groups[0].Value;
+                var repeat = int.Parse(Regex.Match(f, @"(?<=[\\/]r)\d+(?=[\\/])").Groups[0].Value);
+
+                yield return new ExpInfo(System.IO.Path.GetDirectoryName(f), block, run, repeat, blockPrefix + block + blockPostfix + run);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupPlotting.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupPlotting.cs
new file mode 100644
index 0000000..691505c
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupPlotting.cs
@@ -0,0 +1,127 @@
+using M4M;
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace M4M.ExperimentGroups
+{
+    public class GroupPlotting
+    {
+        public static PlotModel PlotBlockTracees<TIndividual>(IEnumerable<ExpWholeSamples<TIndividual>> exps, Func<WholeSample<TIndividual>, double> sampler) where TIndividual : IIndividual<TIndividual>
+        {
+            var cts = MakeBlockTracees(exps, sampler);
+            return PlotBockTracees(cts);
+        }
+
+        public static PlotModel PlotBockTracees<TSample>(IEnumerable<ExpSamples<TSample>> exps, Func<TSample, double> sampler, int sampleRate)
+        {
+            var cts = MakeBlockTracees(exps, sampler, sampleRate);
+            return PlotBockTracees(cts);
+        }
+        
+        private static PlotModel PlotBockTracees(Dictionary<string, ColourfulTraces> tracees)
+        {
+            var plot = M4M.TrajectoryPlotting.PrepareTrajectoriesPlot("Fitness Trajectories", "Epoch", "Fitness", null, 0, 500, "x", "y", null, false, false, false);
+            var multiPlot = new PlotModel();
+            foreach (var ct in tracees.OrderBy(ct => ct.Key))
+                M4M.EvolvabilityTraces.PlotArea(plot, ct.Value, 1);
+            return plot;
+        }
+
+        public static double TerminalWholesampleSelector<TIndividual>(IEnumerable<WholeSample<TIndividual>> samples) where TIndividual : IIndividual<TIndividual>
+        {
+            return samples.Last().Judgements[0].Judgement.Benefit;
+        }
+
+        public static double MeanWholesampleSelector<TIndividual>(IEnumerable<WholeSample<TIndividual>> samples) where TIndividual : IIndividual<TIndividual>
+        {
+            return samples.Average(s => s.Judgements[0].Judgement.Benefit);
+        }
+
+        public static PlotModel PlotBox<TSample>(IEnumerable<ExpSamples<TSample>> exps, string title, string xTitle, string yTitle, Func<string, string> blockFormatter, Func<IEnumerable<TSample>, double> selector)
+        {
+            blockFormatter = blockFormatter ?? (x => x);
+
+            var plot = new PlotModel() { Title = title };
+            var caxis = new CategoryAxis() { Title = xTitle, Position = AxisPosition.Bottom, Key = "x" };
+            plot.Axes.Add(caxis);
+            plot.Axes.Add(new LinearAxis() { Title = yTitle, Position = AxisPosition.Left, Key = "y" });
+
+            var bp = new BoxPlotSeries();
+            int i = 0;
+            foreach (var block in exps.GroupBy(e => e.ExpInfo.FullBlockRunName).OrderBy(ct => ct.Key))
+            {
+                caxis.Labels.Add(blockFormatter(block.Key));
+                var samples = block.Select(ws => selector(ws.Samples));
+                if (BoxPlotting.Box(i++, samples) is BoxPlotItem bi)
+                    bp.Items.Add(bi);
+            }
+            //bp.BoxWidth = 0.8;
+            plot.Series.Add(bp);
+
+            return plot;
+        }
+
+        public static PlotModel PlotAverage<TSample>(IEnumerable<ExpSamples<TSample>> exps, string title, string xTitle, string yTitle, Func<string, string> blockFormatter, Func<IEnumerable<TSample>, double> selector)
+        {
+            blockFormatter = blockFormatter ?? (x => x);
+
+            var plot = new PlotModel() { Title = title };
+            var caxis = new CategoryAxis() { Title = xTitle, Position = AxisPosition.Bottom, Key = "x" };
+            plot.Axes.Add(caxis);
+            plot.Axes.Add(new LinearAxis() { Title = yTitle, Position = AxisPosition.Left, Key = "y" });
+
+            var cs = new ColumnSeries();
+            int i = 0;
+            foreach (var block in exps.GroupBy(e => e.ExpInfo.FullBlockRunName).OrderBy(ct => ct.Key))
+            {
+                Console.WriteLine(block.Key);
+                Console.WriteLine(blockFormatter(block.Key));
+                caxis.Labels.Add(blockFormatter(block.Key));
+                cs.Items.Add(new ColumnItem(block.Average(s => selector(s.Samples)), i++));
+            }
+
+            plot.Series.Add(cs);
+
+            return plot;
+        }
+
+        public static Dictionary<string, M4M.ColourfulTraces> MakeBlockTracees<TIndividual>(IEnumerable<ExpWholeSamples<TIndividual>> exps, Func<WholeSample<TIndividual>, double> sampler) where TIndividual : IIndividual<TIndividual>
+        {
+            var blocks = exps.GroupBy(e => e.ExpInfo.Block).ToList();
+            var dict = new Dictionary<string, M4M.ColourfulTraces>();
+            var colors = new M4M.TrajectoryColours1D(OxyColors.LightBlue, OxyColors.Red).Colours(blocks.Count);
+
+            int ci = 0;
+            foreach (var block in blocks)
+            {
+                int sr = block.First().Samples[1].Epoch - block.First().Samples[0].Epoch;
+                var samples = block.Select(r => r.Samples.Select(sampler).ToArray()).ToList();
+                var ct = new M4M.ColourfulTraces($"Q={block.Key}", colors[ci++], samples, sr, 0);
+                dict.Add(block.Key, ct);
+            }
+
+            return dict;
+        }
+
+        public static Dictionary<string, M4M.ColourfulTraces> MakeBlockTracees<TSample>(IEnumerable<ExpSamples<TSample>> exps, Func<TSample, double> selector, int sampleRate)
+        {
+            var grps = exps.GroupBy(e => e.ExpInfo.Block).ToList();
+            var dict = new Dictionary<string, M4M.ColourfulTraces>();
+            var colors = new M4M.TrajectoryColours1D(OxyColors.LightBlue, OxyColors.Red).Colours(grps.Count);
+
+            int ci = 0;
+            foreach (var grp in grps)
+            {
+                var samples = grp.Select(r => r.Samples.Select(selector).ToArray()).ToList();
+                var ct = new M4M.ColourfulTraces($"Q={grp.Key}", colors[ci++], samples, sampleRate, 0);
+                dict.Add(grp.Key, ct);
+            }
+
+            return dict;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupRunon.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupRunon.cs
new file mode 100644
index 0000000..a1ab2d0
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupRunon.cs
@@ -0,0 +1,68 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace M4M.ExperimentGroups
+{
+    public class RunonExperimentData<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        // TODO: there is way too much in this constructor
+        public RunonExperimentData(TextWriter console, string dir, string groupName, string runonExpFile, string expFileName, string postfix, string blockTerminator)
+        {
+            Dir = dir;
+            GroupName = groupName;
+
+            RunonExp = M4M.PopulationExperiment<TIndividual>.Load(runonExpFile);
+            Exps = ExpInfo.EnumerateExps(dir, groupName, expFileName, blockTerminator).ToList();
+            Runons = EnumerateRunon(console, Exps, RunonExp, expFileName, postfix).ToArray();
+        }
+
+        public string Dir { get; }
+        public string GroupName { get; }
+
+        public PopulationExperiment<TIndividual> RunonExp { get; }
+        public IReadOnlyList<ExpInfo> Exps { get; }
+        public ExpWholeSamples<TIndividual>[] Runons { get; }
+
+        public static IEnumerable<ExpWholeSamples<TIndividual>> EnumerateRunon(TextWriter console, IEnumerable<ExpInfo> exps, M4M.PopulationExperiment<TIndividual> runonExp, string expFileName, string postfix)
+        {
+            var rand = new M4M.CustomMersenneTwister(1);
+            var context = new M4M.ModelExecutionContext(rand);
+
+            int i = 0;
+            foreach (var e in exps)
+            {
+                var runonDir = System.IO.Path.Combine(e.Dir, "runon" + postfix);
+                if (!System.IO.Directory.Exists(runonDir))
+                    System.IO.Directory.CreateDirectory(runonDir);
+
+                var runonWholesampleFile = System.IO.Path.Combine(runonDir, "wholesampleseRunon.dat");
+                if (System.IO.File.Exists(runonWholesampleFile))
+                {
+                    var wsSat = M4M.WholeSample<TIndividual>.LoadWholeSamples(runonWholesampleFile);
+                    yield return new ExpWholeSamples<TIndividual>(e, wsSat, "runon");
+                }
+                else
+                {
+                    var unRunonExpFile = System.IO.Path.Combine(e.Dir, expFileName);
+                    var unRunonExp = PopulationExperiment<TIndividual>.Load(unRunonExpFile);
+
+                    var exp = PopulationExperimentRunners.PrepareExperiment(unRunonExp.Population, runonExp.PopulationConfig, runonDir, disableAllIO: true, appendTimestamp: false);
+                    var feedback = new WholeSampleFeedback<TIndividual>(new PopulationExperimentFeedback<TIndividual>(), 1, false);
+                    var cliPrefix = $"{i}: ";
+                    i++;
+
+                    PopulationExperimentRunners.RunEpochs(console, cliPrefix, runonExp.PopulationConfig.ExperimentConfiguration.Epochs, context, exp, feedback.Feedback, -1, -1, nosave: true);
+                    var wsRunon = feedback.WholeSamples;
+
+                    exp.Save("runon", false);
+                    WholeSample<TIndividual>.SaveWholeSamples(runonWholesampleFile, wsRunon);
+                    yield return new ExpWholeSamples<TIndividual>(e, wsRunon, "runon");
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupSaturation.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupSaturation.cs
new file mode 100644
index 0000000..0088f6c
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentGroups/GroupSaturation.cs
@@ -0,0 +1,111 @@
+using M4M;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace M4M.ExperimentGroups
+{
+    public class SaturatedExperimentData<TIndividual> where TIndividual : IIndividual<TIndividual>
+    {
+        // TODO: too much stuff in this constructor
+        public SaturatedExperimentData(TextWriter console, string dir, string groupName, string satExpFile, string wholesampleFilename, string blockTerminator, ProjectionMode projectionMode, bool saturateExistingTarget)
+        {
+            Dir = dir;
+            GroupName = groupName;
+            ProjectionMode = projectionMode;
+
+            SatExp = M4M.PopulationExperiment<TIndividual>.Load(satExpFile);
+            Exps = ExpInfo.EnumerateExps(dir, groupName, wholesampleFilename + ".dat", blockTerminator).ToList();
+            Sats = EnumerateSaturated(console, Exps, SatExp, wholesampleFilename, projectionMode, saturateExistingTarget).ToArray();
+        }
+
+        public string Dir { get; }
+        public string GroupName { get; }
+        public ProjectionMode ProjectionMode { get; }
+
+        public PopulationExperiment<TIndividual> SatExp { get; }
+        public IReadOnlyList<ExpInfo> Exps { get; }
+        public ExpWholeSamples<TIndividual>[] Sats { get; }
+
+        public static PopulationExperiment<TIndividual> ReplaceTargets(PopulationExperiment<TIndividual> exp, ITarget[] targets, FileStuff fileStuff)
+        {
+            var popConfig = exp.PopulationConfig;
+            var config = exp.PopulationConfig.ExperimentConfiguration;
+            var newConfig = new ExperimentConfiguration(config.Size, targets, config.TargetCycler, config.Epochs, config.GenerationsPerTargetPerEpoch, config.InitialStateResetProbability, config.InitialStateResetRange, config.ShuffleTargets, config.DevelopmentRules, config.ReproductionRules, config.JudgementRules);
+            var newPopConfig = new PopulationExperimentConfig<TIndividual>(newConfig, popConfig.SelectorPreparer, popConfig.EliteCount, popConfig.HillclimberMode, popConfig.PopulationEndTargetOperation, popConfig.PopulationResetOperation, popConfig.CustomPopulationSpinner);
+            var newExp = new PopulationExperiment<TIndividual>(exp.Population, newPopConfig, fileStuff);
+            return newExp;
+        }
+
+        public static PopulationExperiment<TIndividual> Saturate(PopulationExperiment<TIndividual> exp, ITarget[] targets,  double min, double threshold, double max, FileStuff fileStuff)
+        {
+            var satTargets = targets.Select(t => new SaturationTarget(t, min, threshold, max)).ToArray();
+            return ReplaceTargets(exp, satTargets, fileStuff);
+        }
+
+        public static IEnumerable<ExpWholeSamples<TIndividual>> EnumerateSaturated(TextWriter console, IEnumerable<ExpInfo> exps, M4M.PopulationExperiment<TIndividual> satExp, string wholesampleFilename, ProjectionMode projectionMode, bool saturateExistingTarget)
+        {
+            var rand = new M4M.CustomMersenneTwister(1);
+            var context = new M4M.ModelExecutionContext(rand);
+            IWholeSampleProjector<TIndividual> projector = saturateExistingTarget
+                ? null 
+                : new M4M.BasicExtractorWholeSampleProjectorPreparer(projectionMode, false).PrepareProjector(satExp);
+
+            foreach (var e in exps)
+            {
+                var satPath = System.IO.Path.Combine(e.Dir, wholesampleFilename + "sat.dat");
+                if (System.IO.File.Exists(satPath))
+                {
+                    var wsSat = M4M.WholeSample<TIndividual>.LoadWholeSamples(satPath);
+                    yield return new ExpWholeSamples<TIndividual>(e, wsSat, "saturated");
+                }
+                else
+                {
+                    var unSatPath = System.IO.Path.Combine(e.Dir, wholesampleFilename + ".dat");
+                    var ws = M4M.WholeSample<TIndividual>.LoadWholeSamples(unSatPath);
+
+                    if (saturateExistingTarget)
+                    {
+                        var targets = new[] { ws.Last().Target };
+                        var satTargetTemplate = (SaturationTarget)satExp.PopulationConfig.ExperimentConfiguration.Targets[0];
+                        var fileStuff = FileStuff.CreateNow(e.Dir, "", "", false, false);
+                        var exp = Saturate(satExp, targets, satTargetTemplate.Min, satTargetTemplate.Threshold, satTargetTemplate.Max, fileStuff);
+                        projector = new M4M.BasicExtractorWholeSampleProjectorPreparer(projectionMode, false).PrepareProjector(exp);
+                        exp.Save("sat", false);
+                        console.WriteLine("SaturateExistingTarget: " + exp.FileStuff.OutDir);
+                    }
+
+                    var wsSat = M4M.WholeSampleProjectionHelpers.Project(console, context, ws, projector, false, false);
+                    M4M.WholeSample<TIndividual>.SaveWholeSamples(satPath, wsSat);
+                    yield return new ExpWholeSamples<TIndividual>(e, wsSat, "saturated");
+                }
+            }
+        }
+
+        public static void ProcessWholeSamples(TextWriter console, IEnumerable<ExpWholeSamples<TIndividual>> sats, M4M.PopulationExperiment<TIndividual> satExp, Func<string, string> blockFormatter)
+        {
+            blockFormatter = blockFormatter ?? (x => x);
+
+            var satTarget = (M4M.SaturationTarget)satExp.PopulationConfig.ExperimentConfiguration.Targets[0];
+
+            if (satTarget.Target is M4M.Epistatics.CorrelationMatrixTarget cmTarget)
+            {
+                var bMax = cmTarget.CorrelationMatrix.Enumerate().Count(x => x != 0.0);
+
+                foreach (var e in sats)
+                {
+                    var last = e.Samples.Last();
+                    var bt = last.Judgements[0].Judgement.Benefit;
+                    var flatLine = e.Samples.AsEnumerable().Reverse().TakeWhile(s => s.Judgements[0].Judgement.Benefit == bt);
+                    console.WriteLine($"{blockFormatter(e.ExpInfo.Block)}/runs{e.ExpInfo.Run}/r{e.ExpInfo.Repeat}: {last.Epoch - flatLine.Last().Epoch} epochs at {bt}/{bMax}");
+                }
+            }
+            else// if (satTarget.Target is M4M.Epistatics.IIvmcProperTarget ivmcProperTarget)
+            {
+                console.WriteLine("ProcessWholeSamples: Unsupproted target type: " + satTarget.Target.GetType().FullName);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.New/ExperimentParsing.cs b/M4MCode/M4M_MkI/M4M.New/ExperimentParsing.cs
new file mode 100644
index 0000000..7979fa5
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/ExperimentParsing.cs
@@ -0,0 +1,816 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using M4M.Epistatics;
+using M4M.Modular;
+using MathNet.Numerics.Random;
+using static M4M.Misc;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class ExperimentParsing
+    {
+        public static NoiseType ParseNoiseType(string name)
+        {
+            if (string.Equals(name, "uniform", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return NoiseType.Uniform;
+            }
+            else if (string.Equals(name, "binary", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return NoiseType.Binary;
+            }
+            else if (string.Equals(name, "gaussian", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return NoiseType.Gaussian;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised noise type '{name}'");
+            }
+        }
+
+        public static Hebbian.HebbianType ParseHebbianType(string name)
+        {
+            if (string.Equals(name, "standard", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hebbian.HebbianType.Standard;
+            }
+            else if (string.Equals(name, "increaseonly", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hebbian.HebbianType.IncreaseOnly;
+            }
+            else if (string.Equals(name, "decreaseonly", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hebbian.HebbianType.DecreaseOnly;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised hebbian type '{name}'");
+            }
+        }
+
+        public static Hebbian.MatrixNormalisation ParseMatrixNormalisation(string name)
+        {
+            if (string.Equals(name, "none", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hebbian.MatrixNormalisation.None;
+            }
+            else if (string.Equals(name, "iterative", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hebbian.MatrixNormalisation.IterativeNormalisation;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised matrix normalisation '{name}'");
+            }
+        }
+
+        public static Hopfield.BiasType ParseBiasType(string name)
+        {
+            if (string.Equals(name, "none", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasType.None;
+            }
+            else if (string.Equals(name, "constant", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasType.Constant;
+            }
+            else if (string.Equals(name, "linear", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasType.Linear;
+            }
+            else if (string.Equals(name, "quadratic", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasType.Quadratic;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised bias type '{name}'");
+            }
+        }
+
+        public static Hopfield.InteractionType ParseInteractionType(string name)
+        {
+            if (string.Equals(name, "none", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.InteractionType.None;
+            }
+            else if (string.Equals(name, "linear", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.InteractionType.Linear;
+            }
+            else if (string.Equals(name, "quadratic", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.InteractionType.Quadratic;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised interaction type '{name}'");
+            }
+        }
+
+        public static Hopfield.InteractionMatrixSource ParseInteractionMatrixSource(string name)
+        {
+            if (string.Equals(name, "genome", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.InteractionMatrixSource.Genome;
+            }
+            else if (string.Equals(name, "target", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.InteractionMatrixSource.Target;
+            }
+            else if (string.Equals(name, "genomeAndTarget", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.InteractionMatrixSource.GenomeAndTarget;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised interaction matrix source '{name}'");
+            }
+        }
+
+        public static Hopfield.BiasVectorSource ParseBiasVectorSource(string name)
+        {
+            if (string.Equals(name, "genome", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasVectorSource.Genome;
+            }
+            else if (string.Equals(name, "target", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasVectorSource.Target;
+            }
+            else if (string.Equals(name, "genomeAndTarget", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return Hopfield.BiasVectorSource.GenomeAndTarget;
+            }
+            else
+            {
+                throw new Exception($"Unrecognised bias vector source type '{name}'");
+            }
+        }
+
+        public static Population<DenseIndividual> ParseDensePopulation(TextWriter console, CliParams clips, ModelExecutionContext context, PopulationExperimentConfig<DenseIndividual> popConfig, string populationType)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("DensePopulations can either come from an experiment file (exp:filename), or be one of the following types:\n" +
+                    " - hillclimber\n" +
+                    "Generally applicable parameters include:\n" +
+                    " - popSize");
+            }
+
+            if (populationType.StartsWith("exp:", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var filename = populationType.Substring("exp:".Length);
+
+                PopulationExperiment<DenseIndividual> exp = PopulationExperiment<DenseIndividual>.Load(filename);
+                return exp.Population;
+            }
+
+            var config = popConfig.ExperimentConfiguration;
+
+            int popSize = clips.Get("popSize", int.Parse, 1);
+            ITransMatMutator transMatMutator = GenomeParsing.GetTransMatMutator(clips.Get("transmatmutator", "default"), config.Size);
+
+            // g0
+            var g0string = clips.Get("g0string", null);
+            Linear.Vector<double> g0 = g0string == null ? null : ParseExtremesVector(g0string, config.ReproductionRules.InitialStateClamping.Min, config.ReproductionRules.InitialStateClamping.Max, 0.0);
+
+            switch (populationType)
+            {
+                case "hillclimber":
+                    return TypicalConfiguration.CreatePopulation(context, popSize, config.Size, config.DevelopmentRules, transMatMutator: transMatMutator, g0: g0);
+            }
+
+            throw new Exception("Unparsable population type: " + populationType);
+        }
+
+        public static Linear.Matrix<double> ParseMatrix(string str)
+        {
+            string type = null;
+
+            if (str.Contains(":"))
+            {
+                var data = str.Split(':');
+                type = data[0];
+                str = data[1];
+            }
+
+            // TODO: more
+            switch (type)
+            {
+                case "flat":
+                {
+                    var data = str.Split(';');
+                    var size = int.Parse(data[0]);
+                    var val = double.Parse(data[1]);
+                    return Linear.CreateMatrix.Dense<double>(size, size, val);
+                }
+                case "diagonal":
+                {
+                    var data = str.Split(';');
+                    var size = int.Parse(data[0]);
+                    var val = double.Parse(data[1]);
+                    return Linear.CreateMatrix.DenseDiagonal<double>(size, size, val);
+                }
+                case "cols":
+                    var cols = str.Split(';').Select(double.Parse).ToArray();
+                    return Misc.Columns(cols.Length, cols);
+                case "rows":
+                    var rows = str.Split(';').Select(double.Parse).ToArray();
+                    return Misc.Rows(rows, rows.Length);
+                case "dense":
+                {
+                    var data = str.Split(';');
+                    var intra = double.Parse(data[0]);
+                    var inter = double.Parse(data[1]);
+                    var modules = MultiModulesStringParser.Instance.Parse(string.Join(";", data.Skip(2)));
+                    return Misc.Dense(modules, intra, inter);
+
+                }
+                default:
+                    throw new Exception("Unparsable matrix string: " + str);
+            }
+        }
+
+        public static IPopulationResetOperation<DenseIndividual> ParsePopulationResetOperation(string name)
+        {
+            if (string.Equals(name, "none", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PopulationResetOperations.None;
+            }
+            else if (string.Equals(name, "uniform", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PopulationResetOperations.RandomIndependent;
+            }
+            else if (string.Equals(name, "binary", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PopulationResetOperations.RandomBinaryIndependent;
+            }
+            else if (string.Equals(name, "zero", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PopulationResetOperations.Zero;
+            }
+            else if (string.Equals(name, "perfect", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PopulationResetOperations.MatchTargetVectorPerfectPhenotype;
+            }
+            else if (name.StartsWith("modular", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var data = name.Substring("modular".Length).Split(';');
+
+                var noiseType = ParseNoiseType(data[0]);
+                var noiseMag = double.Parse(data[1]);
+                var modules = MultiModulesStringParser.Instance.Parse(data[2]);
+
+                return new RandomModulesPopulationResetOperation(modules, noiseType, noiseMag);
+            }
+            else if (name.StartsWith("NeutralMutation", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var count = ParseInt(name.Substring("NeutralMutation".Length));
+                return new NeutralMutationPopulationResetOperation(count);
+            }
+            else if (string.Equals(name, "forcing", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return PopulationResetOperations.MatchForcingVector;
+            }
+            else if (name.StartsWith("additiveNoise", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var data = name.Substring("additiveNoise".Length).Split(';');
+
+                var noiseType = ParseNoiseType(data[0]);
+                var noiseMag = double.Parse(data[1]);
+                var clamp = data.Length < 3 ? true : bool.Parse(data[2]);
+
+                return new AdditiveNoisePopulationResetOperation(noiseType, noiseMag, clamp);
+            }
+            else if (name.StartsWith("IterativeNormalisation", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var rowSum = double.Parse(name.Substring("IterativeNormalisation".Length));
+                return new IterativeNormalisationResetOperation(rowSum);
+            }
+            else if (name.StartsWith("combined", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var data = name.Substring("combined".Length).Split(new[] { "||" }, StringSplitOptions.None);
+
+                var subOps = data.Select(ParsePopulationResetOperation).ToArray();
+                return new CombinedPopulationResetOperation<DenseIndividual>(subOps);
+            }
+            else
+            {
+                throw new ArgumentException($"Unrecognised reset operation: \"{name}\"");
+            }
+        }
+
+        public static ISelectorPreparer<TIndividual> ParseSelectorPreparer<TIndividual>(string name) where TIndividual : IIndividual<TIndividual>
+        {
+            if (string.Equals(name, "ranked", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return SelectorPreparers<TIndividual>.Ranked;
+            }
+            else if (string.Equals(name, "pareto", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return SelectorPreparers<TIndividual>.Pareto;
+            }
+            else if (string.Equals(name, "proportional", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return SelectorPreparers<TIndividual>.Proportional;
+            }
+            else
+            {
+                throw new ArgumentException($"Unrecognised selector preparer: \"{name}\". Consider one of: ranked, pareto, proportional");
+            }    
+        }
+
+        // let's start to think about a more general target loading (really we should be a table, or something...)
+        public static EpistaticTargetPackage ParseEpistaticTargetPackage(TextWriter console, CliParams clips, string defaultTargetPackageClass, string targetPackageName)
+        {
+            console.WriteLine($"({defaultTargetPackageClass}) {targetPackageName}");
+
+            // compound from file
+            if (targetPackageName.StartsWith("file:", StringComparison.InvariantCultureIgnoreCase))
+            {
+                // read many from a file
+                var filename = targetPackageName.Substring("file:".Length);
+                var lines = System.IO.File.ReadLines(filename);
+                
+                List<ITarget> targets = new List<ITarget>();
+                foreach (string line in lines)
+                {
+                    if (string.IsNullOrEmpty(line) || line.StartsWith("//"))
+                        continue;
+
+                    var cclips = clips.CloneCliParams();
+                    cclips.ConsumeLine(line);
+                    targets.AddRange(ParseEpistaticTargetPackage(console, cclips, defaultTargetPackageClass, cclips.Get("targetpackage")).Targets);
+                }
+
+                return new EpistaticTargetPackage(filename, targets.ToArray());
+            }
+
+            // from expfile
+            if (targetPackageName.StartsWith("exp:", StringComparison.InvariantCultureIgnoreCase))
+            {
+                // read many from a experiment file
+                var filename = targetPackageName.Substring("exp:".Length);
+
+                var expConfig = CliPlotHelpers.LoadExperimentConfig(filename);
+
+                return new EpistaticTargetPackage(filename, expConfig.Targets.ToArray());
+            }
+
+            // determine class
+            string targetPackageClass;
+            if (targetPackageName.Contains("/"))
+            {
+                string[] data = targetPackageName.Split('/');
+                targetPackageClass = data[0];
+                targetPackageName = data[1];
+            }
+            else
+            {
+                targetPackageClass = defaultTargetPackageClass;
+            }
+
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("Recognised TargetPackageClasses:\n" +
+                    " - ivmc\n" +
+                    " - mc");
+            }
+            
+            // TODO: this should be less terrible
+            Dictionary<string, Func<TextWriter, CliParams, string, EpistaticTargetPackage>> parsers = new Dictionary<string, Func<TextWriter, CliParams, string, EpistaticTargetPackage>>(StringComparer.InvariantCultureIgnoreCase);
+            parsers.Add("ivmc", ParseIvmcTargetPackage);
+            parsers.Add("mc", ParseMcTargetPackage);
+
+            if (parsers.TryGetValue(targetPackageClass, out var parser))
+            {
+                return parser(console, clips, targetPackageName);
+            }
+            else
+            {
+                throw new Exception("Unrecognised target package class '" + targetPackageClass + "'. Qualify the class (e.g. 'ivmc/') or method (e.g. 'exp:'");
+            }
+        }
+
+        public static EpistaticTargetPackage ParseMcTargetPackage(TextWriter console, CliParams clips, string targetPackageName)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("McTargetPackage Options (checkoncheck):\n" +
+                    " - MCn\n" +
+                    " - MCk\n" +
+                    " - MCp");
+            }
+
+            int n = clips.Get("MCn", int.Parse);
+            int k = clips.Get("MCk", int.Parse);
+            double p = clips.Get("MCp", double.Parse);
+
+            if (string.Equals(targetPackageName, "checkoncheck", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return new EpistaticTargetPackage($"MCCheckOnCheck_n{n}k{k}", new[] { new MCTargetCheckOnCheck(n, k, p) });
+            }
+            else if (string.Equals(targetPackageName, "plain", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return new EpistaticTargetPackage($"MCPlainOnPlain_n{n}k{k}", new[] { new MCTargetPlainOnPlain(n, k, p) });
+            }
+            else
+            {
+                throw new Exception("Unrecognised MC TargetPackage: '" + targetPackageName + "'");
+            }
+        }
+
+        public static EpistaticTargetPackage ParseIvmcSinusoidTargetPackage(TextWriter console, CliParams clips, string targetPackageName)
+        {
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("IvmcSinusoidTargetPackage Options (4/custom):\n" +
+                    " - plusphases and minusphases\n" +
+                    " - plusamps and minusamps\n" +
+                    " - plusfreqs and minusfreqs\n" +
+                    " - targetString (+/-)\n" +
+                    " - modulesString");
+            }
+            
+            var judgementPreparer = ParseIvmcProperJudgementPreparer(console, clips);
+            
+            if (targetPackageName == "4")
+            {
+                var period = clips.Get("period", double.Parse);
+                var freq = 1.0 / period;
+                var qperiod = period / 8.0;
+                var mean = clips.Get("mean", double.Parse);
+                var amp = clips.Get("amp", double.Parse);
+
+                var target = ParseExtremesVector("++++++++++++++++", -1.0, 1.0, 0.0);
+                var modules = Modular.MultiModulesStringParser.Instance.Parse("4*4");
+
+                var plusPhases = new[] { qperiod * 0, qperiod * 1, qperiod * 2, qperiod * 3 };
+                var minusPhases = new[] { qperiod * 4, qperiod * 5, qperiod * 6, qperiod * 7};
+                var plusAmps = new[] { amp, amp, amp, amp };
+                var minusAmps = new[] { amp, amp, amp, amp };
+                var plusFreqs = new[] { freq, freq, freq, freq };
+                var minusFreqs = new[] { freq, freq, freq, freq };
+                var plusMeans = new[] { mean, mean, mean, mean };
+                var minusMeans = new[] { mean, mean, mean, mean };
+
+                string friendlyName = $"IvmcProperSinusoid4P{period}M{mean}A{amp}";
+                
+                return new EpistaticTargetPackage(friendlyName, new[]
+                {
+                new Epistatics.IvmcProperSinusoid(
+                    judgementPreparer, modules,
+                    plusPhases, minusPhases,
+                    plusAmps, minusAmps,
+                    plusFreqs, minusFreqs,
+                    plusMeans, minusMeans,
+                    target, friendlyName)
+                });
+            }
+            else if (string.Equals(targetPackageName, "custom", StringComparison.InvariantCultureIgnoreCase))
+            {
+                string targetString = clips.Get("targetString");
+                var target = ParseExtremesVector(targetString, -1.0, 1.0, 0.0);
+
+                string modulesString = clips.Get("modulesString");
+                var modules = Modular.MultiModulesStringParser.Instance.Parse(modulesString);
+
+                var plusPhases = clips.Get("plusPhases", ParseDoubleList);
+                var minusPhases = clips.Get("minusPhases", ParseDoubleList);
+                var plusAmps = clips.Get("plusAmps", ParseDoubleList);
+                var minusAmps = clips.Get("minusAmps", ParseDoubleList);
+                var plusFreqs = clips.Get("plusFreqs", ParseDoubleList, clips.Get("plusPeriods", s => ParseDoubleList(s).Select(d => 1.0 / d).ToArray()));
+                var minusFreqs = clips.Get("minusFreqs", ParseDoubleList, clips.Get("minusPeriods", s => ParseDoubleList(s).Select(d => 1.0 / d).ToArray()));
+                var plusMeans = clips.Get("plusMeans", ParseDoubleList);
+                var minusMeans = clips.Get("minusMeans", ParseDoubleList);
+
+                string friendlyName = "Custom IvmcProperSinusoid";
+
+                return new EpistaticTargetPackage(friendlyName, new[]
+                {
+                    new Epistatics.IvmcProperSinusoid(judgementPreparer, modules, plusPhases, minusPhases, plusAmps, minusAmps, plusFreqs, minusFreqs, plusMeans, minusMeans, target, friendlyName)
+                });
+            }
+
+            throw new Exception("Unrecognised TargetPackage name: " + targetPackageName + "\n" +
+                "Try one of:\n" +
+                " - 4 (4x4 modules)\n" +
+                " - custom");
+        }
+
+        public static IIvmcProperJudgementPreparer ParseIvmcProperJudgementPreparer(TextWriter console, CliParams clips)
+        {
+            var judgementMode = clips.Get("judgemode", TraditionalIvmcProperJudgementPreparer.ParseTraditionalIvmcProperJudgementMode, IvmcProperJudgementMode.Proper);
+
+            if (judgementMode == IvmcProperJudgementMode.Stacked)
+            {
+                var mbe = ModuleBenefitFunctions.ParseModuleBenefitFunction(clips.Get("modulebenefitfunction", "split"));
+                var decayFactor = clips.Get("decayfactor", double.Parse, 1.0);
+                var rescaleFitness = clips.Get("rescalefitness", bool.Parse, true);
+                var scaleFactor = clips.Get("scalefactor", int.Parse, 2);
+                return new IvmcStackedProperJudgementPreparer(mbe, decayFactor, rescaleFitness, scaleFactor);
+            }
+            else
+            {
+                return new TraditionalIvmcProperJudgementPreparer(judgementMode);
+            }
+        }
+
+        public static EpistaticTargetPackage ParseIvmcTargetPackage(TextWriter console, CliParams clips, string targetPackageName)
+        {
+            if (targetPackageName.StartsWith("sin:", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return ParseIvmcSinusoidTargetPackage(console, clips, targetPackageName.Substring("sin:".Length));
+            }
+
+            if (clips.IsSet("help"))
+            {
+                console.WriteLine("ProperIvmcTargetPackage Options (4/custom):\n" +
+                    " - Z (Z rate)\n" +
+                    " - CH (High fitness, default 1.0)\n" +
+                    " - CL (Low fitness, default 0.7)\n" +
+                    " - C0 (Zero fitness, default 0.0)\n" +
+                    " - targetString (+/-)\n" +
+                    " - modulesString\n" +
+                    " - variationmodulesString (default null)\n" +
+                    " - variationModulePlusOverridesString (ivmcvmpos, default null)\n" +
+                    " - variationModuleMinusOverridesString (ivmcvmmos, default null)\n" +
+                    " - judgemode (IvmcProperJudgementMode, one of: proper, split, stackedsplit, or stacked)\n" +
+                    "     if stacked, then qualify\n" +
+                    "      - modulebenefitfunction (default split)\n" +
+                    "      - decayfactor (default 1.0)\n" +
+                    "      - rescalefitness (default true)\n" +
+                    "      - scalefactor (default 2.0)");
+            }
+            
+            double cHigh = clips.Get("CH", double.Parse, 1.0);
+            double cLow = clips.Get("CL", double.Parse, 0.7);
+            double cZero = clips.Get("C0", double.Parse, 0.0);
+            double Z = clips.Get("Z", double.Parse);
+
+            if (targetPackageName == "4")
+            {
+                var judgementMode = clips.Get("judgemode", TraditionalIvmcProperJudgementPreparer.ParseTraditionalIvmcProperJudgementMode, IvmcProperJudgementMode.Proper);
+                return IvmcTargetPackages.IvmcProper1_4x4(judgementMode, cHigh, cLow, cZero, Z);
+            }
+            else if (string.Equals(targetPackageName, "custom", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var judgementPreparer = ParseIvmcProperJudgementPreparer(console, clips);
+                string targetString = clips.Get("targetString");
+                string modulesString = clips.Get("modulesString");
+                string variationModulesString = clips.Get("variationmodulesString", clips.Get("ivmcvms", null));
+                string variationModulePlusOverridesString = clips.Get("variationModulePlusOverridesString", clips.Get("ivmcvmpos", null));
+                string variationModuleMinusOverridesString = clips.Get("variationModuleMinusOverridesString", clips.Get("ivmcvmmos", null));
+                int ivmcFixedSampleCount = clips.Get("ivmcFixedSampleCount", int.Parse, 0);
+
+                if (ivmcFixedSampleCount > 0)
+                {
+                    int ivmcFixedSampleSeed = clips.Get("ivmcFixedSampleSeed", int.Parse);
+                    var rand = new CustomMersenneTwister(ivmcFixedSampleSeed);
+                    var moduleCount = MultiModulesStringParser.Instance.Parse(modulesString).ModuleCount;
+                    IvmcTargetPackages.GenerateRandomIvmcHL0Overrides(rand, moduleCount, Z, ivmcFixedSampleCount, out variationModuleMinusOverridesString, out variationModulePlusOverridesString);
+                }
+
+                // multi module-override mode
+                if ((!string.IsNullOrEmpty(variationModulePlusOverridesString) || !string.IsNullOrEmpty(variationModulePlusOverridesString)) 
+                    && (variationModuleMinusOverridesString.Contains('|') || variationModulePlusOverridesString.Contains('|')))
+                {
+                    if (!string.IsNullOrEmpty(variationModulePlusOverridesString) && !string.IsNullOrEmpty(variationModulePlusOverridesString))
+                    {
+                        var variationModuleMinusOverridesData = variationModuleMinusOverridesString.Split('|');
+                        var variationModulePlusOverridesData = variationModulePlusOverridesString.Split('|');
+                        var customNames = clips.Get("targetnames", s => s.Split('|'), null);
+                        
+                        if (variationModuleMinusOverridesData.Length != variationModulePlusOverridesData.Length)
+                            throw new Exception("Multi module-override mode requires the same number of override strings for both Plus and Minus overrides");
+
+                        var targets = new List<ITarget>();
+                        for (int ti = 0; ti < variationModuleMinusOverridesData.Length; ti++)
+                        {
+                            var innerVariationModuleMinusOverridesString = variationModuleMinusOverridesData[ti];
+                            var innerVariationModulePlusOverridesString = variationModulePlusOverridesData[ti];
+                            var targetName = customNames == null ? null : customNames[ti];
+                            targets.Add(IvmcTargetPackages.IvmcProper1_CustomTarget(modulesString, variationModulesString, innerVariationModulePlusOverridesString, innerVariationModuleMinusOverridesString, targetString, judgementPreparer, cHigh, cLow, cZero, Z, targetName));
+                        }
+
+                        return new EpistaticTargetPackage($"IvmcProper1_CustomCH{cHigh}CL{cLow}CZ{cZero}Z{Z}", targets.ToArray());
+                    }
+                    else
+                    {
+                        throw new Exception("Multi module-override mode requires both Plus and Minus override strings to be fully qualified");
+                    }
+                }
+
+                return IvmcTargetPackages.IvmcProper1_Custom(modulesString, variationModulesString, variationModulePlusOverridesString, variationModuleMinusOverridesString, targetString, judgementPreparer, cHigh, cLow, cZero, Z, null);
+            }
+
+            throw new Exception("Unrecognised TargetPackage name: " + targetPackageName + "\n" +
+                "Try one of:\n" +
+                " - 4 (4x4 modules)\n" +
+                " - custom");
+        }
+
+        public static EpistaticTargetPackage ParseCorrelationMatrixTargetPackage(CliParams clips, string targetPackageName)
+        {
+            if (targetPackageName.StartsWith("ncs", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int size = int.Parse(targetPackageName.Substring("ncs".Length));
+                return new EpistaticTargetPackage("ConcentricSquares" + size, new[] { StandardCorrelationMatrixTargets.ConcentricSquares(size, true) });
+            }
+            else if (targetPackageName.StartsWith("llc", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var data = targetPackageName.Substring("llc".Length).Split('x');
+                int width = int.Parse(data[0]);
+                int height = int.Parse(data[1]);
+                var neighbourType = clips.Get("neighbourType", Neighbours.ParseNeightbourType, NeighbourType.Five);
+                var pattern = clips.Get("matrixPattern", MatrixPatterns.GetPattern, CheckPattern.Instance);
+                bool torus = clips.Get("torusTarget", bool.Parse, false);
+                return new EpistaticTargetPackage($"Linear{pattern.Name}Local{neighbourType}Constraints{width}x{height}{(torus ? "Torus" : "")}", new[] { StandardCorrelationMatrixTargets.LinearLocalConstraints(width, height, pattern, neighbourType, torus, false) });
+            }
+            else if (targetPackageName.StartsWith("cs", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int size = int.Parse(targetPackageName.Substring("cs".Length));
+                return new EpistaticTargetPackage("ConcentricSquares" + size, new[] { StandardCorrelationMatrixTargets.ConcentricSquares(size, false) });
+            }
+            else if (targetPackageName.StartsWith("partialcs", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var seed = clips.Get("targetpackageseed", int.Parse);
+                var rand = new CustomMersenneTwister(seed);
+
+                var pruneProbability = clips.Get("pruneProbability", double.Parse);
+                int size = int.Parse(targetPackageName.Substring("partialcs".Length));
+                return new EpistaticTargetPackage("PartialConcentricSquares" + size + "P" + pruneProbability, new[] { StandardCorrelationMatrixTargets.PartialConcentricSquares(size, false, pruneProbability, rand) });
+            }
+            else if (targetPackageName.StartsWith("randominconsistent", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var seed = clips.Get("targetpackageseed", int.Parse);
+                var rand = new CustomMersenneTwister(seed);
+
+                var data = targetPackageName.Substring("randominconsistent".Length).Split(';');
+                var size = int.Parse(data[0]);
+                var mag = double.Parse(data[1]);
+                var noiseType = ParseNoiseType(data[2]);
+                return new EpistaticTargetPackage("RandomInconsistent" + size + ";" + mag + ";" + noiseType.ToString(), new[] { StandardCorrelationMatrixTargets.RandomInconsistentConstraintTarget(rand, size, mag, noiseType, false) });
+            }
+            else if (targetPackageName.StartsWith("chiff", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int levels = int.Parse(targetPackageName.Substring("chiff".Length));
+                var downFactor = clips.Get("downFactor", double.Parse, 0.5);
+                bool chaff = clips.Get("chaff", bool.Parse, false);
+                return new EpistaticTargetPackage("ContinuousHiff" + levels, new[] { StandardCorrelationMatrixTargets.ContinuousHiff(levels, false, downFactor, chaff) });
+            }
+            else if (targetPackageName.StartsWith("flat", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int size = int.Parse(targetPackageName.Substring("flat".Length));
+                var agreement = clips.Get("agreement", double.Parse, 0.0);
+                return new EpistaticTargetPackage("Flat" + size, new[] { StandardCorrelationMatrixTargets.Flat(size, false, agreement) });
+            }
+            else if (targetPackageName.StartsWith("funkymca", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int moduleCount = int.Parse(targetPackageName.Substring("funkymca".Length));
+                var agreement = clips.Get("agreement", double.Parse);
+                return new EpistaticTargetPackage("FunkyMcA" + moduleCount, new[] { StandardCorrelationMatrixTargets.FunkyMcAscending(moduleCount, false, agreement) });
+            }
+            else if (targetPackageName.StartsWith("funkymc", StringComparison.InvariantCultureIgnoreCase))
+            {
+                var moduleString = targetPackageName.Substring("funkymc".Length);
+                var modules = Modular.MultiModulesStringParser.Instance.Parse(moduleString);
+                var agreement = clips.Get("agreement", double.Parse);
+                return new EpistaticTargetPackage("FunkyMc" + moduleString, new[] { StandardCorrelationMatrixTargets.FunkyMc(modules, false, agreement) });
+            }
+            else if (targetPackageName.StartsWith("sudoku", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int size = int.Parse(targetPackageName.Substring("sudoku".Length));
+                var diagonalCoef = clips.Get("diagonalCoef", double.Parse);
+                var constraintCoef = clips.Get("constraintCoef", double.Parse);
+                bool additiveConstraints = clips.Get("additiveconstraints", bool.Parse, true);
+                var flatCoef = clips.Get("flatCoef", double.Parse, 0.0);
+                var mat = Sudoku.GeneralSudoku(size, diagonalCoef, constraintCoef, additiveConstraints);
+                mat.Add(flatCoef, mat);
+
+                var partial = clips.Get("partialsolution", Sudoku.ParsePartialSolution, null);
+
+                if (partial == null)
+                {
+                    var target = new CorrelationMatrixTarget(mat, "Sudoku" + size, false);
+                    return new EpistaticTargetPackage("Sudoku" + size, new[] { target });
+                }
+                else
+                {
+                    var forcingMagnitude = clips.Get("forcingMagnitude", double.Parse, 1.0);
+                    var forcingVector = Sudoku.PrepareForcingVector(partial, false) * forcingMagnitude;
+                    var target = new CorrelationMatrixTarget(mat, "Sudoku" + size, false, forcingVector);
+                    return new EpistaticTargetPackage("Sudoku" + size, new[] { target });
+                }
+            }
+            else if (targetPackageName.StartsWith("nqueens", StringComparison.InvariantCultureIgnoreCase))
+            {
+                int size = int.Parse(targetPackageName.Substring("nqueens".Length));
+                var diagonalCoef = clips.Get("diagonalCoef", double.Parse);
+                var constraintCoef = clips.Get("constraintCoef", double.Parse);
+                bool proportionalConstraints = clips.Get("proportionalConstraints", bool.Parse, true);
+                var flatCoef = clips.Get("flatCoef", double.Parse, 0.0);
+                var mat = NQueens.GeneralNQueens(size, diagonalCoef, constraintCoef, proportionalConstraints);
+                mat.Add(flatCoef, mat);
+
+                var partial = clips.Get("partialsolution", NQueens.ParsePartialSolution, null);
+
+                if (partial == null)
+                {
+                    var target = new CorrelationMatrixTarget(mat, "NQueens" + size, false);
+                    return new EpistaticTargetPackage("NQueens" + size, new[] { target });
+                }
+                else
+                {
+                    var forcingMagnitude = clips.Get("forcingMagnitude", double.Parse, 1.0);
+                    var forcingVector = NQueens.PrepareForcingVector(partial, false) * forcingMagnitude;
+                    var target = new CorrelationMatrixTarget(mat, "NQueens" + size, false, forcingVector);
+                    return new EpistaticTargetPackage("NQueens" + size, new[] { target });
+                }
+            }
+
+            throw new Exception("Unrecognised TargetPackage name: " + targetPackageName + "\n" +
+                "Try one of:\n" +
+                " - cs{size}\n" +
+                " - ncs{size}\n" +
+                " - randominconsistent{size;mag;noisetype}\n" +
+                " - chiff\n" +
+                " - flat\n" +
+                " - funkymca\n" +
+                " - sudoku{size}\n" +
+                " - nqueens{size}\n");
+        }
+
+        public static IRegularisationFunction<IGenome> ParseRegFunction(string name)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("const", StringComparison.InvariantCultureIgnoreCase))
+                    return JudgementRules.ConstantEquivalent;
+
+                if (name.Equals("l1", StringComparison.InvariantCultureIgnoreCase))
+                    return JudgementRules.L1Equivalent;
+
+                if (name.Equals("rowl1", StringComparison.InvariantCultureIgnoreCase))
+                    return JudgementRules.RowL1Equivalent;
+
+                if (name.Equals("coll1", StringComparison.InvariantCultureIgnoreCase))
+                    return JudgementRules.ColL1Equivalent;
+
+                if (name.Equals("l2", StringComparison.InvariantCultureIgnoreCase))
+                    return JudgementRules.L2Equivalent;
+
+                if (name.StartsWith("l1And2AB", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var ab = name.Remove(0, "l1And2AB".Length).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(double.Parse).ToArray();
+                    return JudgementRules.L1And2(ab[0], ab[1]);
+                }
+
+                if (name.StartsWith("mmsoAB", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var ab = name.Remove(0, "mmsoAB".Length).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(double.Parse).ToArray();
+                    return JudgementRules.MMSO(ab[0], ab[1]);
+                }
+                
+                if (name.StartsWith("mmsolinABC", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var abc = name.Remove(0, "mmsolinABC".Length).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(double.Parse).ToArray();
+                    return JudgementRules.MMSOLin(abc[0], abc[1], abc[2]);
+                }
+
+                if (name.StartsWith("mmsolinAQ", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var aq = name.Remove(0, "mmsolinAQ".Length).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(double.Parse).ToArray();
+                    return JudgementRules.MMSOLin(aq[0], aq[1]);
+                }
+            }
+            
+            throw new ArgumentException("Unrecognised regularisation function: '" + name + "'. Consider one of these:\n" +
+                " - const\n" +
+                " - l1\n" +
+                " - rowl1\n" +
+                " - coll1\n" +
+                " - l2\n" +
+                " - MmsoAB{a;b}\n" +
+                " - MmsoLinAQ{a;q}\n" +
+                " - MmsoLinABC{a;b;c}");
+        }
+
+        public static ITargetPackage ParseSquareBinaryPuzzle(CliParams clips)
+        {
+            var targetStrings = clips.Get("targetstring").Split('|');
+            var targetVectors = targetStrings.Select(ts => Misc.ParseExtremesVector(ts, -1, +1, 0.0)).ToArray();
+            var rcc = clips.Get("rcc", double.Parse);
+            var rlcc = clips.Get("rlcc", double.Parse);
+            var d = (int)Math.Sqrt(targetVectors[0].Count);
+            var targets = targetVectors.Select(tv => new BinaryPuzzleTarget(tv.ToArray(), d, d, rcc, rlcc)).ToArray();
+            return new TargetPackage("SquareBinaryPuzzles", targets);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/GenomeParsing.cs b/M4MCode/M4M_MkI/M4M.New/GenomeParsing.cs
new file mode 100644
index 0000000..cf33c96
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/GenomeParsing.cs
@@ -0,0 +1,175 @@
+using M4M.Epistatics;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static M4M.Misc;
+
+namespace M4M
+{
+    public static class GenomeParsing
+    {
+        public static IInitialStateMutator GetInitialStateMutator(string name, int size)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.StartsWith("modules", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    int moduleCount = int.Parse(name.Remove(0, "modules".Length));
+                    return new ModuleInitialStateMutator(size, moduleCount);
+                }
+
+                if (name.StartsWith("onehotmodules", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var modules = Modular.MultiModulesStringParser.Instance.Parse(name.Remove(0, "onehotmodules".Length));
+                    return new InitialStateOneHotModulesMutator(modules, 1.0, 0.0);
+                }
+            }
+
+            throw new ArgumentException("Unrecognised initial state mutator: '" + name + "'. Consider one of these:\n" +
+                " - default\n" +
+                " - modules{moduleCount}");
+        }
+
+        public static ITransMatMutator GetTransMatMutator(string name, int size)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("null", StringComparison.InvariantCultureIgnoreCase))
+                    return NullTransMatMutator.Instance;
+
+                if (name.Equals("throw", StringComparison.InvariantCultureIgnoreCase))
+                    return ThrowTransMatMutator.Instance;
+
+                if (name.Equals("singlecell", StringComparison.InvariantCultureIgnoreCase))
+                    return new SingleCellTransMatMutator();
+
+                if (name.Equals("singlecellsymmetric", StringComparison.InvariantCultureIgnoreCase))
+                    return new SingleCellSymmetricTransMatMutator();
+
+                if (name.Equals("celltransfer", StringComparison.InvariantCultureIgnoreCase))
+                    return new CellTransferTransMatMutator();
+
+                if (name.StartsWith("singlebymoduledivideweights", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var modules = Modular.MultiModulesStringParser.Instance.Parse(name.Remove(0, "singlebymoduledivideweights".Length));
+                    return new SingleByModuleTransMatMutator(modules, true);
+                }
+
+                if (name.StartsWith("singlebymodule", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    var modules = Modular.MultiModulesStringParser.Instance.Parse(name.Remove(0, "singlebymodule".Length));
+                    return new SingleByModuleTransMatMutator(modules, false);
+                }
+
+                if (name.StartsWith("multicell", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    int updateCount = Misc.ParseInt(name.Remove(0, "multicell".Length));
+                    return new MutliCellTransMatMutator(updateCount);
+                }
+
+                if (name.StartsWith("columns", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    int moduleCount = int.Parse(name.Remove(0, "columns".Length));
+                    return new Columns2TransMatMutator(size, moduleCount);
+                }
+
+                if (name.StartsWith("singlecolumn", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    int moduleCount = int.Parse(name.Remove(0, "singlecolumn".Length));
+                    return new SingleColumnTransMatMutator(size, moduleCount);
+                }
+            }
+
+            throw new ArgumentException("Unrecognised transmat mutator: '" + name + "'. Consider one of these:\n" +
+                " - default\n" +
+                " - null\n" +
+                " - singlecell\n" +
+                " - multicell{count}\n" +
+                " - singlecolumn{moduleCount}\n" +
+                " - columns{moduleCount}");
+        }
+
+        public static IInitialStateCombiner GetInitialStateCombiner(string name, int size)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("uniformcrossover", StringComparison.InvariantCultureIgnoreCase))
+                    return UniformCrossOver.Instance;
+            }
+
+            throw new ArgumentException("Unrecognised initial state Combiner: '" + name + "'. Consider one of these:\n" +
+                " - default\n" +
+                "uniformcrossover");
+        }
+
+        public static ITransMatCombiner GetTransMatCombiner(string name, int size)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("uniformcrossover", StringComparison.InvariantCultureIgnoreCase))
+                    return UniformCrossOver.Instance;
+            }
+
+            throw new ArgumentException("Unrecognised transmat Combiner: '" + name + "'. Consider one of these:\n" +
+                " - default\n" +
+                "uniformcrossover");
+        }
+
+        public static MatrixEntryAddress[] GetTransMatOpenEntries(string name, int size)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("all", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("nodiagonal", StringComparison.InvariantCultureIgnoreCase))
+                    return Enumerable.Range(0, size).SelectMany(i => Enumerable.Range(0, size - 1).Select(j => new MatrixEntryAddress(i, j >= i ? j + 1 : j))).ToArray();
+
+                if (name.Equals("diagonal", StringComparison.InvariantCultureIgnoreCase))
+                    return Enumerable.Range(0, size).Select(i => new MatrixEntryAddress(i, i)).ToArray();
+            }
+
+            throw new ArgumentException("Unrecognised TransMatOpenEntries: '" + name + "'. Consider one of these:\n" +
+                " - all\n" +
+                " - nodiagonal\n}");
+        }
+
+        public static int[] GetInitialStateOpenEntries(string name, int size)
+        {
+            if (!string.IsNullOrEmpty(name))
+            {
+                if (name.Equals("default", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("all", StringComparison.InvariantCultureIgnoreCase))
+                    return null; // totally legit
+
+                if (name.Equals("none", StringComparison.InvariantCultureIgnoreCase))
+                    return new int[0]; // totally legit
+            }
+
+            throw new ArgumentException("Unrecognised GetInitialStateOpenEntries: '" + name + "'. Consider one of these:\n" +
+                " - all\n" +
+                " - none\n");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/M4M.New.csproj b/M4MCode/M4M_MkI/M4M.New/M4M.New.csproj
new file mode 100644
index 0000000..b07aacf
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/M4M.New.csproj
@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <Content Remove="Info/version.txt" /> 
+    <Content Remove="Info/buildinfo.txt" /> 
+  </ItemGroup>
+  <Target Name="UpdateVersion" BeforeTargets="BeforeBuild" Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <Exec Command="dotnet ../BuildTools/AutoVersion.dll info Info/buildinfo.txt Info/version.txt" ContinueOnError="WarnAndContinue" />
+  </Target>
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\M4M.Model\M4M.Model.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NetState">
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(M4M_AUTO_VERSION)' == 'true'">
+    <EmbeddedResource Include="Info/version.txt" />
+    <EmbeddedResource Include="Info/buildinfo.txt" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="OxyPlot.Core" Version="2.0.0" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" />
+    <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0007" />
+    <PackageReference Include="MathNet.Numerics" Version="4.9.1" />
+  </ItemGroup>
+
+</Project>
diff --git a/M4MCode/M4M_MkI/M4M.New/OxyEx/Cursors.cs b/M4MCode/M4M_MkI/M4M.New/OxyEx/Cursors.cs
new file mode 100644
index 0000000..c0178f7
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/OxyEx/Cursors.cs
@@ -0,0 +1,287 @@
+using OxyPlot;
+using OxyPlot.Annotations;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M.New.OxyEx
+{
+    public class CursorPair
+    {
+        public CursorPair(CursorAnnotation low, CursorAnnotation high)
+        {
+            Low = low ?? throw new ArgumentNullException(nameof(low));
+            High = high ?? throw new ArgumentNullException(nameof(high));
+        }
+
+        public CursorAnnotation Low { get; }
+        public CursorAnnotation High { get; }
+
+        public bool AnyOn => Low.IsOn || High.IsOn;
+
+        public void GetInfo(out double low, out double high, out double delta)
+        {
+            low = Low.Position;
+            high = High.Position;
+            delta = high - low;
+        }
+    }
+
+    public class CursorInfoAnnotation : Annotation
+    {
+        private List<CursorPair> Pairs { get; } = new List<CursorPair>();
+
+        public ScreenPoint Position { get; set; }
+        public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Left;
+        public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top;
+        public double Width { get; } = 200;
+
+        public void AddPair(CursorAnnotation low, CursorAnnotation high)
+        {
+            Pairs.Add(new CursorPair(low, high));
+        }
+
+        private int OnCount => Pairs.Count(p => p.AnyOn);
+
+        private OxyRect Bounds { get; set; }
+
+        public CursorInfoAnnotation()
+        {
+            this.MouseDown += CursorInfoAnnotation_MouseDown;
+            this.MouseUp += CursorInfoAnnotation_MouseUp;
+            this.MouseMove += CursorInfoAnnotation_MouseMove;
+        }
+
+        public override void Render(IRenderContext rc)
+        {
+            int on = OnCount;
+            if (on == 0)
+                return;
+
+            var subHeight = rc.MeasureText("0123456789").Height + 2;
+            var subWidth = Width / 3;
+
+            var height = subHeight * (on + 1);
+
+            var x = Position.X + (Sign(HorizontalAlignment) + 1) * Width / 2;
+            var y = Position.Y + (Sign(VerticalAlignment) + 1) * height / 2;
+            var topLeft = new ScreenPoint(x, y);
+            Bounds = new OxyRect(topLeft, new OxySize(Width, height));
+
+            rc.DrawRectangle(Bounds, OxyColor.FromAColor(128, OxyColors.LightSteelBlue), OxyColors.Black, 1);
+
+            rc.DrawText(new ScreenPoint(x + subWidth * 0.5, y + subHeight * 0.5), "Low", OxyColors.Black, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
+            rc.DrawText(new ScreenPoint(x + subWidth * 1.5, y + subHeight * 0.5), "High", OxyColors.Black, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
+            rc.DrawText(new ScreenPoint(x + subWidth * 2.5, y + subHeight * 0.5), "Delta", OxyColors.Black, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
+
+            foreach (var p in Pairs.Where(p => p.AnyOn))
+            {
+                y += subHeight;
+                p.GetInfo(out double low, out double high, out double delta);
+
+                if (!double.IsNaN(low))
+                    rc.DrawText(new ScreenPoint(x + subWidth * 0.5, y + subHeight * 0.5), low.ToString("G5"), p.Low.Color, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
+                if (!double.IsNaN(high))
+                    rc.DrawText(new ScreenPoint(x + subWidth * 1.5, y + subHeight * 0.5), high.ToString("G5"), p.High.Color, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
+                if (!double.IsNaN(delta))
+                    rc.DrawText(new ScreenPoint(x + subWidth * 2.5, y + subHeight * 0.5), delta.ToString("G5"), p.Low.Color, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
+            }
+        }
+
+        protected override HitTestResult HitTestOverride(HitTestArguments args)
+        {
+            if (Bounds.Contains(args.Point))
+            {
+                return new HitTestResult(this, args.Point);
+            }
+            return null;
+        }
+
+        private bool moused = false;
+        private ScreenVector mouseOffset;
+
+        private void CursorInfoAnnotation_MouseMove(object sender, OxyMouseEventArgs e)
+        {
+            if (moused)
+            {
+                Position = e.Position - mouseOffset;
+                PlotModel.InvalidatePlot(false);
+            }
+        }
+
+        private void CursorInfoAnnotation_MouseUp(object sender, OxyMouseEventArgs e)
+        {
+            moused = false;
+        }
+
+        private void CursorInfoAnnotation_MouseDown(object sender, OxyMouseDownEventArgs e)
+        {
+            if (e.ChangedButton == OxyMouseButton.Left)
+            {
+                moused = true;
+                mouseOffset = e.Position - Position;
+                e.Handled = true;
+            }
+        }
+
+        private int Sign(HorizontalAlignment horizontalAlignment)
+        {
+            switch (horizontalAlignment)
+            {
+                case HorizontalAlignment.Left:
+                    return -1;
+                case HorizontalAlignment.Center:
+                    return 0;
+                case HorizontalAlignment.Right:
+                    return +1;
+                default:
+                    throw new ArgumentException(nameof(horizontalAlignment));
+            }
+        }
+
+        private int Sign(VerticalAlignment verticalAlignment)
+        {
+            switch (verticalAlignment)
+            {
+                case VerticalAlignment.Top:
+                    return -1;
+                case VerticalAlignment.Middle:
+                    return 0;
+                case VerticalAlignment.Bottom:
+                    return +1;
+                default:
+                    throw new ArgumentException(nameof(verticalAlignment));
+            }
+        }
+    }
+
+    public class CursorAnnotation : Annotation
+    {
+        public double Position { get; set; } = double.NaN;
+        public OxyColor Color { get; set; } = OxyColors.LightGray;
+        public bool ReverseX { get; set; } = false;
+        public bool ReverseY { get; set; } = false;
+
+        public double BoxSize { get; set; } = 8;
+        public double LineThickness { get; set; } = 1;
+
+        public bool IsEnabled { get; set; } = true;
+
+        public CursorAnnotation(string xAxisKey, string yAxisKey)
+        {
+            XAxisKey = xAxisKey;
+            YAxisKey = yAxisKey;
+            Layer = AnnotationLayer.BelowAxes;
+
+            this.MouseDown += CursorAnnotation_MouseDown;
+            this.MouseUp += CursorAnnotation_MouseUp;
+            this.MouseMove += CursorAnnotation_MouseMove;
+        }
+
+        public bool IsOn => IsEnabled && XAxis.IsAxisVisible && !double.IsNaN(Position);
+
+        public void GetPoints(out ScreenPoint near, out ScreenPoint far, out ScreenPoint off)
+        {
+            var p = Position;
+            if (double.IsNaN(p))
+                p = ReverseX ? XAxis.ActualMaximum : XAxis.ActualMinimum;
+
+            if (this.YAxisKey == null)
+            {
+                var xp = XAxis.Transform(p);
+                near = new ScreenPoint(xp, XAxis.IsHorizontal() ? XAxis.PlotModel.PlotArea.Bottom : XAxis.PlotModel.PlotArea.Left);
+                far = new ScreenPoint(xp, XAxis.IsHorizontal() ? XAxis.PlotModel.PlotArea.Top : XAxis.PlotModel.PlotArea.Right);
+            }
+            else
+            {
+                near = XAxis.Transform(p, YAxis.ActualMinimum, YAxis);
+                far = XAxis.Transform(p, YAxis.ActualMaximum, YAxis);
+            }
+
+            if (ReverseY)
+            {
+                var t = near;
+                near = far;
+                far = t;
+            }
+
+            if (XAxis.IsVertical())
+            {
+                near = new ScreenPoint(near.Y, near.X);
+                far = new ScreenPoint(far.Y, far.X);
+                off = new ScreenPoint(near.X + (ReverseY ? BoxSize / 2 : -BoxSize / 2), far.Y);
+            }
+            else
+            {
+                off = new ScreenPoint(near.X, far.Y + (ReverseY ? BoxSize / 2 : -BoxSize / 2));
+            }
+        }
+
+        public double TranslateScreenPoint(ScreenPoint sp)
+        {
+            if (XAxis.IsVertical())
+                sp = new ScreenPoint(sp.Y, sp.X);
+
+            var newPosition = XAxis.InverseTransform(sp.X, sp.Y, YAxis).X;
+            if (XAxis.ActualMinimum > newPosition || XAxis.ActualMaximum < newPosition)
+                newPosition = double.NaN;
+            return newPosition;
+        }
+
+        public override void Render(IRenderContext rc)
+        {
+            if (!IsEnabled || !XAxis.IsAxisVisible || XAxis.ActualMinimum > Position || XAxis.ActualMaximum < Position)
+                return;
+
+            GetPoints(out ScreenPoint near, out ScreenPoint far, out ScreenPoint off);
+
+            rc.DrawLine(new[] { near, far }, Color, LineThickness);
+
+            var offRect = new OxyRect(off.X - BoxSize / 2, off.Y - BoxSize / 2, BoxSize, BoxSize);
+            rc.DrawRectangle(offRect, Color, OxyColors.Transparent, 0);
+        }
+
+        protected override HitTestResult HitTestOverride(HitTestArguments args)
+        {
+            if (!IsEnabled || !XAxis.IsAxisVisible || XAxis.ActualMinimum > Position || XAxis.ActualMaximum < Position)
+                return null;
+
+            GetPoints(out ScreenPoint near, out ScreenPoint far, out ScreenPoint off);
+
+            var lineHitRect = new OxyRect(near, far).Inflate(new OxyThickness(LineThickness + 1));
+            var offRect = new OxyRect(off.X - BoxSize / 2, off.Y - BoxSize / 2, BoxSize, BoxSize);
+
+            if (lineHitRect.Contains(args.Point) || offRect.Contains(args.Point))
+            {
+                return new HitTestResult(this, near);
+            }
+            return null;
+        }
+
+        private bool moused = false;
+
+        private void CursorAnnotation_MouseMove(object sender, OxyMouseEventArgs e)
+        {
+            if (moused)
+            {
+                Position = TranslateScreenPoint(e.Position);
+                PlotModel.InvalidatePlot(false);
+            }
+        }
+
+        private void CursorAnnotation_MouseUp(object sender, OxyMouseEventArgs e)
+        {
+            moused = false;
+        }
+
+        private void CursorAnnotation_MouseDown(object sender, OxyMouseDownEventArgs e)
+        {
+            if (e.ChangedButton == OxyMouseButton.Left)
+            {
+                moused = true;
+                e.Handled = true;
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/OxyEx/CustomLabelAxis.cs b/M4MCode/M4M_MkI/M4M.New/OxyEx/CustomLabelAxis.cs
new file mode 100644
index 0000000..f98db02
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/OxyEx/CustomLabelAxis.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M.OxyEx
+{
+    public class CustomAxisLabel
+    {
+        public CustomAxisLabel(double value, string text)
+        {
+            Value = value;
+            Text = text;
+        }
+
+        public double Value { get; }
+        public string Text { get; }
+    }
+
+    public class CustomLabelLinearAxis : OxyPlot.Axes.LinearAxis
+    {
+        public bool ForceMax { get; set; } = false;
+        public bool ForceMin { get; set; } = false;
+        public bool IncludeTick { get; set; } = true;
+        public bool Exclusive { get; set; } = false;
+
+        public List<CustomAxisLabel> CustomAxisLabels = new List<CustomAxisLabel>();
+
+        public CustomLabelLinearAxis()
+        {
+        }
+            
+        protected override string FormatValueOverride(double val)
+        {
+            double eps = Math.Abs(this.ActualMinimum - this.ActualMaximum) * 0.0001;
+            
+            string provisional = CustomAxisLabels.FirstOrDefault(cal => Math.Abs(val - cal.Value) < eps)?.Text;
+            string @default = base.FormatValueOverride(val);
+
+            if (provisional == null)
+                provisional = @default;
+            else if (provisional.Contains("$$$"))
+                provisional = provisional.Replace("$$$", @default);
+
+            return provisional;
+        }
+
+        public override void GetTickValues(out IList<double> majorLabelValues, out IList<double> majorTickValues, out IList<double> minorTickValues)
+        {
+            base.GetTickValues(out majorLabelValues, out majorTickValues, out minorTickValues);
+
+            double eps = Math.Abs(this.ActualMinimum - this.ActualMaximum) * 0.0001;
+            
+            List<CustomAxisLabel> minMax = new List<CustomAxisLabel>();
+            if (ForceMin)
+                minMax.Add(new CustomAxisLabel(this.ActualMinimum, null));
+            if (ForceMax)
+                minMax.Add(new CustomAxisLabel(this.ActualMaximum, null));
+
+            if (Exclusive)
+            {
+                majorLabelValues.Clear();
+                if (IncludeTick)
+                    majorTickValues.Clear();
+            }
+
+            foreach (var cal in CustomAxisLabels.Concat(minMax))
+            {
+                if (this.ActualMinimum <= cal.Value && cal.Value <= this.ActualMaximum
+                    && majorLabelValues.All(lv => Math.Abs(lv - cal.Value) >= eps))
+                {
+                    majorLabelValues.Add(cal.Value);
+
+                    if (IncludeTick)
+                        majorTickValues.Add(cal.Value);
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/Plotting.cs b/M4MCode/M4M_MkI/M4M.New/Plotting.cs
new file mode 100644
index 0000000..bcec752
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/Plotting.cs
@@ -0,0 +1,1303 @@
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using OxyPlot.Annotations;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class FigureLabelAnnotation : OxyPlot.Annotations.Annotation
+    {
+        public FigureLabelAnnotation(OxyPlot.PlotModel plot, string text, OxyColor color)
+        {
+            Plot = plot;
+            Text = text;
+            Color = color;
+            Layer = OxyPlot.Annotations.AnnotationLayer.AboveSeries;
+        }
+
+        private OxyPlot.PlotModel _plot;
+        public OxyPlot.PlotModel Plot
+        {
+            get => _plot;
+            set
+            {
+                if (_plot != null && _plot.Annotations.Contains(this))
+                    _plot.Annotations.Remove(this);
+                if (value.Annotations.Contains(this))
+                    value.Annotations.Remove(this);
+                _plot = value;
+                _plot.Annotations.Add(this);
+            }
+        }
+
+        public string Text { get; set; }
+        public OxyColor Color { get; set; }
+        
+        public override void Render(IRenderContext rc)
+        {
+            var fontSize = !double.IsNaN(this.FontSize) ? this.FontSize : this.PlotModel.TitleFontSize;
+            rc.DrawText(new ScreenPoint(0, 0), Text, Color, Plot.DefaultFont, fontSize, Plot.TitleFontWeight, 0, HorizontalAlignment.Left, VerticalAlignment.Top);
+        }
+    }
+    public class WaterMarkAnnotation : OxyPlot.Annotations.Annotation
+    {
+        public WaterMarkAnnotation(OxyPlot.PlotModel plot, string text, OxyColor color)
+        {
+            Plot = plot;
+            Text = text;
+            Color = color;
+            Layer = OxyPlot.Annotations.AnnotationLayer.BelowAxes;
+        }
+
+        private OxyPlot.PlotModel _plot;
+        public OxyPlot.PlotModel Plot
+        {
+            get => _plot;
+            set
+            {
+                if (_plot != null && _plot.Annotations.Contains(this))
+                    _plot.Annotations.Remove(this);
+                if (value.Annotations.Contains(this))
+                    value.Annotations.Remove(this);
+                _plot = value;
+                _plot.Annotations.Add(this);
+            }
+        }
+
+        public string Text { get; set; }
+        public OxyColor Color { get; set; }
+        
+        public override void Render(IRenderContext rc)
+        {
+            var fontSize = !double.IsNaN(this.FontSize) ? this.FontSize : this.PlotModel.TitleFontSize * 2;
+            rc.DrawText(this.PlotModel.PlotArea.Center, Text, Color, Plot.DefaultFont, fontSize, Plot.TitleFontWeight, 0, HorizontalAlignment.Center, VerticalAlignment.Middle);
+        }
+    }
+
+    public class SideAnnotation : Annotation
+    {
+        public SideAnnotation(AxisPosition position, string text)
+        {
+            if (!(position == AxisPosition.Left || position == AxisPosition.Right || position == AxisPosition.Top || position == AxisPosition.Bottom))
+                throw new ArgumentException("Must be l/r/t/b", nameof(position));
+
+            this.Position = position;
+            this.Text = text;
+            this.TextColor = OxyColors.Automatic;
+            this.Layer = AnnotationLayer.AboveSeries;
+        }
+
+        public AxisPosition Position { get; }
+        public string Text { get; }
+
+        public override void Render(IRenderContext rc)
+        {
+            if (string.IsNullOrEmpty(this.Text))
+                return;
+
+            var actualTextColor = this.ActualTextColor;
+            var actualFont = this.ActualFont;
+            var actualFontSize = this.ActualFontSize;
+            var actualFontWeight = this.ActualFontWeight;
+
+            var m = rc.MeasureText(Text, actualFont, actualFontSize, actualFontWeight);
+
+            ScreenPoint p;
+            double angle;
+
+            var s = 1.0;
+
+            switch (Position)
+            {
+                case AxisPosition.Left:
+                    angle = -90;
+                    p = new ScreenPoint(this.PlotModel.PlotArea.Left - m.Height * s, this.PlotModel.PlotArea.Center.Y);
+                    break;
+                case AxisPosition.Right:
+                    angle = 90;
+                    p = new ScreenPoint(this.PlotModel.PlotArea.Right + m.Height * s, this.PlotModel.PlotArea.Center.Y);
+                    break;
+                case AxisPosition.Top:
+                    angle = 0;
+                    p = new ScreenPoint(this.PlotModel.PlotArea.Center.X, this.PlotModel.PlotArea.Top - m.Height * s);
+                    break;
+                case AxisPosition.Bottom:
+                    angle = 0;
+                    p = new ScreenPoint(this.PlotModel.PlotArea.Center.X, this.PlotModel.PlotArea.Bottom + m.Height * s);
+                    break;
+                default:
+                    throw new InvalidOperationException();
+            }
+
+            rc.DrawText(p, this.Text, actualTextColor, actualFont, actualFontSize, actualFontWeight, angle, OxyPlot.HorizontalAlignment.Center, OxyPlot.VerticalAlignment.Middle);
+        }
+    }
+
+    public class ViewBackgroundAnnotation : OxyPlot.Annotations.Annotation
+    {
+        public ViewBackgroundAnnotation(OxyPlot.PlotModel plot, OxyColor color)
+        {
+            Plot = plot;
+            Color = color;
+            Layer = OxyPlot.Annotations.AnnotationLayer.BelowAxes;
+        }
+
+        private OxyPlot.PlotModel _plot;
+        public OxyPlot.PlotModel Plot
+        {
+            get => _plot;
+            set
+            {
+                if (value.Annotations.Contains(this))
+                    value.Annotations.Remove(this);
+                _plot = value;
+                _plot.Annotations.Add(this);
+            }
+        }
+
+        public OxyPlot.OxyColor Color { get; set; }
+
+        public override void Render(IRenderContext rc)
+        {
+            rc.DrawRectangle(new OxyRect(0, 0, Plot.Width, Plot.Height), Color, OxyColors.Transparent);
+        }
+    }
+
+    public class RepeatingLineAnnotation : OxyPlot.Annotations.Annotation
+    {
+        public RepeatingLineAnnotation(OxyPlot.PlotModel plot, OxyColor color, double interval)
+        {
+            Plot = plot;
+            Color = color;
+            Interval = interval;
+            Layer = OxyPlot.Annotations.AnnotationLayer.AboveSeries;
+        }
+
+        private OxyPlot.PlotModel _plot;
+        public OxyPlot.PlotModel Plot
+        {
+            get => _plot;
+            set
+            {
+                if (value.Annotations.Contains(this))
+                    value.Annotations.Remove(this);
+                _plot = value;
+                _plot.Annotations.Add(this);
+            }
+        }
+
+        public OxyPlot.OxyColor Color { get; set; }
+        public double Interval { get; }
+
+        public override void Render(IRenderContext rc)
+        {
+            var amin = XAxis.ActualMinimum;
+            var amax = XAxis.ActualMaximum;
+            var min = (int)(amin / Interval);
+            var max = (int)(amax / Interval);
+
+            if (min > max)
+            {
+                var t = min;
+                min = max;
+                max = t;
+            }
+
+            if (min >= 0)
+                min++;
+            if (max <= 0)
+                max--;
+
+            var y0 = YAxis.ActualMinimum;
+            var y1 = YAxis.ActualMaximum;
+
+            var pen = new OxyPen(Color);
+
+            for (int i = min; i <= max; i++)
+            {
+                var p0 = XAxis.Transform(i * Interval, y0, YAxis);
+                var p1 = XAxis.Transform(i * Interval, y1, YAxis);
+                rc.DrawLine(p0.X, p0.Y, p1.X, p1.Y, pen);
+            }
+        }
+    }
+
+    public class PlotExportReport
+    {
+        public PlotExportReport(string filename, double exportedWidth, double exportedHeight)
+        {
+            Filename = filename;
+            ExportedWidth = exportedWidth;
+            ExportedHeight = exportedHeight;
+        }
+
+        public string Filename { get; }
+        public double ExportedWidth { get; }
+        public double ExportedHeight { get; }
+
+        public string Summary => $"Output File: {Filename}, Width: {ExportedWidth}, Height: {ExportedHeight}";
+    }
+    
+    public delegate void PostProcessPlot(PlotModel model);
+    
+    public interface IPlotExporter
+    {
+        /// <summary>
+        /// Adds the extention to the filename
+        /// </summary>
+        PlotExportReport ExportPlot(string filename, PlotModel model, double size = 1.0, bool forceAspect = false, PostProcessPlot postProcessor = null);
+
+        /// <summary>
+        /// Adds the extention to the filename
+        /// </summary>
+        PlotExportReport ExportPlot(string filename, PlotModel model, double width, double height, bool forceAspect = false, PostProcessPlot postProcessor = null);
+    }
+
+    public class SimplePdfPlotExporter : IPlotExporter
+    {
+        public PlotExportReport ExportPlot(string filename, PlotModel model, double size, bool forceAspect, PostProcessPlot postProcessor = null)
+        {
+            double width = 297 / 25.4 * 72 * size; // A4
+            double height = 210 / 25.4 * 72 * size;
+            
+            return ExportToPdf(model, filename + ".pdf", width, height, true, forceAspect, postProcessor );
+        }
+
+        public PlotExportReport ExportPlot(string filename, PlotModel model, double width, double height, bool forceAspect, PostProcessPlot postProcessor = null)
+        {
+            return ExportToPdf(model, filename + ".pdf", width, height, true, forceAspect, postProcessor);
+        }
+
+        public static PlotExportReport ExportToPdf(OxyPlot.PlotModel model, string ofname, double width, double height, bool bigger, bool forceAspect, PostProcessPlot postProcessor)
+        {
+            double fc = 1.2;
+            double prl = 2.0;
+            double prc = 2.0;
+            if (model.Axes.Any(a => a.Position == AxisPosition.Right))
+                prc = 0.0;
+            if (model.Axes.Any(a => a.Position == AxisPosition.Left))
+                prl = 0.0;
+
+            model.Padding = new OxyPlot.OxyThickness(model.Padding.Right * prl, 0.0, model.Padding.Right * prc, 0.0);
+            model.TitleFontSize *= fc;
+            model.DefaultFontSize *= fc;
+
+            if (bigger)
+            {
+                foreach (var s in model.Series)
+                {
+                    if (s is OxyPlot.Series.LineSeries)
+                        ((OxyPlot.Series.LineSeries)s).StrokeThickness *= 2.0;
+                }
+                model.LegendFontSize *= fc;
+            }
+
+            OxyPlot.PdfExporter exporter = new PdfExporter();
+            exporter.Width = width;
+            exporter.Height = height;
+            
+            void doPlot()
+            {
+                model.ResetAllAxes();
+                model.InvalidatePlot(true);
+                
+                using (var fs = new System.IO.FileStream(ofname, System.IO.FileMode.Create))
+                {
+                    exporter.Export(model, fs);
+                }
+            }
+
+            void doForceAspect()
+            {
+                if (model.PlotArea.Width > model.PlotArea.Height)
+                {
+                    width = width - model.PlotArea.Width + model.PlotArea.Height;
+                }
+                else
+                {
+                    height = height + model.PlotArea.Width - model.PlotArea.Height;
+                }
+
+                exporter.Width = width;
+                exporter.Height = height;
+            }
+
+            doPlot();
+
+            if (forceAspect)
+            {
+                doForceAspect();
+                doPlot();
+            }
+
+            if (postProcessor != null)
+            {
+                postProcessor(model);
+                doPlot();
+                
+                if (forceAspect)
+                {
+                    doForceAspect();
+                    doPlot();
+                }
+            }
+            
+            return new PlotExportReport(ofname, width, height);
+        }
+    }
+
+    public enum TrajectoryPlotType
+    {
+        Line = 0,
+        Scatter = 1,
+        Rectangles = 2,
+    }
+
+    public class TrajectoryPlotting
+    {
+        public static PlotModel PrepareTrajectoriesPlot(string title, string xlabel, string ylabel, string colorlabel, double? ymin, double? ymax, string xAxisKey, string yAxisKey, string colorAxisKey, bool logX, bool logY, bool reverseY)
+        {
+            PlotModel model = new PlotModel() { Title = title };
+
+            if (xAxisKey != null)
+            {
+                Axis xaxis = logX
+                    ? (Axis)new LogarithmicAxis() { Key = xAxisKey, Title = xlabel, Position = AxisPosition.Bottom }
+                    : (Axis)new LinearAxis() { Key = xAxisKey, Title = xlabel, Position = AxisPosition.Bottom };
+
+                model.Axes.Add(xaxis);
+            }
+
+            if (yAxisKey != null)
+            {
+                Axis yaxis = logY
+                    ? (Axis)new LogarithmicAxis() { Key = yAxisKey, Title = ylabel, Position = AxisPosition.Left }
+                    : (Axis)new LinearAxis() { Key = yAxisKey, Title = ylabel, Position = AxisPosition.Left };
+
+                if (reverseY)
+                {
+                    yaxis.StartPosition = 1;
+                    yaxis.EndPosition = 0;
+                }
+            
+                if (ymin != null)
+                    yaxis.Minimum = ymin.Value;
+                if (ymax != null)
+                    yaxis.Maximum = ymax.Value;
+            
+                model.Axes.Add(yaxis);
+            }    
+
+            if (colorAxisKey != null)
+            {
+                LinearColorAxis coloraxis = new LinearColorAxis() { Key = colorAxisKey, Title = colorlabel, Position = AxisPosition.Right };
+                coloraxis.Palette = OxyPalettes.Gray(100);
+                model.Axes.Add(coloraxis);
+            }
+
+            return model;
+        }
+
+        public static void AddY2Axis(PlotModel model, string y2label, string y2AxisKey, double? y2min, double? y2max)
+        {
+            Axis y2axis = new LinearAxis() { Key = y2AxisKey, Title = y2label, Position = AxisPosition.Right };
+            
+            if (y2min != null)
+                y2axis.Minimum = y2min.Value;
+            if (y2max != null)
+                y2axis.Maximum = y2max.Value;
+            
+            model.Axes.Add(y2axis);
+        }
+
+        /// <summary>
+        /// This very intentionally igores null entries, so that the colours are as they would be if they were not null
+        /// </summary>
+        public static PlotModel PlotTrajectories(string title, string xlabel, string ylabel, string colorlabel, string seriesName, double[][] trajectories, int samplePeriod, double x0, double? ymin, double? ymax, OxyColor? c0, OxyColor? c1, double strokeThickness, int resolution, TrajectoryPlotType plotType, string xAxisKey, string yAxisKey, string colorAxisKey, bool logX, bool logY, bool reverseY)
+        {
+            PlotModel model = PrepareTrajectoriesPlot(title, xlabel, ylabel, colorlabel, ymin, ymax, xAxisKey, yAxisKey, colorAxisKey, logX, logY, reverseY);
+
+            PlotTrajectories(model, seriesName, trajectories, samplePeriod, x0, c0, c1, strokeThickness, resolution, plotType, xAxisKey, yAxisKey);
+
+            return model;
+        }
+
+        /// <summary>
+        /// This very intentionally igores null entries, so that the colours are as they would be if they were not null
+        /// </summary>
+        public static PlotModel PlotTrajectories(string title, string xlabel, string ylabel, string colorlabel, string seriesName, double[][] trajectories, int samplePeriod, double x0, double? ymin, double? ymax, object[] tags, OxyColor[] colors, double strokeThickness, int resolution, TrajectoryPlotType plotType, string xAxisKey, string yAxisKey, string colorAxisKey, bool logX, bool logY, bool reverseY)
+        {
+            PlotModel model = PrepareTrajectoriesPlot(title, xlabel, ylabel, colorlabel, ymin, ymax, xAxisKey, yAxisKey, colorAxisKey, logX, logY, reverseY);
+
+            PlotTrajectories(model, seriesName, trajectories, samplePeriod, x0, tags, colors, strokeThickness, resolution, plotType, xAxisKey, yAxisKey, colorAxisKey);
+
+            return model;
+        }
+        
+        /// <summary>
+        /// This very intentionally igores null entries, so that the colours are as they would be if they were not null
+        /// </summary>
+        public static void PlotTrajectories(PlotModel model, string seriesName, double[][] trajectories, int samplePeriod, double x0, OxyColor? c0, OxyColor? c1, double strokeThickness, int resolution, TrajectoryPlotType plotType, string xAxisKey, string yAxisKey)
+        {
+            int n = trajectories.Length;
+            var tags = Tags1D(n, i => i);
+            var colors = Colours1D(trajectories.Length, c0, c1);
+            PlotTrajectories(model, seriesName, trajectories, samplePeriod, x0, tags, colors, strokeThickness, resolution, plotType, xAxisKey, yAxisKey, null);
+        }
+        
+        
+        /// <summary>
+        /// This very intentionally igores null entries, so that the colours are as they would be if they were not null
+        /// </summary>
+        public static void PlotTrajectories(PlotModel model, string seriesName, double[][] trajectories, int samplePeriod, double x0, object[] tags, OxyColor[] colors, double strokeThickness, int resolution, TrajectoryPlotType plotType, string xAxisKey, string yAxisKey, string colorAxisKey)
+        {    
+            int N = trajectories.Length;
+
+            if (plotType == TrajectoryPlotType.Rectangles)
+            {
+                var rs = new OxyPlot.Series.RectangleSeries();
+                rs.XAxisKey = xAxisKey;
+                rs.YAxisKey = yAxisKey;
+                rs.ColorAxisKey = colorAxisKey;
+                rs.Title = seriesName;
+
+                for (int i = 0; i < N; i++)
+                {
+                    double x = x0 - samplePeriod * resolution;
+                    var samples = trajectories[i];
+                    var lastX = x0;
+                    var last = double.NaN;
+                    foreach (var sample in samples)
+                    {
+                        if (!double.IsNaN(last) && !double.IsNaN(sample))
+                        {
+                            x += samplePeriod * resolution;
+                            var rect = new RectangleItem(lastX, x, i, i+1, sample);
+                            rs.Items.Add(rect);
+                        }
+
+                        last = sample;
+                        lastX = x;
+                    }
+                }
+
+                rs.RenderInLegend = false;
+
+                model.Series.Add(rs);
+            }
+            else
+            {
+                bool first = true;
+                int li = 0;
+                foreach (var line in trajectories)
+                {
+                    if (line == null)
+                        goto skip;
+
+                    OxyColor color = colors != null ? colors[li] : OxyColors.Automatic;
+
+                    Series s;
+                    Action<DataPoint> addDataPoint;
+
+                    switch (plotType)
+                    {
+                        case TrajectoryPlotType.Line:
+                            LineSeries ls = new LineSeries() { LineStyle = OxyPlot.LineStyle.Solid, MarkerType = OxyPlot.MarkerType.None, StrokeThickness = strokeThickness, Color = color, Tag = tags[li] };
+                            addDataPoint = dp => ls.Points.Add(dp);
+                            ls.XAxisKey = xAxisKey;
+                            ls.YAxisKey = yAxisKey;
+                            s = ls;
+                            break;
+                        case TrajectoryPlotType.Scatter:
+                            ScatterSeries ss = new ScatterSeries() { MarkerType = OxyPlot.MarkerType.Diamond, MarkerFill = color, MarkerSize = strokeThickness, Tag = tags[li] };
+                            addDataPoint = dp => ss.Points.Add(new ScatterPoint(dp.X, dp.Y));
+                            ss.XAxisKey = xAxisKey;
+                            ss.YAxisKey = yAxisKey;
+                            s = ss;
+                            break;
+                        default:
+                            throw new Exception("Unsupported TrajectoryPlottingType: " + plotType);
+                    }
+
+                    if (first)
+                    {
+                        s.Title = seriesName;
+                        s.TrackerFormatString += $"\n(li)";
+                        first = false;
+                    }
+
+                    double x = x0;
+
+                    for (int i = 0; i < line.Length; i += resolution)
+                    {
+                        addDataPoint(new DataPoint(x, line[i]));
+                        x += samplePeriod * resolution;
+                    }
+
+                    model.Series.Add(s);
+
+                skip:
+                    li++;
+                }
+            }
+        }
+        
+        public static OxyColor[] Colours1D(int n, OxyColor? c0, OxyColor? c1)
+        {
+            OxyColor[] res = new OxyColor[n];
+            
+            if (c0 == null || c1 == null)
+            {
+                if (n == 1 && c0 != null)
+                {
+                    res[0] = c0.Value;
+                }
+                else
+                {
+                    OxyColor c = c0 == null ? (c1 == null ? OxyColors.Automatic : c1.Value) : c0.Value;
+                    for (int i = 0; i < n; i++)
+                    {
+                        res[i] = c;
+                    }
+                }
+            }
+            else if (n == 1)
+            {
+                res[0] = c0.Value;
+            }
+            else
+            {
+                for (int i = 0; i < n; i++)
+                {
+                    var z = (double)i / (n - 1);
+                    res[i] = OxyColor.Interpolate(c0.Value, c1.Value, z);
+                }
+            }
+
+            return res;
+        }
+
+        public static OxyColor[] Colours2D(int n, OxyColor c00, OxyColor c10, OxyColor c01, OxyColor c11)
+        {
+            OxyColor[] res = new OxyColor[n * n];
+
+            for (int i = 0; i < n; i++)
+            {
+                for (int j = 0; j < n; j++)
+                {
+                    var iz = (double)i / Math.Max(1, (n - 1));
+                    var jz = (double)j / Math.Max(1, (n - 1));
+                    res[i * n + j] = OxyColor.Interpolate(OxyColor.Interpolate(c00, c10, iz), OxyColor.Interpolate(c01, c11, iz), jz);
+                }
+            }
+
+            return res;
+        }
+
+        public static object[] Tags1D(int n, Func<int, object> tagFunc1d)
+        {
+            object[] res = new object[n];
+
+            for (int i = 0; i < n; i++)
+            {
+                res[i] = tagFunc1d(i);
+            }
+
+            return res;
+        }
+
+        public static object[] Tags2D(int n, Func<int, int, object> tagFunc2d)
+        {
+            object[] res = new object[n * n];
+
+            for (int i = 0; i < n; i++)
+            {
+                for (int j = 0; j < n; j++)
+                {
+                    res[i * n + j] = tagFunc2d(i, j);
+                }
+            }
+
+            return res;
+        }
+    }
+
+    public struct MaxMin
+    {
+        public readonly double Max;
+        public readonly double Min;
+
+        public MaxMin(double max, double min)
+        {
+            Max = max;
+            Min = min;
+        }
+    }
+
+    public class TracePlotting
+    {
+        public static PlotModel PreparePlot(string title, bool showLegend, bool epochsx, string xAxisKey, bool logX)
+        {
+            PlotModel model = new PlotModel() { Title = title };
+            
+            Axis xaxis;
+            if (logX)
+            {
+                xaxis = new LogarithmicAxis() { Key = xAxisKey, Title = epochsx ? "Epochs" : "Generations", Position = AxisPosition.Bottom };
+            }
+            else
+            {
+                xaxis = new LinearAxis() { Key = xAxisKey, Title = epochsx ? "Epochs" : "Generations", Position = AxisPosition.Bottom };
+            }
+            model.Axes.Add(xaxis);
+
+            model.IsLegendVisible = showLegend;
+            model.LegendPosition = LegendPosition.RightTop;
+            model.LegendPlacement = LegendPlacement.Outside;
+
+            return model;
+        }
+
+        public static IEnumerable<WholeSample<T>> EnumerateSamples<T>(int offset, TraceInfo<T> traceInfo, int resolution) where T : IIndividual<T>
+        {
+            for (int i = offset; i < offset + resolution && i < traceInfo.TraceWholeSamples.Count; i++)
+            {
+                yield return traceInfo.TraceWholeSamples[i];
+            }
+        }
+
+        public static Func<IEnumerable<T>, double> AverageMetric<T>(Func<T, double> sampler)
+        {
+            return s => s.Select(sampler).Average();
+        }
+
+        public static Func<IEnumerable<T>, double> DiffMetric<T>(Func<IEnumerable<T>, double> metric, double d0)
+        {
+            return s => metric(s) - d0;
+        }
+
+        public static Func<IEnumerable<WholeSample<T>>, double> AverageMetric<T>(TraceInfo<T> traceInfo, bool diff, Func<WholeSample<T>, double> sampler) where T : IIndividual<T>
+        {
+            var sm = AverageMetric(sampler);
+            if (diff)
+                sm = DiffMetric(sm, sampler(traceInfo.TraceWholeSamples.First()));
+            
+            return sm;
+        }
+
+        public static IEnumerable<DataPoint> Sample<T>(TraceInfo<T> traceInfo, int resolution, Func<IEnumerable<WholeSample<T>>, double> samplerMetric, bool epochsx, double offset) where T : IIndividual<T>
+        {
+            if (epochsx)
+            {
+                // exclude the last set if it is deficient
+                for (int i = 0; i + resolution - 1 < traceInfo.TraceWholeSamples.Count; i += resolution)
+                {
+                    var x = traceInfo.TraceWholeSamples[i].Epoch + offset;
+                    var y = samplerMetric(EnumerateSamples(i, traceInfo, resolution));
+                    yield return new DataPoint(x, y);
+                }
+            }
+            else
+            {
+                // exclude the last set if it is deficient
+                for (int i = 0; i + resolution - 1 < traceInfo.TraceWholeSamples.Count; i += resolution)
+                {
+                    var x = traceInfo.TraceWholeSamples[i].Generations + offset;
+                    var y = samplerMetric(EnumerateSamples(i, traceInfo, resolution));
+                    yield return new DataPoint(x, y);
+                }
+            }
+        }
+
+        public static IEnumerable<DataPoint> InjectBreaks(IEnumerable<DataPoint> points)
+        {
+            var lastX = double.NaN;
+
+            foreach (var dp in points)
+            {
+                if (dp.X == lastX)
+                {
+                    yield return DataPoint.Undefined;
+                }
+
+                lastX = dp.X;
+                yield return dp;
+            }
+        }
+
+        public static IEnumerable<DataPoint> MaybeInjectBreaks(IEnumerable<DataPoint> points, bool injectBreaks)
+        {
+            if (injectBreaks)
+                return InjectBreaks(points);
+            else
+                return points;
+        }
+
+        public static void PlotTraceRegCoegs(PlotModel model, TraceInfo<DenseIndividual> traceInfo, int resolution, double? regcoefsmin, double? regcoefsmax, bool onLeft, bool epochsx, bool diff, double offset)
+        {
+            Axis regcoefsaxis = new LinearAxis() { Key = "regcoefs", Title = (diff ? "Change in " : "") + "Regulation Coefficient", Position = onLeft ? AxisPosition.Left : AxisPosition.Right };
+            model.Axes.Add(regcoefsaxis);
+
+            if (regcoefsmin != null)
+                regcoefsaxis.Minimum = regcoefsmin.Value;
+            if (regcoefsmax != null)
+                regcoefsaxis.Maximum = regcoefsmax.Value;
+
+            var exampleIndividual = traceInfo.TraceWholeSamples[0].Judgements[0].Individual;
+            
+            var c00 = OxyColors.DarkCyan;
+            var c10 = OxyColors.LightCyan;
+            var c01 = OxyColors.DarkGreen;
+            var c11 = OxyColors.LightGreen;
+
+            int N = exampleIndividual.Phenotype.Size;
+
+            bool first = true;
+            int li = 0;
+            for (int i = 0; i < N; i++)
+            {
+                for (int j = 0; j < N; j++)
+                {
+                    var iz = (double)i / (N - 1);
+                    var jz = (double)j / (N - 1);
+                    var c = OxyColor.Interpolate(OxyColor.Interpolate(c00, c10, iz), OxyColor.Interpolate(c01, c11, iz), jz);
+                    
+                    LineSeries traitLine = new LineSeries() { Title = "Regulation Coefficients", RenderInLegend = first, Color = c, YAxisKey = regcoefsaxis.Key, Tag = li };
+                    li++;
+
+                    Func<WholeSample<DenseIndividual>, double> sampler = ws => ws.Judgements.Average(ij => ij.Individual.Genome.TransMat[i, j]);
+                    double w0 = sampler(traceInfo.TraceWholeSamples[0]);
+
+                    if (diff)
+                        traitLine.Points.AddRange(Sample(traceInfo, resolution, wss => wss.Average(sampler) - w0, epochsx, offset));
+                    else
+                        traitLine.Points.AddRange(Sample(traceInfo, resolution, wss => wss.Average(sampler), epochsx, offset));
+
+                    model.Series.Add(traitLine);
+
+                    first = false;
+                }
+            }
+        }
+
+        public static Series PlotTraceLine<T>(PlotModel model, TraceInfo<T> traceInfo, string xAxisKey, string yAxisKey, string label, int resolution, bool epochsx, Func<IEnumerable<WholeSample<T>>, double> samplerMetric, OxyColor colour, double strokeThickness, double offset, TrajectoryPlotType plotType, MarkerType markerType, OxyColor markerColour, LineStyle lineStyle, bool injectBreaks, LineStyle brokenLineStyle) where T : IIndividual<T>
+        {
+            switch (plotType)
+            {
+                case TrajectoryPlotType.Line:
+                    LineSeries ls = new LineSeries() { Title = label, Color = colour, XAxisKey = xAxisKey, YAxisKey = yAxisKey, StrokeThickness = strokeThickness, MarkerSize = strokeThickness, BrokenLineStyle = brokenLineStyle, BrokenLineColor = colour, BrokenLineThickness = strokeThickness };
+                    ls.Points.AddRange(MaybeInjectBreaks(Sample(traceInfo, resolution, samplerMetric, epochsx, offset), injectBreaks));
+                    GeneralPlotting.ApplyMarkers(ls, markerType, markerColour);
+                    model.Series.Add(ls);
+                    ls.LineStyle = lineStyle;
+                    return ls;
+                case TrajectoryPlotType.Scatter:
+                    ScatterSeries ss = new ScatterSeries() { Title = label, MarkerFill = colour, XAxisKey = xAxisKey, YAxisKey = yAxisKey, MarkerSize = strokeThickness, MarkerStrokeThickness = 1, MarkerType = OxyPlot.MarkerType.Diamond };
+                    ss.Points.AddRange(Sample(traceInfo, resolution, samplerMetric, epochsx, offset).Select(dp => new ScatterPoint(dp.X, dp.Y)));
+                    GeneralPlotting.ApplyMarkers(ss, markerType, markerColour);
+                    model.Series.Add(ss);
+                    return ss;
+                default:
+                throw new Exception("Unsupported plot type: " + plotType);
+            }
+        }
+
+        public static void PlotTraceLines1D<T>(PlotModel model, TraceInfo<T> traceInfo, string axiskey, string title, string legendTitle, int resolution, double? min, double? max, bool onLeft, bool epochsx, IReadOnlyList<Func<IEnumerable<WholeSample<T>>, double>> samplerMetrics, OxyColor c0, OxyColor c1, double strokeThickness, double offset, TrajectoryPlotType plotType, MarkerType markerType, LineStyle lineStyle, bool injectBreaks, LineStyle brokenLineStyle) where T : IIndividual<T>
+        {
+            Axis axis = new LinearAxis() { Key = axiskey, Title = title, Position = onLeft ? AxisPosition.Left : AxisPosition.Right };
+            model.Axes.Add(axis);
+
+            if (min != null)
+                axis.Minimum = min.Value;
+            if (max != null)
+                axis.Maximum = max.Value;
+            
+            bool first = true;
+            for (int i = 0; i < samplerMetrics.Count; i++)
+            {
+                var z = samplerMetrics.Count == 1 ? 0.0 : (double)i / (samplerMetrics.Count-1);
+                var c = OxyColor.Interpolate(c0, c1, z);
+
+                var s = PlotTraceLine(model, traceInfo, "generations", axis.Key, legendTitle, resolution, epochsx, samplerMetrics[i], c, strokeThickness, offset, plotType, markerType, c, lineStyle, injectBreaks, brokenLineStyle);
+                s.RenderInLegend = first;
+                //model.Series.Add(s);
+
+                first = false;
+            }
+        }
+
+        public static void PlotTraceLinesAverage1D<T>(PlotModel model, TraceInfo<T> traceInfo, string axiskey, string title, string legendTitle, int resolution, double? min, double? max, bool onLeft, bool epochsx, bool diff, IReadOnlyList<Func<WholeSample<T>, double>> samplers, OxyColor c0, OxyColor c1, double strokeThickness, double offset, TrajectoryPlotType plotType, MarkerType markerType, LineStyle lineStyle, bool injectBreaks, LineStyle brokenLineStyle) where T : IIndividual<T>
+        {
+            var samplerMetrics = samplers.Select(s => AverageMetric(traceInfo, diff, s)).ToArray();
+            PlotTraceLines1D(model, traceInfo, axiskey, (diff ? "Change in " : "") + title, legendTitle, resolution, min, max, onLeft, epochsx, samplerMetrics, c0, c1, strokeThickness, offset, plotType, markerType, lineStyle, injectBreaks, brokenLineStyle);
+        }
+
+        public static void PlotPhenotypicTraceTraits<T>(PlotModel model, TraceInfo<T> traceInfo, int resolution, double? traitmin, double? traitmax, bool onLeft, bool epochsx, bool diff, double offset, TrajectoryPlotType plotType, MarkerType markerType, bool injectBreaks, LineStyle brokenLineStyle) where T : IIndividual<T>
+        {
+            var exampleIndividual = traceInfo.TraceWholeSamples[0].Judgements[0].Individual;
+
+            var c0 = OxyColors.DarkBlue;
+            var c1 = OxyColors.LightBlue;
+
+            int N = exampleIndividual.Phenotype.Size;
+            
+            Func<WholeSample<T>, double>[] samplers =
+                Enumerable.Range(0, N).Select<int, Func<WholeSample<T>, double>>(i => ws => ws.Judgements.Average(j => j.Individual.Phenotype[i])).ToArray();
+
+            PlotTraceLinesAverage1D(model, traceInfo, "ptrait", "Phenotypic Trait Expression", "Phenotypic Trait Expression", resolution, traitmin, traitmax, onLeft, epochsx, diff, samplers, c0, c1, 2.0, offset, plotType, markerType, LineStyle.Automatic, injectBreaks, brokenLineStyle);
+        }
+
+        public static void PlotGenotypicTraceTraits(PlotModel model, TraceInfo<DenseIndividual> traceInfo, int resolution, double? traitmin, double? traitmax, bool onLeft, bool epochsx, bool diff, double offset, TrajectoryPlotType plotType, MarkerType markerType, bool injectBreaks, LineStyle brokenLineStyle)
+        {
+            var exampleIndividual = traceInfo.TraceWholeSamples[0].Judgements[0].Individual;
+
+            var c0 = OxyColors.DarkViolet;
+            var c1 = OxyColors.Violet;
+
+            int N = exampleIndividual.Genome.Size;
+
+            var g0 = Enumerable.Range(0, N).Select(i => traceInfo.TraceWholeSamples[0].Judgements.Average(j => j.Individual.Genome.InitialState[i])).ToArray();
+
+            Func<WholeSample<DenseIndividual>, double>[] samplers =
+                Enumerable.Range(0, N).Select<int, Func<WholeSample<DenseIndividual>, double>>(i => ws => ws.Judgements.Average(j => j.Individual.Genome.InitialState[i])).ToArray();
+
+            PlotTraceLinesAverage1D(model, traceInfo, "gtrait", (diff ? "Change in " : "") + "Genotypic Trait Expression", "Genotypic Trait Expression", resolution, traitmin, traitmax, onLeft, epochsx, diff, samplers, c0, c1, 2.0, offset, plotType, markerType, LineStyle.Automatic, injectBreaks, brokenLineStyle);
+        }
+
+        public static void PlotTraceHuskyness(PlotModel model, TraceInfo<DenseIndividual> traceInfo, int[][] moduleTraits, int resolution, double? huskynessmin, double? huskynessmax, bool onLeft, bool epochsx, bool diff, double offset, LineStyle lineStyle, TrajectoryPlotType plotType, MarkerType markerType, bool injectBreaks, LineStyle brokenLineStyle)
+        {
+            var c0 = OxyColors.DarkSlateGray;
+            var c1 = OxyColors.SlateGray;
+
+            Func<WholeSample<DenseIndividual>, double>[] samplers =
+                moduleTraits.Select<int[], Func<WholeSample<DenseIndividual>, double>>(mts => ws => ws.Judgements.Average(j => Analysis.ComputeModuleHuskyness(j.Individual.Genome.TransMat, mts))).ToArray();
+
+            PlotTraceLinesAverage1D(model, traceInfo, "huskyness", (diff ? "Change in " : "") + "Degree of Hierarchy", "Degree of Hierarchy", resolution, huskynessmin, huskynessmax, onLeft, epochsx, diff, samplers, c0, c1, 2.0, offset, plotType, markerType, LineStyle.Automatic, injectBreaks, brokenLineStyle);
+        }
+
+        public static void PlotTraceDevTimeDominance(PlotModel model, MathNet.Numerics.Random.RandomSource rand, DevelopmentRules drules, TraceInfo<DenseIndividual> traceInfo, int[][] moduleTraits, int resolution, double? dtdmin, double? dtdmax, bool onLeft, bool epochsx, bool diff, double offset, LineStyle lineStyle, TrajectoryPlotType plotType, MarkerType markerType, bool injectBreaks, LineStyle brokenLineStyle)
+        {
+            var c0 = OxyColors.DarkGoldenrod;
+            var c1 = OxyColors.DarkGoldenrod;
+
+            Func<WholeSample<DenseIndividual>, double>[] samplers = new[] {
+                new Func<WholeSample<DenseIndividual>, double>(ws => ws.Judgements.Average(j => Analysis.MeasureDevTimeDominance(rand, drules, j.Individual.Genome, moduleTraits)))
+            };
+
+            PlotTraceLinesAverage1D(model, traceInfo, "dtd", (diff ? "Change in " : "") + "Dev-Time Dominance", "Dev-Time Dominance", resolution, dtdmin, dtdmax, onLeft, epochsx, diff, samplers, c0, c1, 2.0, offset, plotType, markerType, LineStyle.Automatic, injectBreaks, brokenLineStyle);
+        }
+
+        public static void PlotTraceFitness<T>(PlotModel model, TraceInfo<T> traceInfo, int resolution, double? fitnessmin, double? fitnessmax, bool onLeft, bool epochsx, bool diff, double offset, LineStyle lineStyle, TrajectoryPlotType plotType, MarkerType markerType) where T : IIndividual<T>
+        {
+            Axis fitnessaxis = new LinearAxis() { Key = "fitness", Title = (diff ? "Change in " : "") + "Fitness", Position = onLeft ? AxisPosition.Left : AxisPosition.Right };
+            model.Axes.Add(fitnessaxis);
+
+            if (fitnessmin != null)
+                fitnessaxis.Minimum = fitnessmin.Value;
+            if (fitnessmax != null)
+                fitnessaxis.Maximum = fitnessmax.Value;
+            
+            double f0 = traceInfo.TraceWholeSamples[0].Judgements.Average(j => j.Judgement.CombinedFitness);
+            
+            // TODO: would be nice to be able to use a Scatter here, so we should abstract this out so that all the PlotTraceeWhatever methods can use it
+            Func<IEnumerable<WholeSample<T>>, double> sampler = diff
+                ? new Func<IEnumerable<WholeSample<T>>, double>(wss => wss.Average(ws => ws.Judgements.Average(j => j.Judgement.CombinedFitness)) - f0)
+                : new Func<IEnumerable<WholeSample<T>>, double>(wss => wss.Average(ws => ws.Judgements.Average(j => j.Judgement.CombinedFitness)));
+            var samples = Sample(traceInfo, resolution, sampler, epochsx, offset);
+
+
+            switch (plotType)
+            {
+                case TrajectoryPlotType.Line:
+                    LineSeries ls = new LineSeries() { Title = "Fitness", RenderInLegend = true, Color = OxyColors.Red, YAxisKey = fitnessaxis.Key, StrokeThickness = 2, LineStyle = lineStyle };
+                    ls.Points.AddRange(samples);
+                    GeneralPlotting.ApplyMarkers(ls, markerType, OxyColors.Red);
+                    model.Series.Add(ls);
+                    break;
+                case TrajectoryPlotType.Scatter:
+                    ScatterSeries ss = new ScatterSeries() { Title = "Fitness", RenderInLegend = true, YAxisKey = fitnessaxis.Key, MarkerSize = 2 };
+                    ss.Points.AddRange(samples.Select(dp => new ScatterPoint(dp.X, dp.Y)));
+                    GeneralPlotting.ApplyMarkers(ss, markerType, OxyColors.Red);
+                    model.Series.Add(ss);
+                    break;
+                default:
+                throw new Exception("Unsupported plot type: " + plotType);
+            }
+        }
+
+        public static void AnnotateTraceTargetTransitions<T>(PlotModel model, TraceInfo<T> traceInfo, bool targetLabels, double offset) where T : IIndividual<T>
+        {
+            ITarget lastTarget = null;
+
+            foreach (var ws in traceInfo.TraceWholeSamples)
+            {
+                ITarget nextTarget = ws.Target;
+
+                if (nextTarget != lastTarget)
+                {
+                    var la = new LineAnnotation() { Type = LineAnnotationType.Vertical, X = ws.Generations + offset, Color = OxyColors.Gray };
+                    
+                    if (targetLabels)
+                    {
+                        la.TextOrientation = AnnotationTextOrientation.AlongLine;
+                        la.Text = nextTarget.FriendlyName;
+                    }
+
+                    model.Annotations.Add(la);
+                    lastTarget = nextTarget;
+
+                }
+            }
+        }
+
+        public static void AnnotateTraceEpochsTransitions<T>(PlotModel model, TraceInfo<T> traceInfo, bool epochLabels, double offset) where T : IIndividual<T>
+        {
+            int lastEpoch = -1;
+
+            foreach (var ws in traceInfo.TraceWholeSamples)
+            {
+                int nextEpoch = ws.Epoch;
+
+                if (nextEpoch != lastEpoch)
+                {
+                    var la = new LineAnnotation() { Type = LineAnnotationType.Vertical, X = ws.Generations + offset, Color = OxyColors.Gray, Layer = AnnotationLayer.AboveSeries };
+
+                    if (epochLabels)
+                    {
+                        la.TextOrientation = AnnotationTextOrientation.AlongLine;
+                        la.Text = $"Epoch {nextEpoch}";
+                    }
+
+                    model.Annotations.Add(la);
+                    lastEpoch = nextEpoch;
+                }
+            }
+        }
+        
+        public static void AnnotateTracesTargetTransitions(PlotModel model, TracesInfo tracesInfo, bool targetLabels)
+        {
+            foreach (var ts in tracesInfo.TargetSwitches)
+            {
+                var la = new LineAnnotation() { Type = LineAnnotationType.Vertical, X = ts.Generation, Color = OxyColors.Gray };
+                    
+                if (targetLabels)
+                {
+                    la.TextOrientation = AnnotationTextOrientation.AlongLine;
+                    la.Text = ts.Target.FriendlyName;
+                }
+
+                model.Annotations.Add(la);
+            }
+        }
+    }
+
+    public class GenomePlotting
+    {
+        public static PlotModel PlotDtm(string title, Linear.Matrix<double> transMat, double? min, double? max)
+        {
+            var plot = GeneralPlotting.PlotMatrix(title, transMat, "Trait j", "Trait i", false, min, max);
+            
+            plot.PlotType = PlotType.Cartesian;
+            
+            return plot;
+        }
+
+        public static PlotModel PlotExpressionVector(string title, Linear.Vector<double> transMat, int moduleCount, double? min, double? max)
+        {
+            var plot = GeneralPlotting.PlotVectorFolded(title, transMat, moduleCount, "Sub-Trait", "Module", false, min, max);
+            
+            plot.PlotType = PlotType.Cartesian;
+            
+            return plot;
+        }
+    }
+
+    public class GeneralPlotting
+    {
+        public static PlotModel PlotMatrix(string title, Linear.Matrix<double> matrix, string xlabel = "X", string ylabel = "Y", bool labels = false, double? min = null, double? max = null)
+        {
+            double[,] darr = matrix.Transpose().ToArray();
+            return PlotHeatmap(title, darr, false, true, xlabel, ylabel, labels, min: min, max: max);
+        }
+
+        public static PlotModel PlotVectorFolded(string title, Linear.Vector<double> vector, int foldCount, string xlabel = "X", string ylabel = "Y", bool labels = false, double? min = null, double? max = null)
+        {
+            int foldLength = vector.Count / foldCount;
+            double[,] darr = new double[foldLength, foldCount];
+
+            for (int i = 0; i < foldLength; i++)
+                for (int j = 0; j < foldCount; j++)
+                    darr[i, j] = vector[j * foldLength + i];
+
+            return PlotHeatmap(title, darr, false, true, xlabel, ylabel, labels, min: min, max: max);
+        }
+
+        public static PlotModel PlotHeatmap(string title, double[,] darr, bool flipX = false, bool flipY = false, string xlabel = "X", string ylabel = "Y", bool labels = false, double? min = null, double? max = null)
+        {
+            // pad
+            bool padX = false;
+            if (darr.GetLength(0) <= 1)
+            {
+                padX = true;
+
+                var ndarr = new double[2, darr.GetLength(1)];
+                for (int i = 0; i < darr.GetLength(1); i++)
+                {
+                    ndarr[0, i] = darr[0, i];
+                    ndarr[1, i] = darr[0, i];
+                }
+                darr = ndarr;
+            }
+            bool padY = false;
+            if (darr.GetLength(1) <= 1)
+            {
+                padY = true;
+
+                var ndarr = new double[darr.GetLength(0), 2];
+                for (int i = 0; i < darr.GetLength(0); i++)
+                {
+                    ndarr[i, 0] = darr[i, 0];
+                    ndarr[i, 1] = darr[i, 0];
+                }
+                darr = ndarr;
+            }
+
+            // plot
+            PlotModel model = new PlotModel() { Title = title };
+
+            model.IsLegendVisible = false;
+            Axis xaxis = new LinearAxis() { Key = "x", Title = xlabel, Position = AxisPosition.Bottom, MinimumMinorStep = 1, MinimumMajorStep = 1 };
+            Axis yaxis = new LinearAxis() { Key = "y", Title = ylabel, Position = AxisPosition.Left, MinimumMinorStep = 1, MinimumMajorStep = 1 };
+            Axis coloraxis = ColoursGrey(min: min, max: max);
+
+            if (flipX)
+            {
+                xaxis.StartPosition = 1;
+                xaxis.EndPosition = 0;
+            }
+
+            if (flipY)
+            {
+                yaxis.StartPosition = 1;
+                yaxis.EndPosition = 0;
+            }
+
+            model.Axes.Add(xaxis);
+            model.Axes.Add(yaxis);
+            model.Axes.Add(coloraxis);
+
+            double x0 = padX ? -1.0 : 0.0;
+            double x1 = padX ? 1.0 : darr.GetLength(0) - 1.0;
+            double y0 = padY ? -1.0 : 0.0;
+            double y1 = padY ? 1.0 : darr.GetLength(1) - 1.0;
+
+            // don't use Sillyness for these: they are integers
+            //xaxis.MajorStep = Math.Max(1.0, Math.Floor(x1 / 5.0));
+            //yaxis.MajorStep = Math.Max(1.0, Math.Floor(y1 / 5.0));
+
+            //xaxis.MaximumPadding = 0;
+            //xaxis.MinimumPadding = 0;
+            //yaxis.MaximumPadding = 0;
+            //yaxis.MinimumPadding = 0;
+
+            double datamin = darr.Min2D(true);
+            double datamax = darr.Max2D();
+            double datarange = datamax - datamin;
+            //coloraxis.MajorStep = SillyFloor10(datarange / 3.0);
+            //coloraxis.MajorStep = SillyFloor10(datarange / 3.0);
+
+            var hmap = new OxyPlot.Series.HeatMapSeries() { Title = title, RenderMethod = OxyPlot.Series.HeatMapRenderMethod.Rectangles, X0 = x0, X1 = x1, Y0 = y0, Y1 = y1, XAxisKey = "x", YAxisKey = "y", ColorAxisKey = "c" };
+            hmap.Data = darr;
+            model.Series.Add(hmap);
+
+            if (labels)
+                hmap.LabelFontSize = 0.2;
+
+            return model;
+        }
+
+        public static double SillyFloor10(double d)
+        {
+            double l10 = Math.Log10(d);
+            l10 = Math.Floor(l10);
+            double e = Math.Pow(10, l10);
+
+            if (e * 5 <= d)
+                return e * 5;
+            if (e * 2 <= d)
+                return e * 2;
+            return e;
+        }
+
+        public static double SillyCeil10(double d)
+        {
+            double l10 = Math.Log10(d);
+            l10 = Math.Ceiling(l10);
+            double e = Math.Pow(10, l10);
+
+            if (e / 5 >= d)
+                return e / 5;
+            if (e / 2 >= d)
+                return e / 2;
+            return e;
+        }
+
+        public static LinearColorAxis ColoursGrey(string key = "c", double? min = null, double? max = null)
+        {
+            var axis = new OxyPlot.Axes.LinearColorAxis { Key = key, Title = "", Position = OxyPlot.Axes.AxisPosition.Right, IsPanEnabled = false, IsZoomEnabled = false, HighColor = OxyPlot.OxyColors.White, LowColor = OxyPlot.OxyColors.Black, Palette = OxyPlot.OxyPalettes.Gray(100) };
+
+            if (max.HasValue)
+                axis.Maximum = max.Value;
+            if (min.HasValue)
+                axis.Minimum = min.Value;
+
+            return axis;
+        }
+
+        public static void ApplyMarkers(Series s, MarkerType markerType, OxyColor color)
+        {
+            if (s is LineSeries ls)
+                ApplyMarkers(ls, markerType, color);
+            if (s is LineSeries ss)
+                ApplyMarkers(ss, markerType, color);
+        }
+
+        public static void ApplyMarkers(LineSeries ls, MarkerType markerType, OxyColor color)
+        {
+            ls.MarkerType = markerType;
+            if (!color.IsAutomatic())
+            {
+                if (MarkerNeedsLine(markerType))
+                    ls.MarkerStroke = color;
+                else
+                    ls.MarkerFill = color;
+            }   
+        }
+
+        public static void ApplyMarkers(ScatterSeries ss, MarkerType markerType, OxyColor color)
+        {
+            ss.MarkerType = markerType;
+            if (!color.IsAutomatic())
+            {
+                if (MarkerNeedsLine(markerType))
+                    ss.MarkerStroke = color;
+                else
+                    ss.MarkerFill = color;
+            }
+        }
+
+        public static bool MarkerNeedsLine(MarkerType markerType)
+        {
+            return markerType == MarkerType.Cross
+                || markerType == MarkerType.Star;
+        }
+    }
+
+    public class HistogramPlotting
+    {
+        public static PlotModel PrepareDodgyHistogram(string title, string xTitle, string yTitle, string xAxisKey, string yAxisKey)
+        {
+            var plot = new PlotModel() { Title = title };
+            plot.Axes.Add(new LinearAxis() { Title = xTitle, Position = AxisPosition.Bottom, Key = xAxisKey ?? "x" });
+            plot.Axes.Add(new LinearAxis() { Title = yTitle, Position = AxisPosition.Left, Key = yAxisKey ?? "y" });
+            return plot;
+        }
+
+        public static void DodgyHistogram(PlotModel plot, IReadOnlyList<double[]> samples, IReadOnlyList<OxyColor> colors, double min, double max, int binCount)
+        {
+            for (int i = 0; i < samples.Count; i++)
+            {
+                var hs = new HistogramSeries() { FillColor = colors[i] };
+                var bins = HistogramHelpers.Collect(samples[i], HistogramHelpers.CreateUniformBins(min, max, binCount), new BinningOptions(BinningOutlierMode.RejectOutliers, BinningIntervalType.InclusiveLowerBound, BinningExtremeValueMode.IncludeExtremeValues)).ToArray();
+                bins = Dodgify(bins, i, samples.Count, samples[i].Length).ToArray();
+                
+                hs.Items.AddRange(bins);
+                plot.Series.Add(hs);
+            }
+        }
+
+        public static IEnumerable<HistogramItem> Dodgify(IEnumerable<HistogramItem> his, int index, int count, int totalHeight)
+        {
+            return his.Select(hi =>
+            {
+                var w = (hi.RangeEnd - hi.RangeStart) / count;
+                var s = hi.RangeStart + w * index;
+                var e = s + w;
+                return new HistogramItem(s, e, hi.Area * totalHeight * w, hi.Count); // convert to a count
+            });
+        }
+    }
+
+    public class BoxPlotting
+    {
+        public static PlotModel PrepareBoxPlot(IReadOnlyList<string> labels, string categoryTitle, string yTitle, string xAxisKey, string yAxisKey)
+        {
+            var plot = new PlotModel() { Title = "Mean Fitness Distributions" };
+            var catAxis = new CategoryAxis() { Title = categoryTitle, Position = AxisPosition.Bottom, Key = xAxisKey ?? "x" };
+            plot.Axes.Add(catAxis);
+            plot.Axes.Add(new LinearAxis() { Title = yTitle, Position = AxisPosition.Left, Key = yAxisKey ?? "y" });
+
+            foreach (var label in labels)
+                catAxis.Labels.Add(label);
+
+            return plot;
+        }
+
+        public static BoxPlotSeries BoxPlot(PlotModel plot, IReadOnlyList<double[]> samples)
+        {
+            var bp = new BoxPlotSeries();
+            for (int i = 0; i < samples.Count; i++)
+            {
+                var sample = samples[i];
+
+                if (sample == null)
+                    continue;
+
+                var bi = BoxPlotting.Box(i, sample);
+                bp.Items.Add(bi);
+            }
+            plot.Series.Add(bp);
+
+            return bp;
+        }
+
+        public static BoxPlotItem Box(double x, IEnumerable<double> samples)
+        {
+            var sorted = samples.OrderBy(s => s).ToArray();
+
+            if (sorted.Contains(double.NaN))
+                return null;
+
+            var lq = MathNet.Numerics.Statistics.ArrayStatistics.LowerQuartileInplace(sorted);
+            var uq = MathNet.Numerics.Statistics.ArrayStatistics.UpperQuartileInplace(sorted);
+            var median = MathNet.Numerics.Statistics.ArrayStatistics.MedianInplace(sorted);
+
+            var lt = lq - (median - lq) * 1.5;
+            var ut = uq + (uq - median) * 1.5;
+
+            var low = Math.Min(samples.Where(s => s >= lt).Min(), lq);
+            var high = Math.Max(samples.Where(s => s <= ut).Max(), uq);
+
+            var bi = new BoxPlotItem(x, low, lq, median, uq, high);
+
+            foreach (var o in sorted.Where(s => s < low || s > high))
+                bi.Outliers.Add(o);
+
+            return bi;
+        }
+
+        public static double Median(IReadOnlyList<double> samples)
+        {
+            int c = samples.Count;
+            if (c % 2 == 1)
+                return samples[c / 2];
+            else
+                return (samples[c / 2 - 1] + samples[c / 2]) / 2.0;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/PopulationRunners.cs b/M4MCode/M4M_MkI/M4M.New/PopulationRunners.cs
new file mode 100644
index 0000000..0a2a700
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/PopulationRunners.cs
@@ -0,0 +1,66 @@
+using M4M.Epistatics;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using static M4M.Misc;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public static class LowRunners
+    {
+        // hopefully this will make repeat seeds most unlikely when we run the same experiment at the same time
+        private static readonly Random SeedProvider = new Random();
+
+        private static int GrabSeed()
+        {
+            lock (SeedProvider)
+            {
+                return SeedProvider.Next();
+            }
+        }
+
+        public static void RunExperiment(TextWriter console, string cliPrefix, string topdir, PopulationExperiment<DenseIndividual> experiment, int? seed = null, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, bool appendTimestamp = true, int savePeriodEpochs = 500, int savePeriodSeconds = 60*60)
+        {
+            experiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(experiment.Population.Clone(), experiment.PopulationConfig, topdir, appendTimestamp: appendTimestamp);
+            RunExperiment(console, cliPrefix, experiment, seed, densePopulationExperimentFactory, densePopulationExperimentFeedbackConfigurator, savePeriodEpochs, savePeriodSeconds);
+        }
+
+        public static void RunExperiment(TextWriter console, string cliPrefix, PopulationExperiment<DenseIndividual> experiment, int? seed = null, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, int savePeriodEpochs = 500, int savePeriodSeconds = 60*60)
+        {
+            var rand = seed == null ? new CustomMersenneTwister(GrabSeed()) : new CustomMersenneTwister(seed.Value);
+            var ioRand = seed == null ? new CustomMersenneTwister(GrabSeed()) : new CustomMersenneTwister(seed.Value);
+            var context = new ModelExecutionContext(rand);
+
+            var feedback = densePopulationExperimentFactory != null ? densePopulationExperimentFactory(ioRand, experiment.PopulationConfig, null) : null;
+            densePopulationExperimentFeedbackConfigurator?.Invoke(feedback);
+
+            PopulationExperimentRunners.WriteOutInitials(ioRand, experiment); // I've been using rand in various places: this is not good
+            experiment.Save("start", false);
+            PopulationExperimentRunners.RunEpochs(console, cliPrefix, experiment.PopulationConfig.ExperimentConfiguration.Epochs, context, experiment, feedback?.Feedback, savePeriodEpochs, savePeriodSeconds);
+        }
+
+        public static void ContinueExperiment(TextWriter console, string cliPrefix, string topdir, PopulationExperiment<DenseIndividual> experiment, int? seed = null, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, bool appendTimestamp = true, int savePeriodEpochs = 500, int savePeriodSeconds = 60*60)
+        {
+            experiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(experiment.Population.Clone(), experiment.PopulationConfig, topdir, experiment.Epoch, experiment.TotalGenerationCount, appendTimestamp: appendTimestamp);
+            ContinueExperiment(console, cliPrefix, experiment, seed, densePopulationExperimentFactory, densePopulationExperimentFeedbackConfigurator, savePeriodEpochs, savePeriodSeconds);
+        }
+
+        public static void ContinueExperiment(TextWriter console, string cliPrefix, PopulationExperiment<DenseIndividual> experiment, int? seed = null, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, int savePeriodEpochs = 500, int savePeriodSeconds = 60*60)
+        {
+            int epochsLeft = experiment.PopulationConfig.ExperimentConfiguration.Epochs - experiment.Epoch;
+
+            var rand = seed == null ? new CustomMersenneTwister(GrabSeed()) : new CustomMersenneTwister(seed.Value);
+            var ioRand = seed == null ? new CustomMersenneTwister(GrabSeed()) : new CustomMersenneTwister(seed.Value);
+            var context = new ModelExecutionContext(rand);
+
+            var feedback = densePopulationExperimentFactory != null ? densePopulationExperimentFactory(ioRand, experiment.PopulationConfig, null) : null;
+            densePopulationExperimentFeedbackConfigurator?.Invoke(feedback);
+            
+            PopulationExperimentRunners.RunEpochs(console, cliPrefix, epochsLeft, context, experiment, feedback?.Feedback, savePeriodEpochs, savePeriodSeconds);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.New/StringHelpers.cs b/M4MCode/M4M_MkI/M4M.New/StringHelpers.cs
new file mode 100644
index 0000000..b78691e
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.New/StringHelpers.cs
@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace M4M
+{
+    // why am I doing this?
+    public struct CodePoint
+    {
+        /// <summary>
+        /// Prepares a CodePoint from a pair from a High and Low surrogate
+        /// </summary>
+        /// <param name="highWord">The High surrogate</param>
+        /// <param name="lowWord">The Low surrogate</param>
+        public CodePoint(char highWord, char lowWord) : this()
+        {
+            HighWord = highWord;
+            LowWord = lowWord;
+
+            if (!char.IsSurrogatePair(highWord, lowWord))
+                throw new Exception("Char pair is not a surrogate pair");
+        }
+
+        /// <summary>
+        /// Prepares a CodePoint from a single-word character
+        /// </summary>
+        public CodePoint(char soloWord) : this()
+        {
+            HighWord = soloWord;
+            LowWord = default(char);
+
+            if (char.IsHighSurrogate(soloWord) || char.IsLowSurrogate(soloWord))
+                throw new Exception("Character is part of a surrogate pair");
+        }
+
+        /// <summary>
+        /// The High-word of a Surrogate Pair, or the single character of a Solo CodeWord.
+        /// </summary>
+        private readonly char HighWord;
+
+        /// <summary>
+        /// The Low-word of a Surrogate Pair, or else a default value.
+        /// </summary>
+        private readonly char LowWord;
+
+        /// <summary>
+        /// Returns whether the CodePoint represents a Surrogate Pair
+        /// </summary>
+        public bool IsSurrogatePair => char.IsHighSurrogate(HighWord);
+
+        /// <summary>
+        /// Enumerates the CodePoints is the given string
+        /// </summary>
+        public static IEnumerable<CodePoint> EnumerateCodePoints(string str)
+        {
+            int pos = 0;
+            while (NextCodePoint(str, ref pos, out CodePoint cp))
+                yield return cp;
+        }
+
+        /// <summary>
+        /// Determines the next CodePoint in the given string
+        /// Returns true if there is such a CodePoint; returns false if positioned at the end of the string
+        /// </summary>
+        /// <param name="str">The string whence to extract the CodePoint</param>
+        /// <param name="position">The position in the string that is the start of the CodePoint (modified by the callee)</param>
+        /// <param name="codePoint">The CodePoint at the given position, or a default value if the end of the string has been reached</param>
+        /// <returns></returns>
+        public static bool NextCodePoint(string str, ref int position, out CodePoint codePoint)
+        {
+            if (position == str.Length)
+            {
+                codePoint = default(CodePoint);
+                return false;
+            }
+            
+            codePoint = CodePointAtInternal(str, ref position);
+            return true;
+        }
+
+        /// <summary>
+        /// Retrieves the code-point at the given position in the given string
+        /// </summary>
+        /// <param name="str">The string whence to extract the CodePoint</param>
+        /// <param name="position">The position in the string that is the start of the CodePoint</param>
+        public static CodePoint CodePointAt(string str, int position)
+        {
+            return CodePointAtInternal(str, ref position);
+        }
+
+        /// <summary>
+        /// Retrieves the code-point at the given position in the given string
+        /// Increments the position according to the size of the CodePoint (position is unchanged if an exception is thrown)
+        /// </summary>
+        /// <param name="str">The string whence to extract the CodePoint</param>
+        /// <param name="pos">The position in the string that is the start of the CodePoint</param>
+        private static CodePoint CodePointAtInternal(string str, ref int pos)
+        {
+            if (pos < 0 || pos >= str.Length)
+                throw new ArgumentOutOfRangeException("Position is outside of the string");
+
+            if (char.IsHighSurrogate(str, pos))
+            {
+                if (pos + 1 >= str.Length)
+                    throw new ArgumentOutOfRangeException("Position contains a High surrogate, but is the last element of the string");
+
+                return new CodePoint(str[pos++], str[pos++]);
+            }
+            else if (char.IsLowSurrogate(str, pos))
+            {
+                throw new ArgumentException("Position refers to a Low surrogate");
+            }
+            else
+            {
+                return new CodePoint(str[pos++]);
+            }
+        }
+
+        /// <summary>
+        /// Converts the CodePoint into a String
+        /// </summary>
+        public override string ToString()
+        {
+            if (IsSurrogatePair)
+                return "" + HighWord + LowWord;
+            else
+                return "" + HighWord;
+        }
+
+        /// <summary>
+        /// Appends the CodeWord to the given StringBuilder
+        /// </summary>
+        public void AppendTo(StringBuilder sb)
+        {
+            if (IsSurrogatePair)
+            {
+                sb.Append(HighWord);
+                sb.Append(LowWord);
+            }
+            else
+            {
+                sb.Append(HighWord);
+            }
+        }
+        
+        public static bool operator ==(CodePoint l, CodePoint r)
+        {
+            return l.HighWord == r.HighWord && l.LowWord == r.LowWord;
+        }
+
+        public static bool operator !=(CodePoint l, CodePoint r)
+        {
+            return l.HighWord != r.HighWord || l.LowWord != r.LowWord;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is CodePoint))
+            {
+                return false;
+            }
+
+            var point = (CodePoint)obj;
+            return HighWord == point.HighWord &&
+                   LowWord == point.LowWord;
+        }
+
+        public override int GetHashCode()
+        {
+            var hashCode = -254445490;
+            hashCode = hashCode * -1521134295 + HighWord.GetHashCode();
+            hashCode = hashCode * -1521134295 + LowWord.GetHashCode();
+            return hashCode;
+        }
+
+        public static implicit operator CodePoint(char c)
+        {
+            return new CodePoint(c);
+        }
+    }
+
+    public static class StringHelpers
+    {
+        /// <summary>
+        /// Compares two strings with StringComparison.InvariantCultureIgnoreCase. 
+        /// </summary>
+        public static bool SoftEquals(this string str, string other)
+        {
+            return string.Equals(str, other, StringComparison.InvariantCultureIgnoreCase);
+        }
+
+        /// <summary>
+        /// The default delimiters to be used when Splitting and Joining strings.
+        /// </summary>
+        public static readonly IReadOnlyList<char> DefaultDelimiters = new char[] { ' ', '\t' };
+
+        /// <summary>
+        /// HashSet of CodePoints mirroring the entries in <see cref="DefaultDelimiters"/>
+        /// </summary>
+        private static readonly HashSet<CodePoint> DefaultCodePointDelimiters = new HashSet<CodePoint>(DefaultDelimiters.Select(c => (CodePoint)c));
+
+        /// <summary>
+        /// The default quote single to be used when Spliting and Joining strings.
+        /// </summary>
+        public static readonly char DefaultQuote = '"';
+
+        /// <summary>
+        /// The default quote single to be used when Spliting and Joining strings.
+        /// </summary>
+        public static readonly char DefaultEscape = '\\';
+
+        /// <summary>
+        /// Splits the given string uses the default delimiters, quote, and escape characters.
+        /// </summary>
+        public static IEnumerable<string> Split(this string str)
+        {
+            return Split(str, DefaultCodePointDelimiters, DefaultQuote, DefaultEscape);
+        }
+        
+        /// <summary>
+        /// Splits the given string using the given delimiter, quote, and escape CodePoints.
+        /// </summary>
+        public static IEnumerable<string> Split(this string str, IEnumerable<CodePoint> delimiters, CodePoint quote, CodePoint escape)
+        {
+            HashSet<CodePoint> delims = new HashSet<CodePoint>(delimiters);
+            return Split(str, delims, quote, escape);
+        }
+        
+        /// <summary>
+        /// Splits the given string using the given delimiter, quote, and escape CodePoints.
+        /// </summary>
+        public static IEnumerable<string> Split(this string str, HashSet<CodePoint> delimiters, CodePoint quote, CodePoint escape)
+        {
+            // why did I write this? it's not going to be much more efficient than using strings, and all it gives you is some confidence that you arn't splitting on something daft
+
+            int pos = 0;
+            CodePoint c;
+            bool escapeNext = false;
+            bool inQuote = false;
+
+            StringBuilder sb = new StringBuilder();
+            while (CodePoint.NextCodePoint(str, ref pos, out c))
+            {
+                if (escapeNext)
+                {
+                    c.AppendTo(sb);
+                    
+                    escapeNext = false;
+                    continue;
+                }
+
+                if (c == escape)
+                {
+                    escapeNext = true;
+                    continue;
+                }
+
+                if (c == quote)
+                {
+                    inQuote = !inQuote;
+                    continue;
+                }
+
+                if (!inQuote && delimiters.Contains(c))
+                {
+                    yield return sb.ToString();
+                    sb.Clear();
+                    continue;
+                }
+
+                c.AppendTo(sb);
+            }
+
+            if (sb.Length > 0)
+                yield return sb.ToString();
+        }
+
+        /// <summary>
+        /// Escapes quotes and escapes in the given string.
+        /// </summary>
+        public static string Escape(string str, CodePoint quote, CodePoint escape)
+        {
+            int pos = 0;
+            CodePoint c;
+
+            bool noChange = true;
+            StringBuilder sb = new StringBuilder();
+            while (CodePoint.NextCodePoint(str, ref pos, out c))
+            {
+                if (c == escape || c == quote)
+                {
+                    escape.AppendTo(sb);
+                    noChange = false;
+                }
+
+                c.AppendTo(sb);
+            }
+
+            if (noChange)
+                return str;
+            else
+                return sb.ToString();
+        }
+
+        /// <summary>
+        /// Joins the given strings with the default delimiter, escaping quote and escape codepoints.
+        /// </summary>
+        public static string Join(this IEnumerable<string> strs)
+        {
+            return Join(strs, DefaultDelimiters[0], DefaultQuote, DefaultEscape);
+        }
+
+        /// <summary>
+        /// Joins the given strings with the given delimiter, escaping quote and escape codepoints.
+        /// </summary>
+        public static string Join(this IEnumerable<string> strs, CodePoint delimiter, CodePoint quote, CodePoint escape)
+        {
+            StringBuilder sb = new StringBuilder();
+            foreach (var str in strs)
+            {
+                if (sb.Length > 0)
+                    sb.Append(delimiter);
+                
+                var escaped = Escape(str, quote, escape);
+                sb.Append(escaped);
+            }
+
+            return sb.ToString();
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old.sln b/M4MCode/M4M_MkI/M4M.Old.sln
new file mode 100644
index 0000000..3c0721b
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old.sln
@@ -0,0 +1,137 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26430.16
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M", "M4M\M4M.csproj", "{B6E4567C-558D-44D1-9CDC-FD4472E785E6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.Model", "M4M.Model\M4M.Model.csproj", "{6A470149-D199-4B60-9F76-0F01FBD587E0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M.Old", "M4M.Old\M4M.Old.csproj", "{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.New", "M4M.New\M4M.New.csproj", "{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.CoreRunner", "M4M.CoreRunner\M4M.CoreRunner.csproj", "{9F765166-0377-446C-8A23-3D5E08DC51D5}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release2|Any CPU = Release2|Any CPU
+		Release2|x64 = Release2|x64
+		Release3|Any CPU = Release3|Any CPU
+		Release3|x64 = Release3|x64
+		Release4|Any CPU = Release4|Any CPU
+		Release4|x64 = Release4|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|x64.ActiveCfg = Debug|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|x64.Build.0 = Debug|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|x64.ActiveCfg = Release|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|x64.Build.0 = Release|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|Any CPU.ActiveCfg = Release2|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|Any CPU.Build.0 = Release2|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|x64.ActiveCfg = Release2|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|x64.Build.0 = Release2|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|Any CPU.ActiveCfg = Release3|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|Any CPU.Build.0 = Release3|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|x64.ActiveCfg = Release3|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|x64.Build.0 = Release3|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|Any CPU.ActiveCfg = Release4|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|Any CPU.Build.0 = Release4|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|x64.ActiveCfg = Release4|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|x64.Build.0 = Release4|x64
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|x64.Build.0 = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|x64.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|x64.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|x64.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|x64.Build.0 = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|x64.Build.0 = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|x64.Build.0 = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|x64.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {24F33315-4D5B-43D3-93D4-9B4D6A603384}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/M4M_MkI/M4M.Old/Analysis.cs b/M4MCode/M4M_MkI/M4M.Old/Analysis.cs
new file mode 100644
index 0000000..7ba2369
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Analysis.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enogh)
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using static M4M.Analysis;
+using System.Diagnostics;
+using MathNet.Numerics.Random;
+using OxyPlot;
+
+namespace M4M
+{
+    public class PdfPlotExporter : IPlotExporter
+    {
+        public static PlotExportReport Translate(Plotting.PdfExportReport report)
+        {
+            return new PlotExportReport(report.Filename, report.ExportedWidth, report.ExportedHeight);
+        }
+
+        public PlotExportReport ExportPlot(string filename, PlotModel model, double size, bool forceAspect, PostProcessPlot postProcessor = null)
+        {
+            return Translate(Plotting.OldOxyPlotting.ExportToPdf(model, filename + ".pdf", size, true, forceAspect, postProcessor));
+        }
+
+        public PlotExportReport ExportPlot(string filename, PlotModel model, double width, double height, bool forceAspect, PostProcessPlot postProcessor = null)
+        {
+            return Translate(Plotting.OldOxyPlotting.ExportToPdf(model, filename + ".pdf", width, height, true, forceAspect, postProcessor));
+        }
+    }
+
+    public class MutantAnalysis
+    {
+        public GenomeInfo<DenseGenome> Source { get; }
+        public IReadOnlyList<GenomeInfo<DenseGenome>> MutantInfos { get; }
+
+        public ReproductionRules ReproductionRules { get; }
+        public DevelopmentRules DevelopmentRules { get; }
+        public JudgementRules JudgementRules { get; }
+        public ITarget Target { get; }
+
+        public double[] FitnessChanges { get; }
+
+        /// <summary>
+        /// makes a copy of the genome, so you don't have to worry about it
+        /// </summary>
+        public MutantAnalysis(DenseGenome genome, int mutantCount, ModelExecutionContext context, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, ITarget target)
+        {
+            // defensive clone (insiginificant cost in terms of everything else)
+            genome = genome.Clone(context);
+
+            ReproductionRules = rrules;
+            DevelopmentRules = drules;
+            JudgementRules = jrules;
+            Target = target;
+            
+            Source = GenomeInfo<DenseGenome>.Process(genome, context, rrules, drules, jrules, target);
+            MutantInfos = Enumerable.Range(0, mutantCount).Select(i => GenomeInfo<DenseGenome>.Process(genome.Mutate(context, rrules), context, rrules, drules, jrules, target)).ToArray();
+
+            FitnessChanges = MutantInfos.Select(mi => mi.Judgement.CombinedFitness - Source.Judgement.CombinedFitness).ToArray();
+        }
+        
+        public void ComputeFitnessChangePercentiles(double[] positions, ref double[] reusableBuffer, ref Percentile[] percentiles)
+        {
+            Percentile.ComputePercentiles(positions, FitnessChanges, ref reusableBuffer, ref percentiles);
+        }
+
+        public void Plot(string outDir, string filePrefix, string prefix)
+        {
+            EnsureDirectory(outDir);
+
+            string file(string name) => System.IO.Path.Combine(outDir, filePrefix + name);
+
+            { // histogram of mutant fitness
+                OxyPlot.PlotModel model = Plotting.OldOxyPlotting.LinearAxes(prefix + " Mutation Fitness Variation", "Fitness Delta", "Frequency");
+                OxyPlot.Series.HistogramSeries hs = new OxyPlot.Series.HistogramSeries() { Title = "Mutation Benefit Variation" };
+                hs.ItemsSource = OxyPlot.HistogramHelpers.Collect(FitnessChanges, HistogramHelpers.CreateUniformBins(FitnessChanges.Min(), FitnessChanges.Max(), 100), new BinningOptions(BinningOutlierMode.RejectOutliers, BinningIntervalType.InclusiveLowerBound, BinningExtremeValueMode.IncludeExtremeValues));
+                model.Series.Add(hs);
+                Plotting.OldOxyPlotting.ExportToPdf(model, file("mfv.pdf"), true, true);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/DefaultDensePopulationFeedback.cs b/M4MCode/M4M_MkI/M4M.Old/DefaultDensePopulationFeedback.cs
new file mode 100644
index 0000000..bc9ce2b
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/DefaultDensePopulationFeedback.cs
@@ -0,0 +1,190 @@
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static M4M.MiscPlotting;
+using static M4M.Analysis;
+using M4M.State;
+
+namespace M4M
+{
+    public static class OldPopulationTrace
+    {
+        /// <summary>
+        /// Runs a trace, and returns the terminal population
+        /// Does not modify the population passed to it
+        /// </summary>
+        /// <param name="rand">Random source</param>
+        /// <param name="stuff">File Stuff</param>
+        /// <param name="population">A template population (cloned before doing anything)</param>
+        /// <param name="populationConfig">The population configuration</param>
+        /// <param name="target">The target to trace to</param>
+        /// <returns>The cloned population after the trace</returns>
+        public static Population<DenseIndividual> RunTrace(ModelExecutionContext context, FileStuff stuff, Population<DenseIndividual> population, PopulationExperimentConfig<DenseIndividual> populationConfig, ITarget target, int exposureEpoch)
+        {
+            var rand = context.Rand;
+            
+            var populationSpinner = populationConfig.CustomPopulationSpinner ?? DefaultPopulationSpinner<DenseIndividual>.Instance;
+
+            // clone population
+            population = population.Clone();
+
+            // perform a trace from one target (i.e. whatever was before) to another
+            ExperimentConfiguration config = populationConfig.ExperimentConfiguration;
+            int N = config.Size;
+
+            List<double> traceFitness = new List<double>();
+            List<IndividualJudgement<DenseIndividual>> traceMedianSamples = new List<IndividualJudgement<DenseIndividual>>();
+
+            void gjFeedback(int generation, IReadOnlyList<IndividualJudgement<DenseIndividual>> judgements)
+            {
+                traceFitness.Add(judgements.Average(ij => ij.Judgement.CombinedFitness));
+                traceMedianSamples.Add(judgements.OrderBy(ij => ij.Judgement.CombinedFitness).Skip(judgements.Count / 2).First());
+            }
+
+            // spin it
+            ExposureInformation exposureInformation = new ExposureInformation(config.GenerationsPerTargetPerEpoch);
+            target.NextExposure(rand, populationConfig.ExperimentConfiguration.JudgementRules, exposureEpoch, ref exposureInformation);
+            if (exposureInformation.ExposureDuration < 0)
+                throw new Exception("ExposureDuration was negative");
+
+            populationSpinner.SpinPopulation(population, context, config.ReproductionRules, config.DevelopmentRules, config.JudgementRules, target, populationConfig.SelectorPreparer, exposureInformation.ExposureDuration, gjFeedback, populationConfig.EliteCount, populationConfig.HillclimberMode);
+                
+            PlotLine(stuff.TitlePrefix + "TrMeanFitness", stuff.File("trfitness"), traceFitness.ToArray(), 1, 1, "Generation", "Mean Fitness");
+                
+            double[][] trPhenotypeState = Enumerable.Range(0, N).Select(i => traceMedianSamples.Select(ij => ij.Individual.Phenotype.Vector[i]).ToArray()).ToArray();
+            PlotLines(stuff.TitlePrefix + "Tr(Median)PhenotypicState", stuff.File("trpstate"), trPhenotypeState, 1, 1, "Generation", "Trait Expression");
+                
+            double[][] trInitialState = Enumerable.Range(0, N).Select(i => traceMedianSamples.Select(ij => ij.Individual.Genome.InitialState[i]).ToArray()).ToArray();
+            PlotLines(stuff.TitlePrefix + "Tr(Median)InitialState", stuff.File("tristate"), trInitialState, 1, 1, "Generation", "Trait Expression");
+
+            double[][] trRcs = Enumerable.Range(0, N*N).Select(i => traceMedianSamples.Select(ij => ij.Individual.Genome.TransMat[i / N, i % N]).ToArray()).ToArray();
+            PlotLines(stuff.TitlePrefix + "Tr(Median)Rcs", stuff.File("trrcs"), trRcs, 1, 1, "Generation", "Regulation Coeficient");
+
+            return population;
+        }
+    }
+
+    /// <summary>
+    /// Adds a lot of plotting stuff to the CommonDensePopulationExperimentFeedback
+    /// </summary>
+    public class DefaultDensePopulationExperimentFeedback : CommonDensePopulationExperimentFeedback
+    {
+        public static DensePopulationExperimentFeedbackFactory SimpleDefaultDensePopulationExperimentFeedback(bool disableTransientIO = false, bool disableAllIO = false, int sampleMax = 2000, int savePeriod = 200, int plotPeriod = 1000, int tracePeriod = 500, IPopulationResetOperation<DenseIndividual> preTraceResetOperation = null)
+        {
+            return (rand, popConfig, feedback) => new DefaultDensePopulationExperimentFeedback(rand, popConfig, disableTransientIO, disableAllIO, savePeriod, plotPeriod, tracePeriod, feedback, Math.Max(1, popConfig.ExperimentConfiguration.Epochs / sampleMax), preTraceResetOperation ?? popConfig.PopulationResetOperation);
+        }
+        
+        public DefaultDensePopulationExperimentFeedback(RandomSource rand, PopulationExperimentConfig<DenseIndividual> populationConfig, bool disableTransientIO, bool disableAllIO, int genomeSavePeriod, int partialPlotPeriod = 100, int tracePeriod = 100, PopulationExperimentFeedback<DenseIndividual> feedback = null, int samplePeriod = 1, IPopulationResetOperation<DenseIndividual> preTraceResetOperation = null) : base(System.Console.Out, rand, populationConfig, disableTransientIO, disableAllIO, genomeSavePeriod, partialPlotPeriod, feedback, samplePeriod)
+        {
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            disableTransientIO = disableTransientIO | disableAllIO;
+            
+            ITarget lastTarget = null;
+            
+            Feedback.EndTarget.Register((stuff, population, target, epoch, generationCount, oldPopulationJudgements) =>
+            {
+                // record last Target
+                lastTarget = target;
+            });
+
+            // feedback
+            Feedback.EndEpoch.Register((stuff, population, epoch, generationCount, opj) =>
+            {
+                if (epoch % genomeSavePeriod == 0 || epoch == populationConfig.ExperimentConfiguration.Epochs)
+                {
+                    DenseIndividual di = population.PeekRandom(rand);
+                    DenseGenome g = di.Genome;
+                    Phenotype p = di.Phenotype;
+                    
+                    if (!disableTransientIO)
+                    {
+                        // plot genome
+                        PlotVectorFolded(stuff.TitlePrefix + "Phenotype " + epoch, stuff.File("p" + epoch + ".pdf"), p.Vector);
+                        PlotMatrix(stuff.TitlePrefix + "Development Transformation Matrix " + epoch, stuff.File("dtm" + epoch + ".pdf"), g.TransMat);
+                        PlotVectorFolded(stuff.TitlePrefix + "Intial State " + epoch, stuff.File("is" + epoch + ".pdf"), g.InitialState);
+                    }
+                }
+                
+                if (epoch % partialPlotPeriod == 0)
+                {
+                    if (!disableTransientIO)
+                    {
+                        PlotLine(stuff.TitlePrefix + "Fitness" + epoch, stuff.File("fitness" + epoch), _fitness.Take(epoch).ToArray(), 1, SamplePeriod, "Epoch", "Mean Fitness");
+
+                        PlotRcs(stuff, epoch + "(best)");
+                    }
+                }
+
+                if (epoch % tracePeriod == 0)
+                {
+                    if (!disableTransientIO)
+                    {
+                        foreach (ITarget other in populationConfig.ExperimentConfiguration.Targets.Where(t => t != lastTarget))
+                        {
+                            var tpop = population.Clone();
+                            preTraceResetOperation?.Reset(context, tpop, Config.InitialStateResetRange, Config.DevelopmentRules, Config.ReproductionRules, other);
+                            OldPopulationTrace.RunTrace(context, stuff.CreateSubStuff((string)("tr" + epoch + "\\" + lastTarget.FriendlyName + "_" + other.FriendlyName)), tpop, populationConfig, other, epoch);
+                        }
+                    }
+
+                    //Target other = populationConfig.ExperimentConfiguration.Targets.FirstOrDefault(t => t != lastTarget);
+                    //PopulationExperiment.RunTrace(rand, stuff.CreateSubStuff("tr" + epoch), population.Clone(), populationConfig, other);
+                }
+            });
+
+            Feedback.Finished.Register((stuff, population, epoch, generationCount) =>
+            {
+                if (!disableAllIO) // we usually want the terminals
+                {
+                    DenseIndividual di = population.PeekRandom(rand);
+                    DenseGenome g = di.Genome;
+                    Phenotype p = di.Phenotype;
+                    
+                    // plot individual
+                    PlotVectorFolded(stuff.Title("Terminal Phenotype"), stuff.File("tp.pdf"), p.Vector);
+                    PlotMatrix(stuff.Title("Terminal Development Transformation Matrix"), stuff.File("tdtm.pdf"), g.TransMat);
+                    PlotVectorFolded(stuff.Title("Terminal Intial State"), stuff.File("tis.pdf"), g.InitialState);
+
+                    PlotLine(stuff.TitlePrefix + "Fitness", stuff.File("fitness"), _fitness.ToArray(), 1, SamplePeriod, "Epoch", "Mean Fitness");
+
+                    PlotRcs(stuff, "_t(best)");
+
+                    System.IO.File.WriteAllText(stuff.File("dtmDesc10.txt"), "Thresh10" + Environment.NewLine + Module.Describe(DtmClassification.DiscerneTrueModules(g.TransMat, g.TransMat.Enumerate().Max(e => Math.Abs(e)) / 10.0)));
+                    System.IO.File.WriteAllText(stuff.File("dtmDesc50.txt"), "Thresh50" + Environment.NewLine + Module.Describe(DtmClassification.DiscerneTrueModules(g.TransMat, g.TransMat.Enumerate().Max(e => Math.Abs(e)) / 50.0)));
+                    System.IO.File.WriteAllText(stuff.File("dtmDesc100.txt"), "Thresh100" + Environment.NewLine + Module.Describe(DtmClassification.DiscerneTrueModules(g.TransMat, g.TransMat.Enumerate().Max(e => Math.Abs(e)) / 100.0)));
+
+                    // perform trace to every target... to every target
+                    foreach (ITarget other in populationConfig.ExperimentConfiguration.Targets.Where(t => t != lastTarget))
+                    {
+                        var tpop = population.Clone();
+                        preTraceResetOperation?.Reset(context, tpop, Config.InitialStateResetRange, Config.DevelopmentRules, Config.ReproductionRules, other);
+                        var traced = OldPopulationTrace.RunTrace(context, stuff.CreateSubStuff((string)("ttr" + lastTarget.FriendlyName + "_" + other.FriendlyName)), tpop, populationConfig, other, epoch);
+
+                        foreach (ITarget otherOther in populationConfig.ExperimentConfiguration.Targets)
+                        {
+                            var innterTpop = traced.Clone();
+                            preTraceResetOperation?.Reset(context, innterTpop, Config.InitialStateResetRange, Config.DevelopmentRules, Config.ReproductionRules, otherOther);
+                            OldPopulationTrace.RunTrace(context, stuff.CreateSubStuff((string)("ttr" + other.FriendlyName + "_" + otherOther.FriendlyName)), innterTpop, populationConfig, otherOther, epoch);
+                        }
+                    }
+                }
+            });
+        }
+        
+        public void PlotRcs(FileStuff stuff, string name)
+        {
+            double[][] rcs = GetRcsData();
+            //PlotLines(stuff.Title(name), stuff.File("rcs" + name), rcs, 1, SamplePeriod, "Epoch", "Regulation Coeficient");
+            PlotRcs(stuff.Title(name), stuff.File("rcs" + name), rcs, SamplePeriod);
+        }
+        
+        public static void PlotRcs(string title, string filename, double[][] rcs, int samplePeriod)
+        {
+            PlotLines(title, filename, rcs, 1, samplePeriod, "Epoch", "Regulation Coeficient");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Epistatics/Epistatics.cs b/M4MCode/M4M_MkI/M4M.Old/Epistatics/Epistatics.cs
new file mode 100644
index 0000000..c0d1a25
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Epistatics/Epistatics.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using static M4M.MiscPlotting;
+using static M4M.Analysis;
+using System.Diagnostics;
+
+namespace M4M.Epistatics
+{
+    public class EpistaticStuff
+    {
+        public static Population<DenseIndividual> GeneralEpistaticExperiment(string friendlyName, EpistaticTargetPackage targetPackage, string superDir, RandomSource rand, RandomSource ioRand, bool disableTransientIO, double λ, double gNoiseTerm = 0.6, double bNoiseTerm = 0.00001, int K = 1000, int epochs = 100000, IRegularisationFunction<IGenome> regularisationFunction = null, IPopulationResetOperation<DenseIndividual> resetIndividualInitialStateOperation = null, IPopulationResetOperation<DenseIndividual> preTraceResetIndividualInitialStateOperation = null, ISquash squash = null, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, int popSize = 1, int eliteCount = 1, bool hillclimberMode = true)
+        {
+            var p = EpistaticStuff.EpistaticPopulationTest(
+                friendlyName: friendlyName,
+                targetPackage: targetPackage,
+                λ: λ,
+                epigenetic: false,
+                epochs: epochs / targetPackage.Targets.Length,
+                K: K,
+                T: 10,
+                decayRate: 0.2,
+                paretoSelection: false,
+                g0: null,
+                gFixed: false,
+                gClamp: null,
+                gResetProb: 1.0,
+                gResetRange: new Misc.Range(-1, 1),
+                gNoiseTerm: gNoiseTerm,
+                gNoiseType: NoiseType.Gaussian,
+                bProb: 1,
+                b0: null,
+                bNoiseTerm: bNoiseTerm,
+                tracePeriod: 500,
+                squash: squash,
+                superDir: superDir,
+                gUpdateCount: 1,
+                popSize: popSize,
+                eliteCount: eliteCount,
+                hillclimberMode: hillclimberMode,
+                disableTransientIO: disableTransientIO,
+                rand: rand,
+                ioRand: ioRand,
+                regularisationFunction: regularisationFunction,
+                resetIndividualInitialStateOperation: resetIndividualInitialStateOperation,
+                preTraceResetIndividualInitialStateOperation: preTraceResetIndividualInitialStateOperation,
+                gOpenEntries: gOpenEntries,
+                bOpenEntries: bOpenEntries,
+                densePopulationExperimentFeedbackConfigurator: densePopulationExperimentFeedbackConfigurator);
+            return p;
+        }
+        
+        public static Population<DenseIndividual> EpistaticPopulationTest(string friendlyName, EpistaticTargetPackage targetPackage, double λ, bool epigenetic = false, double gNoiseTerm = 0.1, NoiseType gNoiseType = NoiseType.Uniform, int epochs = 5000, int K = 1000, int T = 10, double bProb = 1.0, Linear.Vector<double> g0 = null, bool gFixed = false, bool paretoSelection = false, Linear.Matrix<double> b0 = null, int popSize = 5, Range gClamp = null, int tracePeriod = 500, double decayRate = 0.2, ISquash squash = null, string superDir = "", double bNoiseTerm = 0.001, NoiseType bNoiseType = NoiseType.Uniform, int gUpdateCount = 1, ICycler targetCycler = null, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, double gResetProb = 0, Range gResetRange = null, IRegularisationFunction<IGenome> regularisationFunction = null, string customPrefix = null, int eliteCount = 0, bool disableTransientIO = false, bool disableAllIO = false, RandomSource rand = null, RandomSource ioRand = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, IPopulationResetOperation<DenseIndividual> resetIndividualInitialStateOperation = null, IPopulationResetOperation<DenseIndividual> preTraceResetIndividualInitialStateOperation = null, bool hillclimberMode = false)
+        {
+            disableTransientIO = disableTransientIO | disableAllIO;
+
+            ITarget[] targets = targetPackage.Targets;
+
+            // config
+            int N = targetPackage.TargetSize;
+
+            gClamp = gClamp ?? new Range(-1.0, 1.0);
+            regularisationFunction = regularisationFunction ?? JudgementRules.L1Equivalent;
+            resetIndividualInitialStateOperation = resetIndividualInitialStateOperation ?? new RandomIndependantPopulationResetOperation();
+            preTraceResetIndividualInitialStateOperation = preTraceResetIndividualInitialStateOperation ?? new RandomIndependantPopulationResetOperation();
+
+            DevelopmentRules drules = new DevelopmentRules(T, 1.0, decayRate, squash ?? DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(gFixed ? 0.0 : (epigenetic ? 0.0 : gNoiseTerm), bNoiseTerm, bProb, false, gUpdateCount, gClamp, gNoiseType, bNoiseType);
+            JudgementRules jrules = new JudgementRules(λ, regularisationFunction, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, targetCycler ?? Cyclers.Loop, epochs, K, gResetProb, gResetRange, false, drules, rrules, jrules);
+
+            ITransMatMutator mutator = null;
+
+            // essentials
+            rand = rand ?? new MathNet.Numerics.Random.MersenneTwister();
+            ioRand = ioRand ?? new MathNet.Numerics.Random.MersenneTwister();
+            var context = new ModelExecutionContext(rand);
+
+            g0 = g0 ?? Linear.CreateVector.Dense(N, 0.0);
+            b0 = b0 ?? Linear.CreateMatrix.Dense(N, N, 0.0);
+
+            DenseIndividual CreateDefaultIndividual(ModelExecutionContext _context)
+            {
+                var customDevelopmentStep = Development.DefaultDevelopmentStep(N, epigenetic ? Math.Sqrt(gNoiseTerm) : 0.0);
+                var genome = new DenseGenome(g0, b0, gOpenEntries, bOpenEntries, mutator, null, null, null, customDevelopmentStep, null, false);
+                var individual = DenseIndividual.Develop(genome, _context, drules, epigenetic);
+                return individual;
+            }
+
+            // population stuff
+            ISelectorPreparer<DenseIndividual> selector = paretoSelection
+                ? SelectorPreparers<DenseIndividual>.Pareto
+                : SelectorPreparers<DenseIndividual>.Ranked;
+            Population<DenseIndividual> population = new Population<DenseIndividual>(context, _context => CreateDefaultIndividual(_context), popSize);
+            PopulationExperimentConfig<DenseIndividual> popConfig = new PopulationExperimentConfig<DenseIndividual>(config, selector, eliteCount, hillclimberMode, null, resetIndividualInitialStateOperation, DefaultPopulationSpinner<DenseIndividual>.Instance);
+            DefaultDensePopulationExperimentFeedback denseFeedback = new DefaultDensePopulationExperimentFeedback(ioRand, popConfig, disableTransientIO, disableAllIO, Math.Max(1, epochs / 25), 500, tracePeriod, null, Math.Max(1, epochs / 1000), preTraceResetIndividualInitialStateOperation);
+            PopulationExperimentFeedback<DenseIndividual> feedback = denseFeedback.Feedback;
+            densePopulationExperimentFeedbackConfigurator?.Invoke(denseFeedback);
+            
+            // go
+            string expName = $"EpistaticPopTest{friendlyName}{targetPackage.Name}";
+            string subName = "λ" + λ + "Epi" + epigenetic + "mG" + gNoiseTerm + "mB" + bNoiseTerm + "E" + epochs + "K" + K + "T" + T + "P" + (paretoSelection ? "T" : "F") + decayRate.ToString(".000") + "G" + gUpdateCount;
+            PopulationExperimentRunners.RunExperiment<DenseIndividual>(System.Console.Out, "", context, population, popConfig, outDir: System.IO.Path.Combine(superDir, expName + "_" + subName), disableTransientIO: disableTransientIO, disableAllIO: disableAllIO, feedback: feedback, filePrefix: "", titlePrefix: customPrefix ?? (subName + " "));
+            return population;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Evolvability.cs b/M4MCode/M4M_MkI/M4M.Old/Evolvability.cs
new file mode 100644
index 0000000..b5c952e
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Evolvability.cs
@@ -0,0 +1,466 @@
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+
+namespace M4M
+{
+    public class Evolvability
+    {
+        public class EnvironmentTransitionConfiguration
+        {
+            public EnvironmentTransitionConfiguration(int generations, int samplePeriod, DevelopmentRules drules, ReproductionRules rrules, JudgementRules jrules)
+            {
+                Generations = generations;
+                SamplePeriod = samplePeriod;
+                DevelopmentRules = drules;
+                ReproductionRules = rrules;
+                JudgementRules = jrules;
+            }
+
+            public int Generations { get; }
+            public int SamplePeriod { get; }
+            public DevelopmentRules DevelopmentRules { get; }
+            public ReproductionRules ReproductionRules { get; }
+            public JudgementRules JudgementRules { get; }
+
+            public void WriteOut(System.IO.StreamWriter cw)
+            {
+                cw.WriteLine();
+                cw.WriteLine($"Generations = {Generations}");
+
+                cw.WriteLine();
+                cw.WriteLine($"DRules.UpdateRate (τ1) = {DevelopmentRules.UpdateRate}");
+                cw.WriteLine($"DRules.DecayRate (τ2) = {DevelopmentRules.DecayRate}");
+                cw.WriteLine($"DRules.TimeSteps (T) = {DevelopmentRules.TimeSteps}");
+                cw.WriteLine($"DRules.SquashFunction (σ) = {DevelopmentRules.Squash.Name}");
+
+                cw.WriteLine();
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialStateMutationSize)} (M_G) = {ReproductionRules.InitialStateMutationSize}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialStateMutationType)} (M_G type) = {ReproductionRules.InitialStateMutationType}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixMutationSize)} (M_B) = {ReproductionRules.DevelopmentalTransformationMatrixMutationSize}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixMutationType)} (M_B type) = {ReproductionRules.DevelopmentalTransformationMatrixMutationType}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixRate)} (R_B) = {ReproductionRules.DevelopmentalTransformationMatrixRate}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.ExclusiveBMutation)} (BEx) = {ReproductionRules.ExclusiveBMutation}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialTraitUpdates)} (C_G) = {ReproductionRules.InitialTraitUpdates}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.InitialStateClamping)} = {ReproductionRules.InitialStateClamping}");
+                cw.WriteLine($"RRules.{nameof(ReproductionRules.DevelopmentalTransformationMatrixClamping)} = {ReproductionRules.DevelopmentalTransformationMatrixClamping}");
+
+                cw.WriteLine();
+                cw.WriteLine($"JRules.NoiseFactor (κ) = {JudgementRules.NoiseFactor}");
+                cw.WriteLine($"JRules.RegularisationFactor (λ) = {JudgementRules.RegularisationFactor}");
+                cw.WriteLine($"JRules.RegularisationFunction (ϕ) = {JudgementRules.RegularisationFunction.Name}");
+            }
+        }
+
+        public class EnvironmentPopulationTransitionConfiguration
+        {
+            public EnvironmentTransitionConfiguration EnvironmentTransitionConfiguration { get; }
+            public ISelectorPreparer<DenseIndividual> SelectorPreparer { get; }
+
+            public EnvironmentPopulationTransitionConfiguration(EnvironmentTransitionConfiguration environmentTransitionConfiguration, ISelectorPreparer<DenseIndividual> selectorPreparer)
+            {
+                EnvironmentTransitionConfiguration = environmentTransitionConfiguration;
+                SelectorPreparer = selectorPreparer;
+            }
+
+            public void WriteOut(System.IO.StreamWriter cw)
+            {
+                EnvironmentTransitionConfiguration.WriteOut(cw);
+
+                cw.WriteLine();
+                cw.WriteLine($"PopulationSpecifics.SelectorPreparer = {SelectorPreparer.Name}");
+            }
+        }
+
+        public class EnvironmentTransitionResults
+        {
+            public EnvironmentTransitionResults(double[] fitnessSamples, EnvironmentTransitionConfiguration config)
+            {
+                FitnessSamples = fitnessSamples;
+                Configuration = config;
+            }
+
+            public double[] FitnessSamples { get; }
+            public EnvironmentTransitionConfiguration Configuration { get; }
+            private int SamplePeriod => Configuration.SamplePeriod;
+
+            public int IndexToGeneration(int index)
+            {
+                return index * SamplePeriod;
+            }
+
+            public void GenerationToIndex(int generation, out int closestSampledGeneration, out int closestSampleIndex)
+            {
+                // floor
+                closestSampleIndex = generation / SamplePeriod;
+                closestSampledGeneration = IndexToGeneration(closestSampleIndex);
+            }
+
+            public int GenerationToIndex(int generation)
+            {
+                GenerationToIndex(generation, out int _, out int csi);
+                return csi;
+            }
+        }
+
+        public static void StringOut(EnvironmentTransitionResults[] results, string fname)
+        {
+            using (StreamWriter writer = new StreamWriter(fname))
+            {
+                StringOut(results, writer);
+            }
+        }
+
+        public static void StringOut(EnvironmentTransitionResults[] results, StreamWriter writer)
+        {
+            writer.WriteLine(string.Join("\t", results[0].FitnessSamples.Select((_, i) => results[0].IndexToGeneration(i))));
+
+            foreach (var r in results)
+            {
+                writer.WriteLine(string.Join("\t", results[0].FitnessSamples));
+            }
+        }
+
+        // lazy, very lazy
+        public  static void PlotLines(IEnumerable<Tuple<EnvironmentTransitionResults[], OxyColor, string>> results, string fname, string title)
+        {
+            PlotModel model = new PlotModel() { Title = title };
+
+            foreach (var r in results)
+            {
+                EnvironmentTransitionResults[] res = r.Item1;
+                OxyColor colour = r.Item2;
+                string name = r.Item3;
+
+                for (int i = 0; i < res.Length; i++)
+                {
+                    LineSeries ls = new LineSeries();
+
+                    if (i == 0)
+                        ls.Title = name; // only name one of them
+
+                    double[] samples = res[i].FitnessSamples;
+                    for (int idx = 0; i < samples.Length; i++)
+                    {
+                        // this definitely can't be more efficient, oh no, certainly not
+                        ls.Points.Add(new DataPoint(res[i].IndexToGeneration(idx), samples[idx]));
+                    }
+
+                    model.Series.Add(ls);
+                }
+            }
+
+            Plotting.OldOxyPlotting.ExportToPdf(model, fname, true);
+        }
+
+        /// <summary>
+        /// Do not store the results, they are liable to be modified
+        /// </summary>
+        public static IEnumerable<Tuple<int, Percentile[]>> Percentiles(EnvironmentTransitionResults[] results, double[] positions, int step = 1)
+        {
+            for (int i = 0; i < results[0].FitnessSamples.Length; i += step)
+            {
+                double[] buffer = null;
+                Percentile[] percentiles = null;
+                Percentile.ComputePercentiles(positions, results.Select(r => r.FitnessSamples[i]), ref buffer, ref percentiles);
+                yield return new Tuple<int, Percentile[]>(results[0].IndexToGeneration(i), percentiles);
+            }
+        }
+
+        public static void PlotAreas(IEnumerable<Tuple<EnvironmentTransitionResults[], OxyColor, string>> results, string fname, string title, int step = 1)
+        {
+            PlotModel model = new PlotModel() { Title = title };
+
+            model.Axes.Add(new LinearAxis() { Title = "Generation", Position = AxisPosition.Bottom });
+            model.Axes.Add(new LinearAxis() { Title = "Fitness", Position = AxisPosition.Left });
+
+            foreach (var r in results)
+            {
+                EnvironmentTransitionResults[] res = r.Item1;
+                OxyColor colour = r.Item2;
+                string name = r.Item3;
+                
+                AreaSeries innerArea = new AreaSeries() { Color = colour.ChangeIntensity(0.8), Title = name + " 25-75" };
+                AreaSeries outerArea = new AreaSeries() { Color = colour.ChangeIntensity(0.5), Title = name + " 10-90" };
+                LineSeries meanLine = new LineSeries() { Color = colour, Title = name + " mean" };
+                
+                var percentiles = Percentiles(res, new double[] { 0.1, 0.25, 0.5, 0.75, 0.9 }, step);
+
+                foreach (var p in percentiles)
+                {
+                    var generation = p.Item1;
+                    var pcts = p.Item2;
+                    
+                    outerArea.Points.Add(new DataPoint(generation, pcts[0].Value));
+                    outerArea.Points2.Add(new DataPoint(generation, pcts[4].Value));
+
+                    innerArea.Points.Add(new DataPoint(generation, pcts[1].Value));
+                    innerArea.Points2.Add(new DataPoint(generation, pcts[3].Value));
+
+                    meanLine.Points.Add(new DataPoint(generation, pcts[2].Value));
+                }
+                
+                model.Series.Add(outerArea);
+                model.Series.Add(innerArea);
+                model.Series.Add(meanLine);
+            }
+            
+            Plotting.OldOxyPlotting.ExportToPdf(model, fname, true, true, false, null);
+            Plotting.OldOxyPlotting.ExportToPdf(model, fname + "_sm", 0.9, true, false, null);
+        }
+
+        public interface ISampler
+        {
+            void Start(EnvironmentTransitionConfiguration config, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement);
+            void Stop(int generations, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement);
+
+            /// <summary>
+            /// The values past to this should NOT be stored or modified, they are liable to change, be returned more than once, or otherwise act strangely
+            /// </summary>
+            void Sample(int generation, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement);
+        }
+
+        public delegate void SubSampler(int generation, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement, string name, string outDir, Func<string, string> file);
+
+        public class SimpleSampler : ISampler
+        {
+            public SimpleSampler(string outDir, string name, SubSampler subSampler)
+            {
+                if (!System.IO.Directory.Exists(outDir))
+                {
+                    System.IO.Directory.CreateDirectory(outDir);
+                }
+
+                OutDir = outDir;
+                _name = name;
+                _generations = new List<int>();
+                SubSampler = subSampler;
+            }
+
+            public string OutDir { get; }
+            public string _name { get; }
+            private int Count = 0;
+            public string Name => _name + Count;
+            private List<int> _generations { get; }
+            public IReadOnlyList<int> Generations => _generations;
+            private List<double>[] _initials { get; set; }
+            public IReadOnlyList<IReadOnlyList<double>> Initials => _initials;
+            private List<double> _fitnesses { get; set; }
+            public IReadOnlyList<double> Fitnesses => _fitnesses;
+            private int Size => Initials.Count;
+            private EnvironmentTransitionConfiguration CurrentConfig;
+            private SubSampler SubSampler { get; }
+            
+            private string File(string name)
+            {
+                return System.IO.Path.Combine(OutDir, Name + name);
+            }
+
+            public void Sample(int generation, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement)
+            {
+                _generations.Add(generation);
+                for (int i = 0; i < Size; i++)
+                {
+                    _initials[i].Add(genome.InitialState[i]);
+                }
+
+                _fitnesses.Add(judgement.CombinedFitness);
+
+                SubSampler?.Invoke(generation, genome, p, judgement, Name, OutDir, File);
+            }
+
+            public void Start(EnvironmentTransitionConfiguration config, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement)
+            {
+                // reset/setup for next run
+
+                Count++;
+                _generations.Clear();
+                CurrentConfig = config;
+
+                _initials = new List<double>[genome.Size];
+                for (int i = 0; i < Size; i++)
+                {
+                    _initials[i] = new List<double>();
+                }
+
+                _fitnesses = new List<double>();
+            }
+
+            public void Stop(int generations, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement)
+            {
+                PlotIstraj();
+                PlotFitness();
+
+                MiscPlotting.PlotMatrix(Name + ": DTM", File("_dtm.pdf"), genome.TransMat, "Trait i", "Trait j");
+            }
+
+            private void PlotIstraj()
+            {
+                PlotModel plotModel = new PlotModel() { Title = Name + ": Trait Initial State Trajectories" };
+                plotModel.Axes.Add(new LinearAxis() { Position = AxisPosition.Bottom, Title = "Generation" });
+                plotModel.Axes.Add(new LinearAxis() { Position = AxisPosition.Left, Title = "Initial Trait Values" });
+
+                int t = 0;
+                foreach (var traitTraj in Initials)
+                {
+                    LineSeries ls = new LineSeries() { Title = "Trait" + t };
+
+                    for (int i = 0; i < traitTraj.Count; i++)
+                    {
+                        ls.Points.Add(new DataPoint(Generations[i], traitTraj[i]));
+                    }
+
+                    plotModel.Series.Add(ls);
+
+                    t++;
+                }
+
+                plotModel.InvalidatePlot(true);
+                Plotting.OldOxyPlotting.ExportToPdf(plotModel, File("_istraj.pdf"), true, true);
+            }
+
+            private void PlotFitness()
+            {
+                PlotModel plotModel = new PlotModel() { Title = Name + ": Fitness" };
+                plotModel.Axes.Add(new LinearAxis() { Position = AxisPosition.Bottom, Title = "Generation" });
+                plotModel.Axes.Add(new LinearAxis() { Position = AxisPosition.Left, Title = "Fitness" });
+                
+                LineSeries ls = new LineSeries();
+
+                for (int i = 0; i < _fitnesses.Count; i++)
+                {
+                    ls.Points.Add(new DataPoint(Generations[i], _fitnesses[i]));
+                }
+
+                plotModel.Series.Add(ls);
+
+                plotModel.InvalidatePlot(true);
+                Plotting.OldOxyPlotting.ExportToPdf(plotModel, File("_fitness.pdf"), true, true);
+            }
+        }
+
+        public static EnvironmentTransitionResults[] RunManyEnvironmentTransitionExperiments(ModelExecutionContext context, int count, DenseGenome genome, ITarget target, ISampler sampler, EnvironmentTransitionConfiguration config, bool oneTimeSampler)
+        {
+            EnvironmentTransitionResults[] results = new EnvironmentTransitionResults[count];
+
+            for (int i = 0; i < count; i++)
+            {
+                Console.WriteLine(i + "\t\\" + count);
+
+                results[i] = RunEnvironmentTransitionExperiment(context, genome, target, sampler, config);
+
+                if (i == 0 && oneTimeSampler)
+                    sampler = null;
+            }
+
+            return results;
+        }
+
+        public static EnvironmentTransitionResults RunEnvironmentTransitionExperiment(ModelExecutionContext context, DenseGenome genome, ITarget target, ISampler sampler, EnvironmentTransitionConfiguration config)
+        {
+            var rand = context.Rand;
+
+            // current genome and fitness
+            target.NextGeneration(rand, config.JudgementRules);
+            DenseGenome g = genome;
+            Phenotype p = g.Develop(context, config.DevelopmentRules);
+            MultiMeasureJudgement f = MultiMeasureJudgement.Judge(g, p, config.JudgementRules, target);
+
+            int samples = config.Generations / config.SamplePeriod;
+
+            // data
+            double[] fitnesses = new double[samples + 1];
+            
+            // gen 0
+            int si = 0;
+            sampler?.Start(config, g, p, f);
+            sampler?.Sample(si * config.SamplePeriod, g, p, f);
+            fitnesses[si] = f.CombinedFitness;
+
+            for (; ++si <= samples;)
+            {
+                Experiment.SpinMk2(ref g, ref p, ref f, context, target, config.ReproductionRules, config.DevelopmentRules, config.JudgementRules, config.SamplePeriod);
+
+                sampler?.Sample(si * config.SamplePeriod, g, p, f);
+                fitnesses[si] = f.CombinedFitness;
+            }
+
+            sampler?.Stop(si * config.SamplePeriod, g, p, f);
+            return new EnvironmentTransitionResults(fitnesses, config);
+        }
+
+        public static EnvironmentTransitionResults[] RunManyEnvironmentPopulationTransitionExperiments(ModelExecutionContext context, int count, DenseGenome genome, int populationSize, ITarget target, ISampler sampler, EnvironmentPopulationTransitionConfiguration config, bool oneTimeSampler, int exposureEpoch)
+        {
+            EnvironmentTransitionResults[] results = new EnvironmentTransitionResults[count];
+
+            for (int i = 0; i < count; i++)
+            {
+                Console.WriteLine(i + "\t\\" + count);
+
+                results[i] = RunEnvironmentPopulationTransitionExperiment(context, genome, populationSize, target, sampler, config, exposureEpoch);
+
+                if (i == 0 && oneTimeSampler)
+                    sampler = null;
+            }
+
+            return results;
+        }
+
+        public static EnvironmentTransitionResults RunEnvironmentPopulationTransitionExperiment(ModelExecutionContext context, DenseGenome genome, int populationSize, ITarget target, ISampler sampler, EnvironmentPopulationTransitionConfiguration popConfig, int exposureEpoch)
+        {
+            var config = popConfig.EnvironmentTransitionConfiguration;
+            var populationSpinner = DefaultPopulationSpinner<DenseIndividual>.Instance;
+
+            var rand = context.Rand;
+
+            // current genome and fitness
+            ExposureInformation exposureInformation = new ExposureInformation(config.Generations);
+            target.NextExposure(rand, config.JudgementRules, exposureEpoch, ref exposureInformation);
+            if (exposureInformation.ExposureDuration < 0)
+                throw new Exception("ExposureDuration was negative");
+
+            target.NextGeneration(rand, config.JudgementRules); // this is a bit of a lie
+            DenseGenome g = genome;
+            Phenotype p = g.Develop(context, config.DevelopmentRules);
+            MultiMeasureJudgement f = MultiMeasureJudgement.Judge(g, p, config.JudgementRules, target);
+
+            var templateIndividual = DenseIndividual.Develop(genome, context, config.DevelopmentRules, false);
+            Population<DenseIndividual> population = new Population<DenseIndividual>(templateIndividual, populationSize);
+
+            int samples = config.Generations / config.SamplePeriod;
+
+            // data
+            double[] fitnesses = new double[samples + 1];
+
+            // gen 0
+            int si = 0;
+            sampler?.Start(config, g, p, f);
+            sampler?.Sample(si * config.SamplePeriod, g, p, f);
+            fitnesses[si] = f.CombinedFitness;
+            
+            for (; ++si <= samples;)
+            {
+                var res = populationSpinner.SpinPopulation(population, context, config.ReproductionRules, config.DevelopmentRules, config.JudgementRules, target, popConfig.SelectorPreparer, config.SamplePeriod, null, 0, false);
+                
+                // take median individual
+                var median = res.ArgMedian(ij => ij.Judgement.CombinedFitness);
+                g = median.Individual.Genome;
+                p = median.Individual.Phenotype;
+                f = median.Judgement;
+
+                sampler?.Sample(si * config.SamplePeriod, g, p, f);
+                fitnesses[si] = f.CombinedFitness;
+            }
+
+            sampler?.Stop(si * config.SamplePeriod, g, p, f);
+            return new EnvironmentTransitionResults(fitnesses, config);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Experiment.cs b/M4MCode/M4M_MkI/M4M.Old/Experiment.cs
new file mode 100644
index 0000000..81ddcb9
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Experiment.cs
@@ -0,0 +1,390 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enogh)
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using static M4M.MiscPlotting;
+using static M4M.Analysis;
+
+namespace M4M
+{   
+    public class Experiment
+    {
+        /// <summary>
+        /// Returns the terminal genome
+        /// </summary>
+        public static TerminalInfo RunExperiment(RandomSource rand, ExperimentConfiguration config, string outDir, DenseGenome startingGenome = null, ExperimentFeedback feedback = null, string filePrefix = "", string titlePrefix = "", int bigSavePeriod = 200, int bigSaveCount = 100, bool appendTimeToOutDir = true, bool openTerminalsPlots = true, bool disableIO = false, int mutantAnalysisPeriod = 0, RandomSource ioRand = null, bool disableTransientIO = false)
+        {
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            if (!disableIO)
+            {
+                if (appendTimeToOutDir)
+                    outDir += "_" + NowTime;
+                System.IO.Directory.CreateDirectory(outDir);
+            }
+
+            string file(string name)
+            {
+                return System.IO.Path.Combine(outDir, filePrefix + name);
+            }
+
+            System.IO.StreamWriter open(string name)
+            {
+                return new System.IO.StreamWriter(file(name));
+            }
+
+            if (!disableIO)
+            {
+                // print out config
+                using (var cw = open("config.txt"))
+                {
+                    cw.WriteLine("# Config for " + outDir);
+
+                    config.WriteOut(cw);
+                    
+                    cw.WriteLine();
+                    cw.WriteLine();
+                    cw.WriteLine("# Starting Genome Info");
+
+                    if (startingGenome == null)
+                    {
+                        cw.WriteLine("(Default Genome)");
+                    }
+                    else
+                    {
+                        string d = "(Default)";
+                        cw.WriteLine($"Size = {startingGenome.Size}");
+                        cw.WriteLine($"CustomTransMatMutator = {startingGenome.CustomInitialStateMutator?.Name ?? d}");
+                        cw.WriteLine($"CustomInitialStateMutator = {startingGenome.CustomTransMatMutator?.Name ?? d}");
+                        cw.WriteLine($"CustomDevelopmentStep = {startingGenome.CustomDevelopmentStep?.Name ?? d}");
+                    }
+                }
+            }
+
+            // boring stuff
+            System.Diagnostics.Stopwatch sw = new Stopwatch();
+            sw.Reset();
+            sw.Start();
+
+            int size = config.Size;
+            DevelopmentRules drules = config.DevelopmentRules;
+            ReproductionRules rrules = config.ReproductionRules;
+            JudgementRules jrules = config.JudgementRules;
+
+            // init genome
+            // TODO: determine proper way from paper
+            DenseGenome g = startingGenome ?? DenseGenome.CreateDefaultGenome(size);
+            double f = -1; // current fitness
+
+            // dec target (init inside epoch loop)
+            int currentTargetIndex = 0; // always start with 0 for simplicity
+            ITarget currentTarget = null;
+
+            // time remaining estimations
+            TimeSpan lastSampleDuration = default(TimeSpan);
+            int lastSampleEpoch = -1;
+
+            // records
+            int maxRecordCount = 1000;
+            int recordPeriod = Clamp(config.Epochs / maxRecordCount, 1, int.MaxValue);
+            double[][] regulationCoefs = CreateEmpty<double>(config.Size * config.Size, config.Epochs / recordPeriod + 1);
+            double[] fitness = new double[config.Epochs / recordPeriod + 1];
+
+            if (!disableIO)
+            {
+                // plot targets
+                for (int i = 0; i < config.Targets.Count; i++)
+                {
+                    if (config.Targets[i] is VectorTarget vt)
+                    {
+                        PlotVectorFolded(titlePrefix + " Target " + i, file("target" + i + ".pdf"), vt.Vector);
+                    }
+                }
+            }
+
+            // refit bigSavePeriod (max 10 big saves)
+            bigSavePeriod = Clamp(config.Epochs / 10, bigSavePeriod, int.MaxValue);
+            
+            Console.WriteLine(outDir);
+            Console.WriteLine(config.Epochs + "epochs ");
+
+            int totalGenerationCount = 0;
+
+            // for each epoch (start at 1 this time)
+            for (int epoch = 1; epoch <= config.Epochs; epoch++)
+            {
+                for (int ti = 0; ti < config.Targets.Count; ti++)
+                {
+                    // select next currentTarget
+                    currentTargetIndex = config.TargetCycler.Cycle(rand, currentTargetIndex, config.Targets.Count);
+                    currentTarget = config.Targets[currentTargetIndex];
+
+                    feedback?.StartTarget.Call(st => st(g, currentTarget, epoch, totalGenerationCount));
+
+                    // update fitness
+                    currentTarget.NextGeneration(rand, jrules);
+                    Phenotype p = g.Develop(context, drules);
+                    f = MultiMeasureJudgement.Judge(g, p, jrules, currentTarget).CombinedFitness;
+
+                    // mutate, judge, select cycle
+                    Spin(ref g, ref p, ref f, context, currentTarget, rrules, drules, jrules, config.GenerationsPerTargetPerEpoch);
+                    totalGenerationCount += config.GenerationsPerTargetPerEpoch;
+
+                    feedback?.EndTarget.Call(et => et(g, currentTarget, epoch, totalGenerationCount));
+                }
+
+                feedback?.EndEpoch.Call(ee => ee(g, epoch, totalGenerationCount));
+
+                // record stuff
+                if (epoch % recordPeriod == 0)
+                {
+                    // remest
+                    TimeSpan now = sw.Elapsed;
+                    TimeSpan remest = lastSampleEpoch < 0 ? default(TimeSpan) : TimeSpan.FromSeconds((now - lastSampleDuration).TotalSeconds * (double)(config.Epochs - epoch) / (double)(epoch - lastSampleEpoch));
+                    lastSampleEpoch = epoch;
+                    lastSampleDuration = now;
+
+                    // epoch summary
+                    Console.WriteLine($"Epoch {epoch} ({(100.0 * epoch / config.Epochs).ToString("0.0")}%): f = {f}, t = {sw.ElapsedMilliseconds}ms, remest={remest}");
+
+                    // record fitness
+                    fitness[epoch / recordPeriod] = f;
+
+                    // record regulation coefs
+                    int o = 0;
+                    for (int i = 0; i < g.Size; i++)
+                    {
+                        for (int j = 0; j < g.Size; j++)
+                        {
+                            regulationCoefs[o][epoch / recordPeriod] = g.TransMat[i, j];
+                            o++;
+                        }
+                    }
+                }
+
+                // no more than bigSaveCount or so of these
+                if ((epoch % bigSavePeriod == 0 || epoch % Clamp(config.Epochs / bigSaveCount, 1, int.MaxValue) == 0))
+                {
+                    if (!disableIO && !disableTransientIO)
+                    {
+                        // save genome
+                        SaveGenome(file("genome" + epoch + ".dat"), g);
+
+                        // plot genome
+                        PlotVectorFolded(titlePrefix + "Phenotype " + epoch, file("p" + epoch + ".pdf"), g.Develop(context, drules).Vector);
+                        PlotVectorFolded(titlePrefix + "Epoch " + epoch, file("ps" + epoch + ".pdf"), g.Develop(context, drules).Vector, "Sub-trait", "Module");
+                        PlotMatrix(titlePrefix + "Development Transformation Matrix " + epoch, file("dtm" + epoch + ".pdf"), g.TransMat, "Trait i", "Trait j");
+                        PlotMatrix("Epoch " + epoch, file("dtms" + epoch + ".pdf"), g.TransMat, "Trait i", "Trait j");
+                        PlotVectorFolded(titlePrefix + "Intial State " + epoch, file("is" + epoch + ".pdf"), g.InitialState);
+                        PlotVectorFolded(titlePrefix + "Epoch " + epoch, file("ism" + epoch + ".pdf"), g.InitialState, "Sub-trait", "Module");
+                    }
+                }
+
+                // no more than 10 or so of these
+                if (epoch > 0 && epoch % bigSavePeriod == 0)
+                {
+                    try
+                    {
+                        if (!disableIO && !disableTransientIO)
+                        {
+                            SaveTrajectories(file("regcoefs" + epoch + ".dat"), regulationCoefs, recordPeriod);
+
+                            PlotLines(titlePrefix + "Regulation Coefs " + epoch, file("rcs" + epoch + ".pdf"), regulationCoefs, 0, recordPeriod, xTitle: "Epoch", yTitle: "Weight");
+
+                            PlotLine(titlePrefix + "Fitness " + epoch, file("fitness" + epoch + ".pdf"), fitness, 0, recordPeriod, xTitle: "Epoch", yTitle: "Fitness");
+
+                            if (ioRand != null)
+                            {
+                                ITarget other = config.Targets.FirstOrDefault(t => t != currentTarget);
+                                Experiment.RunTrace(ioRand, new FileStuff(outDir, "", "").CreateSubStuff("tr" + epoch), g, config, other);
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        Console.WriteLine(ex);
+                    }
+                }
+
+                // mutant analysis is independantly cycled
+                if (mutantAnalysisPeriod > 0 && (epoch % mutantAnalysisPeriod == 0 || epoch == config.Epochs))
+                {
+                    Console.WriteLine("(MutantAnalysis for epoch " + epoch);
+                    MutantAnalysis ma = new MutantAnalysis(g, 10000, context, rrules, drules, jrules, config.Targets[currentTargetIndex]); // 'current' target
+                    ma.Plot(outDir, "MA" + epoch, titlePrefix + " MA" + epoch + ": ");
+                }
+            }
+
+            feedback?.Finished.Call(_f => _f(g, config.Epochs, totalGenerationCount));
+            
+            // traces
+
+
+            if (!disableIO)
+            {
+                // terminals
+                SaveTrajectories(file("regcoefs.dat"), regulationCoefs, recordPeriod);
+
+                PlotLines(titlePrefix + "Regulation Coefs", file("rcs.pdf"), regulationCoefs, 0, recordPeriod, xTitle: "Epoch", yTitle: "Weight");
+                if (openTerminalsPlots)
+                    Plotting.OldOxyPlotting.ViewPdf(file("rcs.pdf"));
+
+                PlotLine(titlePrefix + "Fitness", file("fitness.pdf"), fitness, 0, recordPeriod);
+                if (openTerminalsPlots)
+                    Plotting.OldOxyPlotting.ViewPdf(file("fitness.pdf"));
+
+                PlotMatrix(titlePrefix + "GRN", file("dtms.pdf"), g.TransMat, "Trait i", "Trait j");
+                PlotMatrix(titlePrefix + "GRN", file("dtm.pdf"), g.TransMat, "Trait i", "Trait j");
+            }
+
+            return new TerminalInfo(g, f);
+        }
+        
+        public static void RunTrace(RandomSource rand, FileStuff stuff, DenseGenome genome, ExperimentConfiguration config, ITarget target)
+        {
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            // perform a trace from one target (i.e. whatever was before) to another
+            int N = config.Size;
+
+            List<double> traceFitness = new List<double>();
+            List<GenomeJudgement> traceSamples = new List<GenomeJudgement>();
+
+            void gjFeedback(int generation, GenomeJudgement judgement)
+            {
+                traceFitness.Add(judgement.Judgement.CombinedFitness);
+                traceSamples.Add(judgement);
+            }
+            
+            // spin it
+            MultiMeasureJudgement fitness = default(MultiMeasureJudgement);
+            Phenotype phenotype = null;
+            Experiment.SpinMk2(ref genome, ref phenotype, ref fitness, context, target, config.ReproductionRules, config.DevelopmentRules, config.JudgementRules, config.GenerationsPerTargetPerEpoch, gjFeedback);
+                
+            PlotLine(stuff.TitlePrefix + "TrMeanFitness", stuff.File("trfitness"), traceFitness.ToArray(), 1, 1, "Generation", "Mean Fitness");
+                
+            double[][] trPhenotypeState = Enumerable.Range(0, N).Select(i => traceSamples.Select(ij => ij.Phenotype.Vector[i]).ToArray()).ToArray();
+            PlotLines(stuff.TitlePrefix + "TrPhenotypicState", stuff.File("trpstate"), trPhenotypeState, 1, 1, "Generation", "Trait Expression");
+                
+            double[][] trInitialState = Enumerable.Range(0, N).Select(i => traceSamples.Select(ij => ij.Genome.InitialState[i]).ToArray()).ToArray();
+            PlotLines(stuff.TitlePrefix + "TrInitialState", stuff.File("tristate"), trInitialState, 1, 1, "Generation", "Trait Expression");
+
+            double[][] trRcs = Enumerable.Range(0, N*N).Select(i => traceSamples.Select(ij => ij.Genome.TransMat[i / N, i % N]).ToArray()).ToArray();
+            PlotLines(stuff.TitlePrefix + "TrRcs", stuff.File("trrcs"), trRcs, 1, 1, "Generation", "Regulation Coeficient");
+        }
+
+        public static void Spin(ref DenseGenome genome, ref Phenotype p, ref double fitness, ModelExecutionContext context, ITarget t, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, int generations)
+        {
+            var rand = context.Rand;
+
+            DenseGenome g = genome;
+            double f = fitness;
+
+            for (int i = 0; i < generations; i++)
+            {
+                // update Target
+                bool invalidatef = t.NextGeneration(rand, jrules);
+
+                if (invalidatef)
+                {
+                    f = MultiMeasureJudgement.Judge(g, p, jrules, t).CombinedFitness;
+                }
+
+                // generate and judge
+                DenseGenome g2 = g.Mutate(context, rrules);
+                Phenotype p2 = g2.Develop(context, drules);
+                double f2 = MultiMeasureJudgement.Judge(g2, p2, jrules, t).CombinedFitness;
+
+                // select
+                if (f2 > f)
+                {
+                    g = g2;
+                    f = f2;
+                }
+            }
+
+            genome = g;
+            fitness = f;
+        }
+
+        public static void SpinMk2(ref DenseGenome genome, ref Phenotype phenotype, ref MultiMeasureJudgement judgement, ModelExecutionContext context, ITarget t, ReproductionRules rrules, DevelopmentRules drules, JudgementRules jrules, int generations, Action<int, GenomeJudgement> judgementFeedback = null)
+        {
+            var rand = context.Rand;
+
+            DenseGenome g = genome;
+            Phenotype p = phenotype;
+            MultiMeasureJudgement f = judgement;
+
+            if (p == null)
+            {
+                t.NextGeneration(rand, jrules);
+                p = g.Develop(context, drules);
+                f = MultiMeasureJudgement.Judge(g, p, jrules, t);
+            }
+
+            for (int i = 0; i < generations; i++)
+            {
+                // update Target
+                bool invalidatef = t.NextGeneration(rand, jrules);
+
+                if (invalidatef)
+                {
+                    f = MultiMeasureJudgement.Judge(g, p, jrules, t);
+                }
+
+                // generate and judge
+                DenseGenome g2 = g.Mutate(context, rrules);
+                Phenotype p2 = g2.Develop(context, drules);
+                MultiMeasureJudgement f2 = MultiMeasureJudgement.Judge(g2, p2, jrules, t);
+
+                // select
+                if (f2.CombinedFitness > f.CombinedFitness)
+                {
+                    g = g2;
+                    p = p2;
+                    f = f2;
+                }
+
+                if (judgementFeedback != null)
+                {
+                    judgementFeedback?.Invoke(i, new GenomeJudgement(g, p, f));
+                }
+            }
+
+            genome = g;
+            phenotype = p;
+            judgement = f;
+        }
+    }
+    
+    public class GenomeJudgement
+    {
+        public GenomeJudgement(DenseGenome genome, Phenotype phenotype, MultiMeasureJudgement judgement)
+        {
+            Genome = genome;
+            Phenotype = phenotype;
+            Judgement = judgement;
+        }
+
+        public DenseGenome Genome { get; }
+        public Phenotype Phenotype { get; }
+        public MultiMeasureJudgement Judgement { get; }
+    }
+
+    public class TerminalInfo
+    {
+        public TerminalInfo(DenseGenome genome, double fitness)
+        {
+            Genome = genome;
+            Fitness = fitness;
+        }
+
+        public DenseGenome Genome { get; }
+        public double Fitness { get; }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Extensions.cs b/M4MCode/M4M_MkI/M4M.Old/Extensions.cs
new file mode 100644
index 0000000..874bad8
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Extensions.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enogh)
+
+namespace M4M.Old
+{
+    public static class Extensions
+    {
+        public static Linear.Matrix<double> CreateGRS(string pattern, double g, double r, double s)
+        {
+            string lpattern = pattern.ToLower();
+            double[] spattern = pattern.Select(c => c == new string(c, 1).ToLower()[0] ? 1.0 : -1.0).ToArray();
+            int N = pattern.Length;
+
+            double cf(int y, int x)
+            {
+                char f = lpattern[x]; // from
+                char t = lpattern[y]; // to
+
+                double sign = spattern[x] * spattern[y];
+
+                if (t != f)
+                {
+                    // no correlation (different modules)
+                    return 0;
+                }
+                else
+                {
+                    if (lpattern.IndexOf(f) == x)
+                    {
+                        // f is the snowflake
+                        if (y == x)
+                        {
+                            // self-feed
+                            return s * sign;
+                        }
+                        else
+                        {
+                            // cross-feed
+                            return r * sign;
+                        }
+                    }
+                    else
+                    {
+                        // not on the beam
+                        return g * sign;
+                    }
+                }
+            }
+
+            return Linear.CreateMatrix.Dense<double>(N, N, cf);
+        }
+
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/FearExperiments.cs b/M4MCode/M4M_MkI/M4M.Old/FearExperiments.cs
new file mode 100644
index 0000000..566d9a3
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/FearExperiments.cs
@@ -0,0 +1,196 @@
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using static M4M.Misc;
+
+namespace M4M
+{
+    public static class FearExperiments
+    {
+        public static void Fear4VaryZ(TextWriter console, string topdir, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null)
+        {
+            string objectives = "Investigate a minimal experiment, with G constant, and a column constraint (size 4, solo/nosolo BEx), with a slight L2 regularisation" +
+                "" +
+                "The idea is that a slight L2 will force it to stay dense, as there is no benefit to hierarchy";
+            topdir = System.IO.Path.Combine(topdir, @"Fear4VaryZ\standard");
+
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+            
+            List<Action> expActs = new List<Action>();
+            
+            //foreach (double z in new[] { 0.0, 0.001, 0.01, 0.1 }) // proportion L2 (0.0 is no L2 (i.e. all L1))
+            foreach (double z in new[] { 0.0, 0.000000001, 0.00000001, 0.0000001 }) // proportion L2 (0.0 is no L2 (i.e. all L1))
+            {
+                foreach (bool solo in new[] { true, false })
+                {
+                    expActs.Add(() =>
+                    {
+                        string prefix = $"z{z}solo{(solo?"T":"F")}";
+
+                        try
+                        {
+                            FearRunners.RunFear(console, topdir: topdir, prefix: prefix, suffix: "", count: 1,
+                                targetPackage: Modular.ModularTargetPackage.HighJudge4x1, λ: 0.02, K: 1000, epochs: 5000, bNoiseTerm: 0.001,
+                                singleColumnMode: solo, popSize: 1, eliteCount: 1, hillclimberMode: true, regularisationFunction: JudgementRules.L1and2(z),
+                                disableTransientIo: false, densePopulationExperimentFactory: densePopulationExperimentFactory, densePopulationExperimentFeedbackConfigurator: densePopulationExperimentFeedbackConfigurator);
+                        }
+                        catch (Exception ex)
+                        {
+                            console.WriteLine($"Run {prefix} crashed");
+                            console.WriteLine(ex);
+                        }
+                    }
+                    );
+                }
+            }
+
+            Parallel.Invoke(expActs.ToArray());
+        }
+        
+        public static void Fear4x4(TextWriter console, string topdir, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null)
+        {
+            string objectives = "Investigate a 4x4MVG with Fear config, L1" +
+                "" +
+                "If this works, then I have to assume that all my work is wrong.";
+            topdir = System.IO.Path.Combine(topdir, @"Fear4x4\standard");
+
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+            
+            List<Action> expActs = new List<Action>();
+            
+            foreach (double z in new[] { 0.0 }) // proportion L2 (0.0 is no L2 (i.e. all L1))
+            {
+                foreach (bool solo in new[] { true, false })
+                {
+                    expActs.Add(() =>
+                    {
+                        string prefix = $"z{z}solo{(solo?"T":"F")}";
+
+                        try
+                        {
+                            FearRunners.RunFear(console, topdir: topdir, prefix: prefix, suffix: "", count: 1,
+                                targetPackage: Modular.ModularTargetPackage.Original4x4, λ: 0.02, K: 1000, epochs: 5000, bNoiseTerm: 0.001,
+                                singleColumnMode: solo, popSize: 1, eliteCount: 1, hillclimberMode: true, regularisationFunction: JudgementRules.L1and2(z),
+                                disableTransientIo: false, densePopulationExperimentFactory: densePopulationExperimentFactory, densePopulationExperimentFeedbackConfigurator: densePopulationExperimentFeedbackConfigurator);
+                        }
+                        catch (Exception ex)
+                        {
+                            console.WriteLine($"Run {prefix} crashed");
+                            console.WriteLine(ex);
+                        }
+                    }
+                    );
+                }
+            }
+
+            Parallel.Invoke(expActs.ToArray());
+        }
+    }
+    
+    public static class FearRunners
+    {
+        public static void RunFear(TextWriter console, string topdir, string prefix, string suffix, Modular.ModularTargetPackage targetPackage, int epochs, int K, double λ, double bNoiseTerm, bool singleColumnMode, IRegularisationFunction<IGenome> regularisationFunction, int count = 10, bool disableTransientIo = true, DensePopulationExperimentFeedbackFactory densePopulationExperimentFactory = null, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, int popSize = 1, int eliteCount = 1, bool hillclimberMode = true)
+        {
+            var nowTime = Misc.NowTime;
+            
+            List<DtmInfo> dtmInfos = new List<DtmInfo>();
+
+            topdir = topdir + "\\" + prefix + targetPackage.Name + suffix;
+
+            int N = targetPackage.TargetSize;
+
+            var fileStuff = FileStuff.CreateNow(topdir, "", prefix, true);
+            
+            RandomSource rand = new MersenneTwister(false);
+            RandomSource ioRand = new MersenneTwister(false);
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+            
+            // main loop
+
+            for (int i = 0; i < count; i++)
+            {
+                var drules = TypicalConfiguration.CreateStandardDevelopmentRules(
+                    squash: DevelopmentRules.TanhHalf
+                    );
+
+                var rrules = TypicalConfiguration.CreateStandardReproductionRules(
+                    gMutationMagnitude: 0.0,
+                    bMutationMagnitude: bNoiseTerm,
+                    bMutationProbability: 1.0,
+                    bMutationType: NoiseType.Uniform,
+                    exclusiveBMutation: true
+                    );
+
+                var jrules = TypicalConfiguration.CreateStandardJudgementRules(
+                    regularisationFactor: λ,
+                    regularisationFunction: regularisationFunction,
+                    noiseFactor: 0.0
+                    );
+
+                var config = TypicalConfiguration.CreateConfig(
+                    N: N,
+                    targets: targetPackage.Targets,
+                    targetCycler: Cyclers.Loop,
+                    epochs: epochs,
+                    generationsPerTargetPerEpoch: K,
+                    gResetProb: 1.0, // make sure g matches s all the time (note: HiJ works as expected, because it uses a coef to scale, it doesn't change the vector)
+                    gResetRange: new Range(-1.0, 1.0), // ignored by MatchTargetVector
+                    drules: drules,
+                    rrules: rrules,
+                    jrules: jrules
+                    );
+
+                var popConfig = TypicalConfiguration.CreatePopulationConfig(
+                    config: config,
+                    eliteCount: eliteCount,
+                    hillclimberMode: hillclimberMode,
+                    resetIndividualInitialStateOperation: PopulationResetOperations.MatchTargetVector
+                    );
+
+                var pop = TypicalConfiguration.CreatePopulation(
+                    context: context,
+                    populationSize: popSize,
+                    N: N,
+                    drules: drules,
+                    transMatMutator: singleColumnMode
+                        ? Mutators.SingleColumn(targetPackage.TargetSize, targetPackage.ModuleCount) // column constraint (only one column updated at a time)
+                        : Mutators.Columns2(targetPackage.TargetSize, targetPackage.ModuleCount) // column constraint
+                    );
+
+                var stuff = fileStuff.CreateSubStuff("r" + i);
+
+                densePopulationExperimentFactory = densePopulationExperimentFactory ?? CommonDensePopulationExperimentFeedback.SimpleCommonDensePopulationExperimentFeedback(console);
+                IDensePopulationExperimentFeedback denseFeedback = densePopulationExperimentFactory(ioRand, popConfig, null);
+                densePopulationExperimentFeedbackConfigurator?.Invoke(denseFeedback);
+
+                var res1 = RunPopulationExperiment(console, rand, stuff, pop, popConfig, denseFeedback.Feedback, disableTransientIo, false);
+            }
+        }
+
+        public static Population<DenseIndividual> RunPopulationExperiment(TextWriter console, RandomSource rand, FileStuff fileStuff, Population<DenseIndividual> initialPopulation, PopulationExperimentConfig<DenseIndividual> populationExperimentConfig, PopulationExperimentFeedback<DenseIndividual> feedback, bool disableTransientIO = true, bool disableAllIO = false)
+        {
+            // essentials
+            rand = rand ?? new MathNet.Numerics.Random.MersenneTwister();
+            var context = new ModelExecutionContext(rand);
+
+            var population = initialPopulation.Clone();
+
+            // go
+            PopulationExperimentRunners.RunExperiment<DenseIndividual>(console, "",
+                context: context,
+                population: population,
+                populationConfig: populationExperimentConfig,
+                stuff: fileStuff,
+                disableTransientIO: disableTransientIO,
+                disableAllIO: disableAllIO,
+                feedback: feedback);
+
+            return population;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/HModExperiments.cs b/M4MCode/M4M_MkI/M4M.Old/HModExperiments.cs
new file mode 100644
index 0000000..fc843da
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/HModExperiments.cs
@@ -0,0 +1,298 @@
+using M4M.Modular;
+using MathNet.Numerics.Random;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static M4M.Misc;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class HModExperiments
+    {
+        // for far too long has I been running things... now I shall prepare them, so that I can run them later at will (or rather, on the HPC cluster) 
+        public static PopulationExperiment<DenseIndividual> PrepareClassicM4M(string topdir, string name, ModularTargetPackage targetPackage, int epochs, double λ, int K, IRegularisationFunction<IGenome> regularisationFunction, bool exclusiveBMutation, bool appendTimestamp, int initialStateUpdates, bool binaryG, bool singleB = false, int T = 10)
+        {
+            topdir = System.IO.Path.Combine(topdir);
+            
+            int N = 16;
+
+            double gNoiseTerm = binaryG ? 2 : 0.1; //p22
+            NoiseType gNoiseType = binaryG ? NoiseType.Binary : NoiseType.Uniform;
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+            NoiseType bNoiseType = NoiseType.Uniform;
+
+            double bProb = 1.0 / 2; //p9/OLD
+            
+            var rand = new MersenneTwister(false);
+            var ioRand = new MersenneTwister(false);
+
+            var popConfig = HModRunners.PrepareHillClimberConfig(targetPackage: targetPackage, regularisationFunction: regularisationFunction, λ: λ, K: K, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bMutationProbability: bProb, exclusiveBMutation: exclusiveBMutation, gResetProb: 0.0, populationResetOperation: PopulationResetOperations.None, gUpdateCount: initialStateUpdates, gNoiseType : gNoiseType, bNoiseType: bNoiseType, T: T);
+            var population = singleB
+                ? HModRunners.CreateSingleCellHillClimberPopulation(rand, popConfig)
+                : HModRunners.CreateStandardHillClimberPopulation(rand, popConfig);
+
+            string subName = "classic" + name;
+            topdir = System.IO.Path.Combine(topdir, subName);
+            
+            var popExperiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(population, popConfig, topdir, "", name, false, appendTimestamp);
+            popExperiment.Save("starter", false);
+            PopulationExperimentRunners.WriteOutInitials(ioRand, popExperiment); // mosty for config.txt, so that a human/GradStudent can look it over
+
+            return popExperiment;
+        }
+        
+        public static PopulationExperiment<DenseIndividual> PreparePerfectM4M(string topdir, string name, ModularTargetPackage targetPackage, int epochs, double λ, int K, IRegularisationFunction<IGenome> regularisationFunction, bool appendTimestamp, bool singleB)
+        {
+            topdir = System.IO.Path.Combine(topdir);
+            
+            int N = targetPackage.TargetSize;
+
+            double gNoiseTerm = 0.0;
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+
+            double bProb = 1.0;
+            
+            var rand = new MersenneTwister(false);
+            var ioRand = new MersenneTwister(false);
+
+            var popConfig = HModRunners.PrepareHillClimberConfig(targetPackage: targetPackage, regularisationFunction: regularisationFunction, λ: λ, K: K, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bMutationProbability: bProb, exclusiveBMutation: true, gResetProb: 1.0, populationResetOperation: PopulationResetOperations.MatchTargetVector, gUpdateCount: 0);
+            var population = singleB
+                ? HModRunners.CreateSingleCellHillClimberPopulation(rand, popConfig)
+                : HModRunners.CreateStandardHillClimberPopulation(rand, popConfig);
+
+            string subName = "perfect" + name;
+            topdir = System.IO.Path.Combine(topdir, subName);
+            
+            var popExperiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(population, popConfig, topdir, "", name, false, appendTimestamp);
+            popExperiment.Save("starter", false);
+            PopulationExperimentRunners.WriteOutInitials(ioRand, popExperiment); // mosty for config.txt, so that a human/GradStudent can look it over
+
+            return popExperiment;
+        }
+        
+        public static PopulationExperiment<DenseIndividual> PreparePopulationM4M(string topdir, string name, Linear.Matrix<double> b0, ModularTargetPackage targetPackage, int epochs, double λ, int K, IRegularisationFunction<IGenome> regularisationFunction, bool exclusiveBMutation, bool appendTimestamp, int populationSize, int eliteCount, bool hillclimberMode, double bProb)
+        {
+            topdir = System.IO.Path.Combine(topdir);
+            
+            int N = 16;
+
+            double gNoiseTerm = 0.1; //p22
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+            
+            var rand = new MersenneTwister(false);
+            var ioRand = new MersenneTwister(false);
+            var context = new ModelExecutionContext(rand);
+
+            var popConfig = HModRunners.PreparePopulationConfig(targetPackage: targetPackage, regularisationFunction: regularisationFunction, λ: λ, K: K, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bMutationProbability: bProb, exclusiveBMutation: exclusiveBMutation, gResetProb: 0.0, populationResetOperation: PopulationResetOperations.None, eliteCount: eliteCount, hillclimberMode: hillclimberMode);
+            var population = TypicalConfiguration.CreatePopulation(context, populationSize, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules, b0: b0);
+
+            string subName = "population" + name;
+            topdir = System.IO.Path.Combine(topdir, subName);
+            
+            var popExperiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(population, popConfig, topdir, "", name, false, appendTimestamp);
+            popExperiment.Save("starter", false);
+            PopulationExperimentRunners.WriteOutInitials(ioRand, popExperiment); // mosty for config.txt, so that a human/GradStudent can look it over
+
+            return popExperiment;
+        }
+
+        public static PopulationExperiment<DenseIndividual> PreparePopulationM4M(string topdir, string name, ModularTargetPackage targetPackage, int epochs, double λ, int K, IRegularisationFunction<IGenome> regularisationFunction, bool exclusiveBMutation, bool appendTimestamp, int populationSize, int eliteCount, bool hillclimberMode, bool binaryG, bool columns)
+        {
+            topdir = System.IO.Path.Combine(topdir);
+            
+            int N = 16;
+            
+            double gNoiseTerm = binaryG ? 2 : 0.1; //p22
+            NoiseType gNoiseType = binaryG ? NoiseType.Binary : NoiseType.Uniform;
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+            NoiseType bNoiseType = NoiseType.Uniform;
+
+            double bProb = 1.0 / 2; //p9/OLD
+            
+            var rand = new MersenneTwister(false);
+            var ioRand = new MersenneTwister(false);
+
+            var popConfig = HModRunners.PreparePopulationConfig(targetPackage: targetPackage, regularisationFunction: regularisationFunction, λ: λ, K: K, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bMutationProbability: bProb, exclusiveBMutation: exclusiveBMutation, gResetProb: 0.0, populationResetOperation: PopulationResetOperations.None, eliteCount: eliteCount, hillclimberMode: hillclimberMode, gNoiseType : gNoiseType, bNoiseType: bNoiseType);
+            var population = columns
+                ? HModRunners.CreateColumnsPopulationPopulation(rand, popConfig, populationSize, targetPackage)
+                : HModRunners.CreateStandardPopulationPopulation(rand, popConfig, populationSize);
+
+            string subName = "population" + name;
+            topdir = System.IO.Path.Combine(topdir, subName);
+            
+            var popExperiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(population, popConfig, topdir, "", name, false, appendTimestamp);
+            popExperiment.Save("starter", false);
+            PopulationExperimentRunners.WriteOutInitials(ioRand, popExperiment); // mosty for config.txt, so that a human/GradStudent can look it over
+
+            return popExperiment;
+        }
+        
+        public static PopulationExperiment<DenseIndividual> PrepareFear(string topdir, string name, ModularTargetPackage targetPackage, int epochs, double λ, int K, IRegularisationFunction<IGenome> regularisationFunction, bool singleB, bool appendTimestamp)
+        {
+            topdir = System.IO.Path.Combine(topdir);
+            
+            int N = targetPackage.TargetSize;
+
+            double gNoiseTerm = 0.0;
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+
+            double bProb = 1.0; // little sense having anything else
+            
+            var rand = new MersenneTwister(false);
+            var ioRand = new MersenneTwister(false);
+
+            var popConfig = HModRunners.PrepareHillClimberConfig(targetPackage, regularisationFunction: regularisationFunction, λ: λ, K: K, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bMutationProbability: bProb, exclusiveBMutation: true, gResetProb: 1.0, populationResetOperation: PopulationResetOperations.MatchTargetVector, gUpdateCount: 0);
+            var population = singleB
+                ? HModRunners.CreateSingleColumnHillClimberPopulation(rand, popConfig, targetPackage)
+                : HModRunners.CreateColumnsHillClimberPopulation(rand, popConfig, targetPackage);
+
+            string subName = "fear" + name;
+            topdir = System.IO.Path.Combine(topdir, subName);
+            
+            var popExperiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(population, popConfig, topdir, "", name, false, appendTimestamp);
+            popExperiment.Save("starter", false);
+            PopulationExperimentRunners.WriteOutInitials(ioRand, popExperiment); // mosty for config.txt, so that a human/GradStudent can look it over
+
+            return popExperiment;
+        }
+    }
+
+    public static class HModRunners
+    {
+        public static Population<DenseIndividual> CreateStandardHillClimberPopulation(RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig)
+        {
+            return TypicalConfiguration.CreatePopulation(new ModelExecutionContext(rand), 1, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules);
+        }
+
+        public static Population<DenseIndividual> CreateStandardPopulationPopulation(RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig, int popSize)
+        {
+            return TypicalConfiguration.CreatePopulation(new ModelExecutionContext(rand), popSize, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules);
+        }
+
+        public static Population<DenseIndividual> CreateColumnsPopulationPopulation(RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig, int popSize, ModularTargetPackage targetPackage)
+        {
+            return TypicalConfiguration.CreatePopulation(new ModelExecutionContext(rand), popSize, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules,
+                transMatMutator: new Columns2TransMatMutator(targetPackage.TargetSize, targetPackage.ModuleCount));
+        }
+
+        public static Population<DenseIndividual> CreateSingleCellHillClimberPopulation(RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig)
+        {
+            return TypicalConfiguration.CreatePopulation(new ModelExecutionContext(rand), 1, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules,
+                transMatMutator: new SingleCellTransMatMutator());
+        }
+        
+        public static Population<DenseIndividual> CreateColumnsHillClimberPopulation(RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig, ModularTargetPackage targetPackage)
+        {
+            return TypicalConfiguration.CreatePopulation(new ModelExecutionContext(rand), 1, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules,
+                transMatMutator: new Columns2TransMatMutator(targetPackage.TargetSize, targetPackage.ModuleCount));
+        }
+        
+        public static Population<DenseIndividual> CreateSingleColumnHillClimberPopulation(RandomSource rand, PopulationExperimentConfig<DenseIndividual> popConfig, ModularTargetPackage targetPackage)
+        {
+            return TypicalConfiguration.CreatePopulation(new ModelExecutionContext(rand), 1, popConfig.ExperimentConfiguration.Size, popConfig.ExperimentConfiguration.DevelopmentRules,
+                transMatMutator: new SingleColumnTransMatMutator(targetPackage.TargetSize, targetPackage.ModuleCount));
+        }
+
+        public static PopulationExperimentConfig<DenseIndividual> PrepareHillClimberConfig(ModularTargetPackage targetPackage, int epochs, int K, double λ, IRegularisationFunction<IGenome> regularisationFunction, bool exclusiveBMutation, int T = 10, double gNoiseTerm = 0.1, double bMutationProbability = 1.0, Linear.Vector<double> g0 = null, bool gFixed = false, Linear.Matrix<double> b0 = null, int popSize = 1, Range gClamp = null, ISquash squash = null, double bNoiseTerm = 0.001, int gUpdateCount = 1, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, double gResetProb = 0, Range gResetRange = null, IPopulationResetOperation<DenseIndividual> populationResetOperation = null, NoiseType gNoiseType = NoiseType.Uniform, NoiseType bNoiseType = NoiseType.Uniform)
+        {
+            int N = targetPackage.TargetSize;
+
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(
+                squash: DevelopmentRules.TanhHalf,
+                timeSteps: T
+                );
+
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(
+                gMutationMagnitude: gNoiseTerm,
+                gMutationType: gNoiseType,
+                gUpdates: gUpdateCount,
+                gClamping: gClamp,
+                bMutationMagnitude: bNoiseTerm,
+                bMutationProbability: bMutationProbability,
+                bMutationType: bNoiseType,
+                exclusiveBMutation: exclusiveBMutation
+                );
+
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(
+                regularisationFactor: λ,
+                regularisationFunction: regularisationFunction,
+                noiseFactor: 0.0
+                );
+
+            var config = TypicalConfiguration.CreateConfig(
+                N: N,
+                targets: targetPackage.Targets,
+                targetCycler: Cyclers.Loop,
+                epochs: epochs,
+                generationsPerTargetPerEpoch: K,
+                gResetProb: gResetProb,
+                gResetRange: gResetRange ?? new Range(-1.0, 1.0),
+                drules: drules,
+                rrules: rrules,
+                jrules: jrules
+                );
+
+            var popConfig = TypicalConfiguration.CreatePopulationConfig(
+                config: config,
+                eliteCount: 1,
+                hillclimberMode: true,
+                resetIndividualInitialStateOperation: populationResetOperation ?? PopulationResetOperations.RandomIndependent
+                );
+
+            return popConfig;
+        }
+        
+        public static PopulationExperimentConfig<DenseIndividual> PreparePopulationConfig(ModularTargetPackage targetPackage, int epochs, int K, double λ, IRegularisationFunction<IGenome> regularisationFunction, bool exclusiveBMutation, int T = 10, double gNoiseTerm = 0.1, double bMutationProbability = 1.0, Linear.Vector<double> g0 = null, bool gFixed = false, Linear.Matrix<double> b0 = null, int popSize = 1, Range gClamp = null, ISquash squash = null, double bNoiseTerm = 0.001, int gUpdateCount = 1, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, double gResetProb = 0, Range gResetRange = null, IPopulationResetOperation<DenseIndividual> populationResetOperation = null, int eliteCount = 0, bool hillclimberMode = false, NoiseType gNoiseType = NoiseType.Uniform, NoiseType bNoiseType = NoiseType.Uniform)
+        {
+            int N = targetPackage.TargetSize;
+
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(
+                squash: DevelopmentRules.TanhHalf,
+                timeSteps: T
+                );
+
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(
+                gMutationMagnitude: gNoiseTerm,
+                gMutationType: gNoiseType,
+                gUpdates: gUpdateCount,
+                gClamping: gClamp,
+                bMutationMagnitude: bNoiseTerm,
+                bMutationProbability: bMutationProbability,
+                bMutationType: bNoiseType,
+                exclusiveBMutation: exclusiveBMutation
+                );
+
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(
+                regularisationFactor: λ,
+                regularisationFunction: regularisationFunction,
+                noiseFactor: 0.0
+                );
+
+            var config = TypicalConfiguration.CreateConfig(
+                N: N,
+                targets: targetPackage.Targets,
+                targetCycler: Cyclers.Loop,
+                epochs: epochs,
+                generationsPerTargetPerEpoch: K,
+                gResetProb: gResetProb,
+                gResetRange: gResetRange ?? new Range(-1.0, 1.0),
+                drules: drules,
+                rrules: rrules,
+                jrules: jrules
+                );
+
+            var popConfig = TypicalConfiguration.CreatePopulationConfig(
+                config: config,
+                eliteCount: eliteCount,
+                hillclimberMode: hillclimberMode,
+                resetIndividualInitialStateOperation: populationResetOperation ?? PopulationResetOperations.RandomIndependent
+                );
+
+            return popConfig;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/M4M.Old.csproj b/M4MCode/M4M_MkI/M4M.Old/M4M.Old.csproj
new file mode 100644
index 0000000..2c36bb1
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/M4M.Old.csproj
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>M4M.Old</RootNamespace>
+    <AssemblyName>M4M.Old</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="MathNet.Numerics">
+      <HintPath>..\..\..\..\Libraries\NotMine\MathNet.Numerics.dll</HintPath>
+    </Reference>
+    <Reference Include="MigraDoc.DocumentObjectModel-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\MigraDoc.DocumentObjectModel-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="MigraDoc.Rendering-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\MigraDoc.Rendering-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="MigraDoc.RtfRendering-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\MigraDoc.RtfRendering-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="NetState, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot, Version=2.0.0.0, Culture=neutral, PublicKeyToken=638079a8f0bd61e9, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.Core.2.0.0\lib\net45\OxyPlot.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot.Pdf, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c167629db8cf8ad9, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.Pdf.2.0.0\lib\net452\OxyPlot.Pdf.dll</HintPath>
+    </Reference>
+    <Reference Include="PdfSharp-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\PdfSharp-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="PdfSharp.Charting-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\PdfSharp.Charting-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Analysis.cs" />
+    <Compile Include="DefaultDensePopulationFeedback.cs" />
+    <Compile Include="Epistatics\Epistatics.cs" />
+    <Compile Include="Evolvability.cs" />
+    <Compile Include="Experiment.cs" />
+    <Compile Include="Extensions.cs" />
+    <Compile Include="FearExperiments.cs" />
+    <Compile Include="HModExperiments.cs" />
+    <Compile Include="MiscPlotting.cs" />
+    <Compile Include="OldPlotting.cs" />
+    <Compile Include="OldProgram.cs" />
+    <Compile Include="OneModule.cs" />
+    <Compile Include="OxyPlotting.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Reporting\Basics.cs" />
+    <Compile Include="Reporting\Figure.cs" />
+    <Compile Include="Reporting\Report.cs" />
+    <Compile Include="Reporting\Reporting.cs" />
+    <Compile Include="Sillyness.cs" />
+    <Compile Include="Topology.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\M4M.Model\M4M.Model.csproj">
+      <Project>{6a470149-d199-4b60-9f76-0f01fbd587e0}</Project>
+      <Name>M4M.Model</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\M4M.New\M4M.New.csproj">
+      <Project>{1e7d2bdd-30e8-48f8-9636-af93f451ec38}</Project>
+      <Name>M4M.New</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M.Old/MiscPlotting.cs b/M4MCode/M4M_MkI/M4M.Old/MiscPlotting.cs
new file mode 100644
index 0000000..112f512
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/MiscPlotting.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using Linear = MathNet.Numerics.LinearAlgebra; // reasonable quality API reference here: https://numerics.mathdotnet.com/api/MathNet.Numerics.LinearAlgebra/ (inline isn't good enogh)
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+
+namespace M4M
+{
+    public static class MiscPlotting
+    {
+        // plotting stuff
+        public static void PlotVector(string name, string filename, Linear.Vector<double> vector, double plotSize = 1, double? min = null, double? max = null)
+        {
+            double[,] darr = vector.ToColumnMatrix().Transpose().ToArray();
+            PlotHeatmap(name, filename, darr, false, false, plotSize: plotSize, min: min, max: max);
+        }
+
+        public static void PlotVectorFolded(string name, string filename, Linear.Vector<double> vector, string xTitle = "X", string yTitle = "Y", double plotSize = 1, double? min = null, double? max = null)
+        {
+            int foldCount = (int)Math.Round(Math.Sqrt(vector.Count));
+            Debug.Assert(foldCount * foldCount == vector.Count, $"Vector Length ({vector.Count}) must be a square number");
+
+            PlotVectorFolded(name, filename, vector, foldCount, xTitle, yTitle, plotSize, min: min, max: max);
+        }
+
+        public static void PlotVectorFolded(string name, string filename, Linear.Vector<double> vector, int foldCount, string xTitle = "X", string yTitle = "Y", double plotSize = 1, double? min = null, double? max = null)
+        {
+            Debug.Assert(vector.Count % foldCount == 0, $"Vector Length ({vector.Count}) must be an integer divisor of the foldCount ({foldCount}");
+
+            double[][] darr = Unroll(vector.ToArray(), foldCount);
+            PlotHeatmap(name, filename, darr, false, false, xTitle, yTitle, plotSize: plotSize, min: min, max: max);
+        }
+
+        public static void PlotMatrix(string name, string filename, Linear.Matrix<double> matrix, string yTitle = "Y", string xTitle = "X", bool labels = false, double plotSize = 1, double? min = null, double? max = null)
+        {
+            double[,] darr = matrix.Transpose().ToArray();
+            PlotHeatmap(name, filename, darr, false, true, xTitle, yTitle, labels, plotSize, min: min, max: max);
+        }
+
+        public static void PlotLines(string name, string filename, double[][] data, double x0 = 0, double dx = 1, string xTitle = "X", string yTitle = "Y", string[] seriesLabels = null, double plotSize = 1)
+        {
+            // plot
+            Plotting.OxyPlotModelShim plot = new Plotting.OxyPlotModelShim(name);
+
+            plot.LinearX(xTitle);
+            plot.LinearY(yTitle);
+
+            for (int i = 0; i < data.Length; i++)
+            {
+                double[] d = data[i];
+                string label = seriesLabels != null ? seriesLabels[i] : "";
+                plot.PlotLine(label, d, x0, dx);
+            }
+
+            plot.ExportPdf(filename, plotSize);
+        }
+
+        public static void PlotLines(string name, string filename, IDictionary<double, double>[] data, string xTitle = "X", string yTitle = "Y", string[] seriesLabels = null, double plotSize = 1)
+        {
+            // plot
+            Plotting.OxyPlotModelShim plot = new Plotting.OxyPlotModelShim(name);
+
+            plot.LinearX(xTitle);
+            plot.LinearY(yTitle);
+
+            for (int i = 0; i < data.Length; i++)
+            {
+                IDictionary<double, double> d = data[i];
+                string label = seriesLabels != null ? seriesLabels[i] : "";
+                plot.PlotLine(label, d.Select(kv => new Tuple<double, double>(kv.Key, kv.Value)).OrderBy(kv => kv.Item1));
+            }
+
+            plot.ExportPdf(filename, plotSize);
+        }
+
+        public static void PlotLine(string name, string filename, double[] data, double x0 = 0, double dx = 1, string xTitle = "X", string yTitle = "Y", double plotSize = 1)
+        {
+            // plot
+            Plotting.OxyPlotModelShim plot = new Plotting.OxyPlotModelShim(name);
+            
+            plot.LinearX(xTitle);
+            plot.LinearY(yTitle);
+
+            plot.PlotLine("", data, x0, dx);
+
+            plot.ExportPdf(filename, plotSize);
+        }
+
+        public static void PlotHeatmap(string name, string filename, double[][] darr, bool flipX = false, bool flipY = false, string xTitle = "X", string yTitle = "Y", bool labels = false, double plotSize = 1, double? min = null, double? max = null)
+        {
+            double[,] ndarr = new double[darr[0].Length, darr.Length];
+
+            for (int j = 0; j < darr.Length; j++)
+            {
+                for (int i = 0; i < darr[0].Length; i++)
+                {
+                    ndarr[i, j] = darr[j][i];
+                }
+            }
+
+            PlotHeatmap(name, filename, ndarr, flipX, flipY, xTitle, yTitle, labels, plotSize, min: min, max: max);
+        }
+
+        public static void PlotHeatmap(string name, string filename, double[,] darr, bool flipX = false, bool flipY = false, string xTitle = "X", string yTitle = "Y", bool labels = false, double plotSize = 1, double? min = null, double? max = null)
+        {
+            // pad
+            bool padX = false;
+            if (darr.GetLength(0) <= 1)
+            {
+                padX = true;
+
+                var ndarr = new double[2, darr.GetLength(1)];
+                for (int i = 0; i < darr.GetLength(1); i++)
+                {
+                    ndarr[0, i] = darr[0, i];
+                    ndarr[1, i] = darr[0, i];
+                }
+                darr = ndarr;
+            }
+            bool padY = false;
+            if (darr.GetLength(1) <= 1)
+            {
+                padY = true;
+
+                var ndarr = new double[darr.GetLength(0), 2];
+                for (int i = 0; i < darr.GetLength(0); i++)
+                {
+                    ndarr[i, 0] = darr[i, 0];
+                    ndarr[i, 1] = darr[i, 0];
+                }
+                darr = ndarr;
+            }
+
+            // plot
+            Plotting.OxyPlotModelShim plot = new Plotting.OxyPlotModelShim(name);
+
+            plot.LinearX(label : xTitle);
+            plot.LinearY(label : yTitle, flip: true);
+            plot.ColoursGrey(min : min, max : max);
+            double x0 = padX ? -1.0 : 0.0;
+            double x1 = padX ? 1.0 : darr.GetLength(0) - 1.0;
+            double y0 = padY ? -1.0 : 0.0;
+            double y1 = padY ? 1.0 : darr.GetLength(1) - 1.0;
+            var hmap = plot.PlotHeatmap("", darr, x0, x1, y0, y1);
+            plot.Sillyify(Clamp(darr.GetLength(0), 1, 10), "x");
+            plot.Sillyify(Clamp(darr.GetLength(1), 1, 10), "y");
+            
+            if (labels)
+                hmap.LabelFontSize = 0.2;
+
+
+            plot.ExportPdf(filename, plotSize);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/OldPlotting.cs b/M4MCode/M4M_MkI/M4M.Old/OldPlotting.cs
new file mode 100644
index 0000000..d7d22c9
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/OldPlotting.cs
@@ -0,0 +1,240 @@
+using M4M;
+using System.Linq;
+
+namespace Plotting
+{
+    public class PdfExportReport
+    {
+        public PdfExportReport(string filename, double exportedWidth, double exportedHeight)
+        {
+            Filename = filename;
+            ExportedWidth = exportedWidth;
+            ExportedHeight = exportedHeight;
+        }
+
+        public string Filename { get; }
+        public double ExportedWidth { get; }
+        public double ExportedHeight { get; }
+    }
+    
+    public static class OldOxyPlottingExtentions
+    {
+        public static OxyPlot.Axes.Axis GetAxis(this OxyPlot.PlotModel model, string key)
+        {
+            // surly there is a method/indexer for this?
+            return model.Axes.First(a => a.Key == key);
+        }
+    }
+
+    public static class OldOxyPlotting
+    {
+        public static System.Diagnostics.Process ViewPdf(string name)
+        {
+            if (!name.EndsWith(".pdf"))
+                name += ".pdf";
+
+            try
+            {
+                return System.Diagnostics.Process.Start(name);
+            }
+            catch
+            {
+                return null; // high quality code
+            }
+        }
+
+        public static void ExportToPdf(OxyPlot.PlotModel model, string ofname, bool bigger = true, bool noHalf = false, bool forceAspect = false, PostProcessPlot postProcessor = null)
+        {
+            ExportToPdf(model, ofname, 1.0, bigger, forceAspect, postProcessor);
+            if (!noHalf)
+                ExportToPdf(model, ofname + "_half", 0.5, bigger, forceAspect, postProcessor);
+        }
+        
+        public static void DummyExport(OxyPlot.PlotModel model)
+        {
+            OxyPlot.Pdf.PdfExporter exporter = new OxyPlot.Pdf.PdfExporter();
+            double Width = 297 / 25.4 * 72 * 1.0; // A4
+            double Height = 210 / 25.4 * 72 * 1.0;
+
+            exporter.Width = Width;
+            exporter.Height = Height;
+
+            model.InvalidatePlot(true);
+            
+            using (var fs = new System.IO.MemoryStream())
+            {
+                exporter.Export(model, fs);
+            }
+        }
+
+        public static PdfExportReport ExportToPdf(OxyPlot.PlotModel model, string ofname, double size, bool bigger, bool forceAspect, PostProcessPlot postProcessor)
+        {
+            double width = 297 / 25.4 * 72 * size; // A4
+            double height = 210 / 25.4 * 72 * size;
+
+            return ExportToPdf(model, ofname, width, height, bigger, forceAspect, postProcessor);
+        }
+
+        public static PdfExportReport ExportToPdf(OxyPlot.PlotModel model, string ofname, double width, double height, bool bigger, bool forceAspect, PostProcessPlot postProcessor)
+        {
+            double fc = 1.2;
+            double prc = 2.0;
+            if (model.Axes.Count > 2)
+                prc = 0.0;
+
+            model.Padding = new OxyPlot.OxyThickness(0.0, 0.0, model.Padding.Right * prc, 0.0); // Yan says there shouldn't be any whitespace
+            model.TitleFontSize *= fc;
+            model.DefaultFontSize *= fc;
+
+            if (bigger)
+            {
+                foreach (var s in model.Series)
+                {
+                    if (s is OxyPlot.Series.LineSeries)
+                        ((OxyPlot.Series.LineSeries)s).StrokeThickness *= 2.0;
+                }
+                model.LegendFontSize *= fc;
+            }
+
+            ofname = ofname.EndsWith(".pdf") ? ofname : ofname + ".pdf";
+            
+            OxyPlot.Pdf.PdfExporter exporter = new OxyPlot.Pdf.PdfExporter();
+
+            exporter.Width = width;
+            exporter.Height = height;   
+
+            model.InvalidatePlot(true);
+
+            void doPlot()
+            {
+                using (var fs = new System.IO.FileStream(ofname, System.IO.FileMode.Create))
+                {
+                    exporter.Export(model, fs);
+                }
+            }
+
+            void doForceAspect()
+            {
+                if (model.PlotArea.Width > model.PlotArea.Height)
+                {
+                    width = width - model.PlotArea.Width + model.PlotArea.Height;
+                }
+                else
+                {
+                    height = height + model.PlotArea.Width - model.PlotArea.Height;
+                }
+
+                exporter.Width = width;
+                exporter.Height = height;
+            }
+
+            doPlot();
+
+            if (forceAspect)
+            {
+                doForceAspect();
+                doPlot();
+            }
+
+            if (postProcessor != null)
+            {
+                postProcessor(model);
+                doPlot();
+                
+                if (forceAspect)
+                {
+                    doForceAspect();
+                    doPlot();
+                }
+            }
+
+            return new PdfExportReport(ofname, width, height);
+        }
+
+        public static OxyPlot.PlotModel RecolourLineSeries(OxyPlot.PlotModel model, OxyPlot.OxyColor from, OxyPlot.OxyColor to)
+        {
+            OxyPlot.Series.LineSeries[] ss = model.Series.Where(s => s is OxyPlot.Series.LineSeries).Select(s => s as OxyPlot.Series.LineSeries).ToArray();
+
+            for (int i = 0; i < ss.Length; i++)
+            {
+                ss[i].Color = OxyPlot.OxyColor.Interpolate(from, to, (double)i / (ss.Length > 1 ? ss.Length - 1 : 1));
+            }
+
+            return model;
+        }
+
+        public static OxyPlot.PlotModel LinearAxes(string title, string xlabel, string ylabel, string ylabel2 = null)
+        {
+            OxyPlot.PlotModel model = new OxyPlot.PlotModel() { Title = title };
+
+            model.Axes.Add(new OxyPlot.Axes.LinearAxis() { Key = "x", Title = xlabel, Position = OxyPlot.Axes.AxisPosition.Bottom, StartPosition = 0, EndPosition = 1 });
+            model.Axes.Add(new OxyPlot.Axes.LinearAxis() { Key = "y", Title = ylabel, Position = OxyPlot.Axes.AxisPosition.Left, StartPosition = 0, EndPosition = 1 });
+            if (ylabel2 != null)
+                model.Axes.Add(new OxyPlot.Axes.LinearAxis() { Key = "y2", Title = ylabel2, Position = OxyPlot.Axes.AxisPosition.Right, StartPosition = 0, EndPosition = 1 });
+            
+            return model;
+        }
+        
+        public static OxyPlot.PlotModel PlotFreeLines(OxyPlot.PlotModel model, double strokeWidth, System.Collections.Generic.IEnumerable<System.Tuple<string, System.Collections.Generic.IEnumerable<System.Tuple<double, double>>>> values, string yAxisKey = "y")
+        {
+            foreach (System.Tuple<string, System.Collections.Generic.IEnumerable<System.Tuple<double, double>>> vs in values)
+            {
+                string sname = vs.Item1;
+                System.Collections.Generic.IEnumerable<System.Tuple<double, double>> vals = vs.Item2;
+
+                //OxyPlot.Series.StairStepSeries series = new OxyPlot.Series.StairStepSeries();
+                OxyPlot.Series.LineSeries series = new OxyPlot.Series.LineSeries();
+                series.Title = sname;
+
+                foreach (System.Tuple<double, double> d in vals)
+                {
+                    series.Points.Add(d == null ? OxyPlot.DataPoint.Undefined : new OxyPlot.DataPoint(d.Item1, d.Item2));
+                }
+
+                series.StrokeThickness = strokeWidth;
+                series.YAxisKey = yAxisKey;
+
+                model.Series.Add(series);
+            }
+
+            return model;
+        }
+        
+        public static OxyPlot.PlotModel PlotFunc(OxyPlot.PlotModel model, double strokeWidth, string label, System.Func<double, double> func, double x0, double x1, double n, string yAxisKey = "y")
+        {
+            string sname = label;
+            
+            OxyPlot.Series.FunctionSeries series = new OxyPlot.Series.FunctionSeries(func, x0, x1, n);
+            series.Title = sname;
+
+            series.StrokeThickness = strokeWidth;
+            series.YAxisKey = yAxisKey;
+
+            model.Series.Add(series);
+
+            return model;
+        }
+        
+        public static OxyPlot.PlotModel PlotLine(OxyPlot.PlotModel model, double strokeWidth, string label, System.Collections.Generic.IEnumerable<System.Tuple<double, double>> values, string yAxisKey = "y")
+        {
+            string sname = label;
+            System.Collections.Generic.IEnumerable<System.Tuple<double, double>> vals = values;
+
+            //OxyPlot.Series.StairStepSeries series = new OxyPlot.Series.StairStepSeries();
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.LineSeries();
+            series.Title = sname;
+
+            foreach (System.Tuple<double, double> d in vals)
+            {
+                series.Points.Add(d == null ? OxyPlot.DataPoint.Undefined : new OxyPlot.DataPoint(d.Item1, d.Item2));
+            }
+
+            series.StrokeThickness = strokeWidth;
+            series.YAxisKey = yAxisKey;
+
+            model.Series.Add(series);
+
+            return model;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/OldProgram.cs b/M4MCode/M4M_MkI/M4M.Old/OldProgram.cs
new file mode 100644
index 0000000..001ef5c
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/OldProgram.cs
@@ -0,0 +1,2947 @@
+using OxyPlot;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+using RandomSource = MathNet.Numerics.Random.RandomSource;
+using static M4M.Misc;
+using static M4M.MiscPlotting;
+using static M4M.Analysis;
+using OxyPlot.Axes;
+using OxyPlot.Series;
+using M4M.Modular;
+
+namespace M4M.Old
+{
+    public class Program
+    {
+        // should really be generic in the string term, and take a ToString func
+        public static void WriteStuff(System.IO.StreamWriter writer, IDictionary<Tuple<double, double>, string> stuff, string lTitle, string tTitle)
+        {
+            writer.Write(lTitle + "\\" + tTitle);
+            foreach (var t in stuff.Keys.Select(k => k.Item2).Distinct().OrderBy(k => k))
+            {
+                writer.Write("\t");
+                writer.Write(t);
+            }
+
+            foreach (var l in stuff.Keys.Select(k => k.Item1).Distinct().OrderBy(k => k))
+            {
+                writer.WriteLine();
+                writer.Write(l);
+                foreach (var t in stuff.Keys.Select(k => k.Item2).Distinct().OrderBy(k => k))
+                {
+                    writer.Write("\t");
+                    writer.Write(stuff[new Tuple<double, double>(l, t)].ToString());
+                }
+            }
+        }
+
+        private static void ConfigureCulture()
+        {
+            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
+        }
+
+        private static void ConfigureMathNet(bool quiet)
+        {
+            MathNet.Numerics.Control.UseSingleThread();
+            //bool t = MathNet.Numerics.Control.TryUseNativeCUDA();
+            //if (t)
+            //    Console.WriteLine("CUDA");
+
+            if (MathNet.Numerics.Control.TryUseNativeOpenBLAS() && !quiet)
+                Console.WriteLine("OpenBLAS");
+        }
+
+        private static void PalinHCTest()
+        {
+            Console.WriteLine("# HC");
+            MatrixPool.EnableMatrixPools = true;
+
+            var iorand = new MathNet.Numerics.Random.MersenneTwister(0, false);
+            var rand = new MathNet.Numerics.Random.MersenneTwister(0, false);
+            var context = new ModelExecutionContext(rand);
+
+            var exp = HModExperiments.PrepareClassicM4M("C:/M4MTest/hc", "hc", Modular.ModularTargetPackage.Original4x4, 1000, 0.2, 20000, JudgementRules.L1Equivalent, false, false, 1, false, false, 10);
+            var feedback = new CommonDensePopulationExperimentFeedback(System.Console.Out, iorand, exp.PopulationConfig, false, false, 1, 1, null, 1000, 100000, 10000000, 0, 1);
+            exp.Population.PopulationParallelism = 1;
+
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+            sw.Start();
+            PopulationExperimentRunners.RunEpochs(System.Console.Out, "", 1, context, exp, null);
+            sw.Stop();
+
+            var matPool = context.GetMatrixPool(16, 16);
+            Console.WriteLine("CacheHits:   " + matPool.CacheHits);
+            Console.WriteLine("CacheMisses: " + matPool.CacheMisses);
+            Console.WriteLine("CacheCaps:   " + matPool.CacheCaps);
+
+            Console.WriteLine("HC" + ": " + sw.ElapsedMilliseconds + "ms");
+            Console.WriteLine();
+        }
+
+        private static void ParallelPopulationTest(int tasks)
+        {
+            Console.WriteLine("# P" + tasks);
+
+            var iorand = new MathNet.Numerics.Random.MersenneTwister(0, false);
+            var rand = new MathNet.Numerics.Random.MersenneTwister(0, false);
+            var context = new ModelExecutionContext(rand);
+
+            var exp = HModExperiments.PreparePopulationM4M("C:/M4MTest/p" + tasks, "p" + tasks, Modular.ModularTargetPackage.Original4x4, 1000, 0.2, 20000, JudgementRules.L1Equivalent, false, false, 16, 1, true, false, false);
+            var feedback = new CommonDensePopulationExperimentFeedback(System.Console.Out, iorand, exp.PopulationConfig, false, false, 1, 1, null, 1000, 100000, 10000000, 0, 1);
+            exp.Population.PopulationParallelism = tasks;
+
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+            sw.Start();
+            PopulationExperimentRunners.RunEpochs(System.Console.Out, "", 1, context, exp, null);
+            sw.Stop();
+
+            var matPool = context.GetMatrixPool(16, 16);
+            Console.WriteLine("CacheHits:   " + matPool.CacheHits);
+            Console.WriteLine("CacheMisses: " + matPool.CacheMisses);
+            Console.WriteLine("CacheCaps:   " + matPool.CacheCaps);
+
+            Console.WriteLine("P" + tasks + ": " + sw.ElapsedMilliseconds + "ms");
+            Console.WriteLine();
+        }
+
+        public static void Main(string[] args)
+        {
+#if false
+            while (true)
+            {
+                PalinHCTest();
+                ParallelPopulationTest(1);
+                ParallelPopulationTest(2);
+                //ParallelPopulationTest(4);
+                //ParallelPopulationTest(8);
+            }
+            Console.ReadKey(true);
+            return;
+#endif
+
+            //var exp = PopulationExperiment<DenseIndividual>.Load(@"C:\M4MResults\cbbnk\mmsolinstep4x4_long\medk1\epoch10000save.dat");
+            //exp.RunEpoch(new MathNet.Numerics.Random.MersenneTwister(false), null);
+
+            //return;
+
+            bool quiet = args.Contains("quiet");
+
+            if (!quiet)
+                Console.WriteLine("~~ M4M Framework Running ~~");
+            ConfigureCulture();
+            ConfigureMathNet(quiet);
+
+            Cli cli = Cli.PrepareDefaultCli(
+                plotExporter: new PdfPlotExporter()
+                );
+            CliPrompt cliPrompt = new CliPrompt(cli);
+            cliPrompt.Run(System.Console.Out, args);
+
+            //TimeGeneration();
+            //TimeTanh();
+            //StateTesting();
+            //Console.ReadKey();
+
+            //HModStuff();
+            //CbbnkStuff();
+            //FearStuff();
+        }
+
+        private static void TimeTanh()
+        {
+            int n = 100000;
+            int rr = 1000;
+            double[] x = new double[n];
+            double[] y = new double[n];
+
+            Random rnd = new Random();
+            for (int i = 0; i < n; i++)
+                x[i] = rnd.NextDouble() + 0.2;
+
+            // warmup
+            for (int i = 0; i < n; i++)
+                y[i] = Math.Tanh(x[i]);
+
+            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+            sw.Restart();
+
+            for (int r = 0; r < rr; r++)
+            {
+                for (int i = 0; i < n; i++)
+                    y[i] = Math.Tanh(x[i]);
+            }
+
+            sw.Start();
+            Console.WriteLine(sw.ElapsedMilliseconds);
+            Console.WriteLine(sw.ElapsedMilliseconds / (double)(n * rr));
+            Console.WriteLine(y.Sum());
+        }
+
+        private static void TimeGeneration()
+        {
+            int rr = 100;
+            int n = 20000 / rr;
+
+            var rand = new MathNet.Numerics.Random.MersenneTwister(false);
+            var context = new ModelExecutionContext(rand);
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(DevelopmentRules.TanhHalf);
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(0.1, 0.1 / (15 * 15), 1.0, false);
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(0.1, JudgementRules.L1Equivalent, 0.0);
+            var s = ModularTargetPackage.Original4x4.Targets[0];
+            s.NextGeneration(rand, jrules);
+
+            // warmup
+            DenseIndividual di = DenseIndividual.Develop(DenseGenome.CreateDefaultGenomeDense(16, customDevelopmentStep: new DefaultDevelopmentStep(16, 0.0)), context, drules, false);
+            IndividualJudgement<DenseIndividual> dij = new IndividualJudgement<DenseIndividual>(di, MultiMeasureJudgement.Judge(di.Genome, di.Phenotype, jrules, s));
+
+            for (int i = 0; i < n; i++)
+            {
+                s.NextGeneration(rand, jrules);
+
+                var di2 = di.Mutate(context, drules, rrules);
+                var dij2 = new IndividualJudgement<DenseIndividual>(di2, MultiMeasureJudgement.Judge(di2.Genome, di2.Phenotype, jrules, s));
+
+                if (dij2.Judgement.CombinedFitness > dij.Judgement.CombinedFitness)
+                {
+                    di = di2;
+                    dij = dij2;
+                }
+            }
+
+            // go
+
+            for (int q = 0; q < 10; q++)
+            {
+                // re-prep
+                di = DenseIndividual.Develop(DenseGenome.CreateDefaultGenomeDense(16, customDevelopmentStep: new DefaultDevelopmentStep(16, 0.0)), context, drules, false);
+                dij = new IndividualJudgement<DenseIndividual>(di, di.Judge(jrules, s));
+
+                Population<DenseIndividual> pop = new Population<DenseIndividual>(di, 1);
+
+                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+                sw.Restart();
+
+                for (int r = 0; r < rr; r++)
+                {
+                    for (int i = 0; i < n; i++)
+                    {
+                        //s.NextGeneration(rand, jrules);
+
+                        pop.CyclePopulation(context, jrules, s, rrules, drules, null, 1, true, null);
+
+                        // simulate t16 (i.e. development)
+                        //double x = 0.99;
+                        //for (int j = 0; j < 10 * 16; j++)
+                        //{
+                        //    double y = Math.Tanh(x);
+                        //    for (int k = 0; k < 16; k++)
+                        //        x *= x;
+                        //}
+
+                        //var p2 = di.Genome.Develop(rand, drules);
+
+                        //var j = di.Judge(jrules, s);
+                        //var g2 = di.Genome.Mutate(rand, rrules);
+                        //var p2 = g2.Develop(rand, drules);
+                        //var j2 = MultiMeasureJudgement.Judge(g2, p2, jrules, s);
+
+                        //dij = new IndividualJudgement<DenseIndividual>(di, di.Judge(jrules, s));
+                        //var di2 = di.Mutate(rand, drules, rrules);
+                        //var dij2 = new IndividualJudgement<DenseIndividual>(di2, di2.Judge(jrules, s));
+
+                        //if (dij2.Judgement.CombinedFitness > dij.Judgement.CombinedFitness)
+                        //{
+                        //    di = di2;
+                        //    dij = dij2;
+                        //}
+                    }
+
+                    //new RandomIndependantPopulationResetOperation().Reset(rand, pop, new Misc.Range(-1, 1), drules, s);
+                }
+
+                sw.Start();
+                Console.WriteLine(sw.ElapsedMilliseconds + "ms");
+                Console.WriteLine(sw.ElapsedMilliseconds / (double)(n * rr) + "ms/gen");
+                Console.WriteLine(1000000 * sw.ElapsedMilliseconds / (double)(n * rr) + "ns/gen");
+                Console.WriteLine(di.Genome.InitialState[0]);
+            }
+        }
+
+        public static void StateTesting()
+        {
+            DenseGenome dg = DenseGenome.CreateDefaultGenome(16, new Columns2TransMatMutator(16, 4), null, new DefaultDevelopmentStep(16, 0.0));
+            var popconfig = PrepareConfig(Epistatics.BBNKTargetPackages.CbbnkStep4x4Variable(0.5, 1.0, 0.1), 5000, 1000, 0.2);
+            var context = new ModelExecutionContext(new MathNet.Numerics.Random.MersenneTwister(false));
+            var pop = new Population<DenseIndividual>(DenseIndividual.Develop(DenseGenome.CreateDefaultGenome(16), context, popconfig.ExperimentConfiguration.DevelopmentRules, false), 1);
+            var extracts = pop.ExtractAll().ToList();
+
+            using (var ms = new System.IO.MemoryStream())
+            {
+                State.GraphSerialisation.Write(dg, ms);
+                State.GraphSerialisation.Write(popconfig, ms);
+                State.GraphSerialisation.Write(extracts, ms);
+                State.GraphSerialisation.Write(pop, ms);
+
+                ms.Position = 0;
+
+                var dg2 = State.GraphSerialisation.Read<DenseGenome>(ms);
+                var popconfig2 = State.GraphSerialisation.Read<PopulationExperimentConfig<DenseIndividual>>(ms);
+                var meh = State.GraphSerialisation.Read<IReadOnlyList<DenseIndividual>>(ms);
+                var pop2 = State.GraphSerialisation.Read<Population<DenseIndividual>>(ms);
+
+                Console.ReadKey(true);
+            }
+        }
+
+        public static PopulationExperimentConfig<DenseIndividual> PrepareConfig(Epistatics.EpistaticTargetPackage targetPackage, int epochs, int K, double λ, double gNoiseTerm = 0.6, double bNoiseTerm = 0.00001, int eliteCount = 1, bool hillclimberMode = true, IRegularisationFunction<IGenome> regularisationFunction = null, double bMutationProbability = 1.0, bool exclusiveBMutation = false, bool binaryG = false, double gResetProb = 1.0)
+        {
+            int N = targetPackage.TargetSize;
+
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(
+                squash: DevelopmentRules.TanhHalf
+                );
+
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(
+                gMutationMagnitude: gNoiseTerm,
+                gMutationType: binaryG ? NoiseType.Binary : NoiseType.Gaussian,
+                bMutationMagnitude: bNoiseTerm,
+                bMutationProbability: bMutationProbability,
+                bMutationType: NoiseType.Uniform,
+                exclusiveBMutation: exclusiveBMutation
+                );
+
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(
+                regularisationFactor: λ,
+                regularisationFunction: regularisationFunction,
+                noiseFactor: 0.0
+                );
+
+            var config = TypicalConfiguration.CreateConfig(
+                N: N,
+                targets: targetPackage.Targets,
+                targetCycler: Cyclers.Loop,
+                epochs: epochs,
+                generationsPerTargetPerEpoch: K,
+                gResetProb: gResetProb,
+                gResetRange: new Range(-1.0, 1.0),
+                drules: drules,
+                rrules: rrules,
+                jrules: jrules
+                );
+
+            var popConfig = TypicalConfiguration.CreatePopulationConfig(
+                config: config,
+                eliteCount: eliteCount,
+                hillclimberMode: hillclimberMode,
+                resetIndividualInitialStateOperation: gResetProb == 0 ? PopulationResetOperations.None : (binaryG ? PopulationResetOperations.RandomBinaryIndependent : PopulationResetOperations.RandomIndependent)
+                );
+
+            return popConfig;
+        }
+
+        public static void HModStuff()
+        {
+            //HModExperiments.M4MPerfectG(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 2500, plotPeriod: 1000, tracePeriod: 10000), null);
+            //HModExperiments.ClassicM4M(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 2500, plotPeriod: 1000, tracePeriod: 10000), null);
+        }
+
+        public static void CbbnkStuff()
+        {
+            //CbbnkExperiments.MMSOLinStep(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000));
+            //CbbnkExperiments.MMSOLinStep3x2(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000));
+            //CbbnkExperiments.MMSOLinStep2x3(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000));
+            //CbbnkExperiments.MMSOLinStep3x3(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000), IsWatchPopConfigor());
+            //CbbnkExperiments.MMSOLinStep4x4(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000), IsWatchPopConfigor
+            //CbbnkExperiments.MMSOLinStep4x4Long(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 2500, plotPeriod: 1000, tracePeriod: 10000), IsWatchPopConfigor());
+
+            // continuations
+            //var oldDir = @"C:\M4MExperiments\MMSOLinStep4x4\shortblock1\q0.8a2ω0.8λ1CbbnkStep4x4P0.8_0.5_0.1_3MVG_20180830T112908\r0";
+            //CbbnkExperiments.MMSOLinStep4x4Continue(oldDir, 1.0, DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 2000, tracePeriod: 2000), IsWatchPopConfigor());
+
+            var zeroDir = @"C:\M4MExperiments\ZERO\MMSOLinStep4x4Long\block2\M_G0.65M_B1.5E-05CbbnkStep4x4P0.8_0.5_0.1_3MVG_20180903T233033\r0";
+            var zeroExperiment = PopulationExperiment<DenseIndividual>.Load(zeroDir + @"\epoch0savestart.dat");
+            zeroExperiment = PopulationExperimentRunners.PrepareExperiment<DenseIndividual>(zeroExperiment.Population, zeroExperiment.PopulationConfig, zeroDir + "rerun");
+            var rand = new MathNet.Numerics.Random.MersenneTwister(0, false);
+            var ioRand = new MathNet.Numerics.Random.MersenneTwister(0, false);
+            var context = new ModelExecutionContext(rand);
+            var feedback = DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 2500, plotPeriod: 1000, tracePeriod: 10000).Invoke(ioRand, zeroExperiment.PopulationConfig, null);
+            PopulationExperimentRunners.WriteOutInitials(rand, zeroExperiment);
+            PopulationExperimentRunners.RunEpochs(System.Console.Out, "", zeroExperiment.PopulationConfig.ExperimentConfiguration.Epochs, context, zeroExperiment, feedback.Feedback);
+        }
+
+        public static void FearStuff()
+        {
+            //FearExperiments.Fear4VaryZ(@"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000), IsWatchPopConfigor());
+            FearExperiments.Fear4x4(System.Console.Out, @"C:\M4MExperiments\", DefaultDensePopulationExperimentFeedback.SimpleDefaultDensePopulationExperimentFeedback(savePeriod: 500, plotPeriod: 1000, tracePeriod: 2000));
+        }
+
+        static void OldMain(string[] args)
+        {
+            throw new Exception("Should not be using OldMain");
+
+            //var g = Analysis.LoadGenome(@"C:\M4MExperiments\MMSOLinVaryHighQAB\uncruelshort7\q0.5a5CbbnkCruel2x2P1_0.5_2\EpistaticPopTestBBNKCruelCbbnkCruel2x2P1_0.5_2_λ0.2EpiFalsemG0.6mB1E-05E5000K1000T10PF.200G1_20180818T005656\genome200.dat");
+
+
+            //return;
+
+            //ConfigureMathNet(false);
+
+            //TestExperiment();
+            //L1SpikyTargetExperiments();
+            //L1SpikyTargetCountingExperiments();
+            //DiffModExperiment();
+
+            //EvolvabilityTest();
+            //LeaderTest();
+
+            //IdentityExperiment();
+            //for(int i=0;i<4;i++)
+            //    ColumnExperiment();
+
+            //HorrifyingStuff();
+
+            //ToplogyExperiments();
+
+            //TestExperiment_SoloTarget(); // for MA
+
+            //CutoffTargetExperiment();
+
+            //DistanceFitnessExperiments();
+
+            //HierarchyForProfitExperiments(); // come back to this
+
+            //FourTwoFourWheelers();
+
+            //Ands();
+
+            //Paushes();
+
+            //SharingExperiments();
+
+            //PopulationTests();
+            //Fear(FileStuff.CreateNow("fear", "", "")); // as expected...
+
+            //Ev2Tests();
+
+            //MorePopulationStuff();
+
+            //GradientMeasuring();
+
+            //Ev2Tests();
+
+            //WeirdStuff();
+
+            //int N = 16;
+
+            // LNo and L1 are fine, L2 (overfits)
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.ConstantEquivalent, λ: 0, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            // these are all just slower versions of the above, all going to overfit
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38, K: 5_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38, K: 10_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38, K: 40_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            // doesn't seem to go wrong...
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 0.38, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            // ... tighten λ: nothing
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38.0/N, K: 20_000, epochs: 1000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            // ... run it long: nothing
+            //PopNotExperiment(targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 0.38, K: 20_000, epochs: 1000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            //SupportingFigures();
+
+            // nice runs
+            //PopNotExperiment(customPrefix: "L1 λ=0.00 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.00, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            // need a long one
+            //PopNotExperiment(customPrefix: "L1 λ=0.8 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.8, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            /*
+            var drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            var expected = ModularTargetPackage.Targets4x4Total.ToDictionary(t => t, t => 1.0 / 16);
+            var gno = LoadGenomes(@"C:\WORK\RAW\raw\Code\Other\M4M_MkI\M4M\bin\x64\Release\PopNotOrg4x4λ0Noise0.1E150K20000CF10_20180524T144040", drules, expected).ToDictionary(sg => sg.Epoch, sg => sg);
+            var gl1 = LoadGenomes(@"C:\WORK\RAW\raw\Code\Other\M4M_MkI\M4M\bin\x64\Release\PopNotOrg4x4λ0.22Noise0.1E150K20000CF10_20180524T115521", drules, expected).ToDictionary(sg => sg.Epoch, sg => sg);
+
+            IDictionary<double, double> entropy(IDictionary<int, SavedGenome> dict) => dict.ToDictionary(kv => (double)kv.Key, kv => kv.Value.GenomeDistibutionInfo.Entropy);
+            PlotLines("Entropy", "entropy.pdf", new[] { entropy(gno), entropy(gl1) }, "Epoch", "Entropy", new[] { "L1 λ = 0.00", "L1 λ = 0.22" }, 0.6);
+
+            IDictionary<double, double> error(IDictionary<int, SavedGenome> dict) => dict.ToDictionary(kv => (double)kv.Key, kv => kv.Value.GenomeDistibutionInfo.Error);
+            PlotLines("Error", "error.pdf", new[] { error(gno), error(gl1) }, "Epoch", "Chi-Squared Error", new[] { "L1 λ = 0.00", "L1 λ = 0.22" }, 0.6);
+            */
+
+            //Type2EvolvabilityTest4(1);
+            //Type2EvolvabilityTest4(2);
+            //Type2EvolvabilityTest4(4);
+
+            //Epistatics.BbnkStuff.Cbbnk2x2TestVeryLong(null, null, false);
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongG1(null, null, false);
+            //Epistatics.BbnkStuff.Cbbnk2x2DivergenceTests();
+
+            //Epistatics.BbnkStuff.Cbbnk2x2GNoiseTests();
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongG1("BBNK", null, null, false, 0.6);
+            //Epistatics.BbnkStuff.Cbbnk3x3TestRatherLongG1("BBNK", null, null, true, gNoiseTerm: 0.5, λ : 0.2);
+            //Epistatics.BbnkStuff.Cbbnk3x3TestRatherLongG1("BBNK", null, null, true, gNoiseTerm: 0.5, λ : 0.3);
+            //Epistatics.BbnkStuff.Cbbnk3x3TestRatherLongG1("BBNK", null, null, true, gNoiseTerm: 0.5, λ : 0.1);
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongVariableG("C:\\M4MExperiments\\BBNKMVG", null, null, true, gNoiseTerm: 0.6, λ: 0.2);
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongVariableB("C:\\M4MExperiments\\BBNKMVG", null, null, true, λ: 0.2);
+
+            //MiscPlotting.PlotMatrix("MC10x6", "C:\\M4MExperiments\\MC\\mc10x6", Epistatics.MCTarget.PrepareMCMatrixCheckCheck(10, 6, 0.33));
+
+            //var phenotype = new Phenotype(Epistatics.MCTarget.PrepareMCIdealVectorCheckCheck(10, 6));
+            //var target = Epistatics.MCTargetPackages.MC(10, 6, 0.1).Targets[0];
+            //target.NextGeneration(null, new JudgementRules(0, null, 0));
+            //Console.WriteLine(target.Judge(phenotype));
+            //Epistatics.MCStuff.MCTestRatherLongG(Epistatics.MCTargetPackages.MCCheckOnCheck(2, 2, 0.1), "C:\\M4MExperiments\\MC", null, null, false, 0.6, 0.002);
+            //Epistatics.MCStuff.MCTestRatherLongG(Epistatics.MCTargetPackages.MCCheckOnCheck(2, 2, 0.1), "C:\\M4MExperiments\\MC", null, null, false, 0.6, 0.0002);
+            //Epistatics.MCStuff.MCTestRatherLongG(Epistatics.MCTargetPackages.MCCheckOnCheck(10, 6, 0.1), "C:\\M4MExperiments\\MC", null, null, false, 0.6, 0.002);
+            //Epistatics.MCStuff.MCTestRatherLongG(Epistatics.MCTargetPackages.MCCheckOnCheck(10, 6, 0.1), "C:\\M4MExperiments\\MC", null, null, false, 0.6, 0.0002);
+
+            // this has a higher lambda, and M_B
+            //Epistatics.MCStuff.MCTestRatherLongG(Epistatics.MCTargetPackages.MCCheckOnCheck(10, 6, 0.01), "C:\\M4MExperiments\\MC", null, null, false, 0.6, 0.004, 0.00005);
+
+            // back to the old model
+            //Epistatics.MCStuff.MCTestRatherLongG(Epistatics.MCTargetPackages.MCCheckOnCheck(10, 6, 0.01), "C:\\M4MExperiments\\MC", null, null, false, 0.6, 0.0002, 0.00001);
+
+            // approximately Dave's config
+            //Epistatics.MCStuff.MCTestG(Epistatics.MCTargetPackages.MCCheckOnCheck(10, 6, 0.01), "C:\\M4MExperiments\\MC", null, null, true, 0.0, 0.0000, 0.00005, 200, 20000, 20);
+            //Epistatics.MCStuff.MCTestG(Epistatics.MCTargetPackages.MCCheckOnPlain(10, 6, 0.01), "C:\\M4MExperiments\\MC", null, null, true, 0.0, 0.0000, 0.00005, 200, 20000, 20);
+
+            // 3x2
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel3x2Variable(0.5), "C:\\M4MExperiments\\BBNKMVG3x2", null, null, true, λ: 0.10, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel3x2Variable(0.5), "C:\\M4MExperiments\\BBNKMVG3x2", null, null, true, λ: 0.15, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel3x2Variable(0.5), "C:\\M4MExperiments\\BBNKMVG3x2", null, null, true, λ: 0.20, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel3x2Variable(0.5), "C:\\M4MExperiments\\BBNKMVG3x2", null, null, true, λ: 0.25, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel3x2Variable(0.5), "C:\\M4MExperiments\\BBNKMVG3x2", null, null, true, λ: 0.30, K: 1000, epochs: 500000);
+
+            // 2x3
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.10, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.15, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.20, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.25, K: 1000, epochs: 500000);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.30, K: 1000, epochs: 500000);
+
+            // high λ, low M_G
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.30, K: 1000, epochs: 100000, gNoiseTerm: 0.4);
+
+            // high λ, L1
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.40, K: 1000, epochs: 20000, gNoiseTerm: 0.6);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.50, K: 1000, epochs: 20000, gNoiseTerm: 0.6);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.60, K: 1000, epochs: 20000, gNoiseTerm: 0.6);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3", null, null, true, λ: 0.70, K: 1000, epochs: 20000, gNoiseTerm: 0.6);
+
+            // high λ, LHalf (only λ = 0.01 shows any promise)
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.010, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.020, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.030, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.040, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.050, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.060, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.BbnkStuff.CbbnkGeneralG(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.070, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNK", Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.010, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNK", Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.015, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNK", Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.015, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent);
+
+            // pre-determined start points
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNK", Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3L1", null, null, true, λ: 0.2, K: 1000, epochs: 500000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent, resetIndividualInitialStateOperation: Epistatics.EpistaticStuff.ResetIndividualInitialStateOperationSampleSimilar(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5).Targets.Select(t => ((VectorTarget)t).Vector * -0.5)));
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNK", Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5), "C:\\M4MExperiments\\BBNKMVG2x3LHalf", null, null, true, λ: 0.015, K: 1000, epochs: 500000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.LHalfEquivalent, resetIndividualInitialStateOperation: Epistatics.EpistaticStuff.ResetIndividualInitialStateOperationSampleSimilar(Epistatics.BBNKTargetPackages.CbbnkCruel2x3Variable(0.5).Targets.Select(t => ((VectorTarget)t).Vector * -0.5)));
+
+            // Kind (not Cruel) - both form dense totalty: λ clearly not high enough... hopefully
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.02, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // higher λ
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.4, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.5, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.6, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // 2x2r6
+            // dense
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            // fail
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG2x2", null, null, true, λ: 0.4, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            // fail
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG2x2", null, null, true, λ: 0.6, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // nope
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.6, K: 1000, epochs: 50000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.7, K: 1000, epochs: 50000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.8, K: 1000, epochs: 50000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.9, K: 1000, epochs: 50000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // this is meant to work...
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKind", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), "C:\\M4MExperiments\\BBNKCruelMVG2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), "C:\\M4MExperiments\\BBNKCruelMVG2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), "C:\\M4MExperiments\\BBNKCruelMVG2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // ... but it doesn't ...
+            // ... but this does: C:\M4MExperiments\BBNKCruel2x2\EpistaticPopTestRatherLongGCbbnkCruel2x2P0.5_2_λ0.2EpiFalsemG0.6mB1E-05E50000K1000T10PF.200G1_20180704T120108
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongG1("C:\\M4MExperiments\\BBNKCruel2x2", null, null, false, 0.6);
+
+            // only thing that is different is number of epochs, and the targets (SO CHECK THE TARGETS)
+            // but these don't work...
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(0.5), "C:\\M4MExperiments\\BBNKCruel2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2NonVariable(0.5), "C:\\M4MExperiments\\BBNKCruelMVG2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // why are these not the same?!
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongG1("C:\\M4MExperiments\\BBNKCruel2x2", null, null, false, 0.6);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(0.5), "C:\\M4MExperiments\\BBNKCruel2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // OK... both are non-determinisctic
+            //for (int i = 0; i < 5; i++)
+            //    Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(0.5), "C:\\M4MExperiments\\BBNKCruel2x2", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(0.5), "C:\\M4MExperiments\\BBNKCruel2x2", null, null, false, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // I hate my life
+            //for (int i = 0; i < 5; i++)
+            //    Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongG1("C:\\M4MExperiments\\BBNKCruel2x2", null, null, true, 0.6);
+
+            // still hate it
+            //for (int i = 0; i < 5; i++)
+            //    Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(0.5), "C:\\M4MExperiments\\BBNKCruel2x2", null, null, false, λ: 0.2, K: 1000, epochs: 100000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+            // still hate it still
+            //for (int i = 0; i < 5; i++)
+            //    Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2NonVariable(0.5), "C:\\M4MExperiments\\BBNKCruel2x2NVG", null, null, false, λ: 0.2, K: 1000, epochs: 100000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+            //for (int i = 0; i < 5; i++)
+            //    Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKCruel", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), "C:\\M4MExperiments\\BBNKCruel2x2MVG", null, null, false, λ: 0.2, K: 1000, epochs: 100000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent);
+
+
+            // no!
+            //Epistatics.EpistaticStuff.GeneralExpistaticExperiment("BBNKKindComplements", Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5), "C:\\M4MExperiments\\BBNKKindMVG3x3", null, null, true, λ: 0.2, K: 1000, epochs: 20000, gNoiseTerm: 0.6, regularisationFunction: JudgementRules.L1Equivalent, resetIndividualInitialStateOperation: Epistatics.EpistaticStuff.ResetIndividualInitialStateOperationSampleSimilar(Epistatics.BBNKTargetPackages.CbbnkKind3x3Variable(0.5).Targets.Select(t => ((VectorTarget)t).Vector * -0.5), "Complements"));
+
+
+
+            // nice runs (all nice)
+            //PopNotExperiment(customPrefix: "L1 λ=0.00 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.00, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=0.00 BEx ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.00, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, exclusiveB: true);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 BEx ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, exclusiveB: true);
+
+            // λ=0.22 BEx long run, varying R_B, and BEx
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 BEx ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 2000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, exclusiveB: true);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 R_P=1.0 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 2000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, exclusiveB: false);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 R_P=0.5 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 2000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0, exclusiveB: false);
+
+            // even longer run
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 BEx ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, exclusiveB: true);
+            //PopNotExperiment(customPrefix: "L1 λ=0.5 BEx ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.5, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, exclusiveB: true);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 P_B=1 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0, exclusiveB: false);
+
+            //var rcs022 = LoadRegCoefs(@"C:\WORK\RAW\raw\Code\Other\M4M_MkI\M4M\bin\x64\Release\PopNotOrg4x4λ0.22Noise0.1E5000K20000CF10_20180706T123748\regcoefs.dat");
+            //void eprcs022(int notEpoch)
+            //{
+            //    PlotVectorFolded($"Epoch {notEpoch * 5}", $"meh/eprcs{notEpoch * 5}.pdf", Linear.CreateVector.DenseOfEnumerable(rcs022.Select(t => t[notEpoch])), "Trait j", "Trait i", min: -1.8, max: 2.1);
+            //}
+            //eprcs022(0);
+            //eprcs022(2);
+            //eprcs022(15);
+            //eprcs022(150);
+            //eprcs022(600);
+            //eprcs022(1000);
+
+            //PopNotExperiment(customPrefix: "L1 λ=0.00 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.00, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            // K /= 10, Epochs *= 10 (not really/very slow)
+            //PopNotExperiment(customPrefix: "L1 λ=0.11 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.11, K: 2_000, epochs: 15000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 2_000, epochs: 15000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=0.8 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.8, K: 2_000, epochs: 15000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+            //PopNotExperiment(customPrefix: "L1 λ=1.0 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 1.0, K: 2_000, epochs: 15000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2);
+
+            //Ev2PopTests();
+
+            //Epistatics.BbnkStuff.Cbbnk2x2TestRatherLongG1("C:\\M4MExperiments\\BBNKCruel2x2", null, null, false, 0.6);
+
+
+            // back to the madness
+            // OK, we got some hmods for each of these.
+            //string sf = "B1";
+            //ManyCbbnks(sf, Epistatics.BBNKTargetPackages.CbbnkCruel2x2(0.5), 100000);
+            //ManyCbbnks(sf, Epistatics.BBNKTargetPackages.CbbnkCruel2x2NonVariable(0.5), 100000);
+            //ManyCbbnks(sf, Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), 100000);
+
+            //string pf = "Bω0.5";
+            //ManyCbbnks(pf, "λ0.20", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), 100000, λ: 0.20);
+            //ManyCbbnks(pf, "λ0.25", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), 100000, λ: 0.25);
+            //ManyCbbnks(pf, "λ0.30", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), 100000, λ: 0.30);
+
+            //string pf = "Bω0.25";
+            //ManyCbbnks(pf, "λ0.20", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.25), λ: 0.20);
+            //ManyCbbnks(pf, "λ0.25", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.25), λ: 0.25);
+            //ManyCbbnks(pf, "λ0.30", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.25), λ: 0.30);
+
+            //string pf = "Bω0.4";
+            //ManyCbbnks(pf, "λ0.20", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.4), λ: 0.20);
+            //ManyCbbnks(pf, "λ0.25", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.4), λ: 0.25);
+            //ManyCbbnks(pf, "λ0.30", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.4), λ: 0.30);
+
+            //ManyCbbnks("λ0.20", "Bω0.5", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.5), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.45", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.45), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.4", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.4), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.35", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.35), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.3", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.3), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.25", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.25), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.2", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(0.2), λ: 0.20, count: 1);
+
+            //ManyCbbnks("λ0.20", "Bω0.5", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.5), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.45", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.45), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.4", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.4), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.35", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.35), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.3", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.3), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.25", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.25), λ: 0.20, count: 1);
+            //ManyCbbnks("λ0.20", "Bω0.2", Epistatics.BBNKTargetPackages.CbbnkKind2x2Variable(0.2), λ: 0.20, count: 1);
+
+            //var g = LoadGenome(@"C:\M4MExperiments\CbbnkCruel2x2P0.5_2\EpistaticPopTestBBNKCruelCbbnkCruel2x2P0.5_2_λ0.2EpiFalsemG0.6mB1E-05E50000K1000T10PF.200G1_20180718T110943\terminalGenome.dat");
+            //var dtm = g.TransMat;
+            //var dtmInfo = new DtmInfo(dtm, DtmClassification.DiscerneTrueModules(dtm, dtm.Enumerate().Max(e => Math.Abs(e)) / 10.0));
+            //Console.WriteLine(dtmInfo.Description);
+
+
+            //var g = LoadGenome(@"C:\M4MExperiments\Bω0.4CbbnkCruel2x2P0.4_4MVGλ0.25\EpistaticPopTestBBNKCruelCbbnkCruel2x2P0.4_4MVG_λ0.25EpiFalsemG0.6mB1E-05E25000K1000T10PF.200G1_20180720T171120\terminalgenome.dat");
+            //var dtm = g.TransMat;
+            //var dtmInfo = new DtmInfo(dtm, DtmClassification.DiscerneTrueModules(dtm, dtm.Enumerate().Max(e => Math.Abs(e)) / 10.0));
+            //Console.WriteLine(dtmInfo.Description);
+
+            //double ψ = 0.5;
+            //foreach (var ω in new[] { 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5 })
+            //{
+            //    ManyCbbnks("Block1_", "Bω" + ω.ToString("0.00"), Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(ω, ψ), λ: 0.20, count: 10);
+            //}
+
+            //double ψ = 0.5;
+            //foreach (var ω in new[] { 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.5 })
+            //{
+            //    ManyCbbnks("Block2_", "Bω" + ω.ToString("0.00"), Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(ω, ψ), λ: 0.20, count: 10);
+            //}
+
+            //double ψ = 0.5;
+            //double ω = 0.5;
+            //ManyCbbnks("BiasBlock1_", "Bω" + ω.ToString("0.00"), Epistatics.BBNKTargetPackages.CbbnkCruel2x2VariablePositiveBias(ω, ψ), λ: 0.20, count: 10, disableTransientIo: false);
+            //ManyCbbnks("BiasBlock1_", "Bω" + ω.ToString("0.00"), Epistatics.BBNKTargetPackages.CbbnkCruel2x2VariableNegativeBias(ω, ψ), λ: 0.20, count: 10, disableTransientIo: false);
+
+
+
+            // a few variations on a nice run
+            // template
+            //PopNotExperiment(customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 150, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.TanhHalf);
+            // LHalf - not sure...
+            //PopNotExperiment(outDir: @"C:\M4MExperiments\VBlock1", customPrefix: "LHalf λ=0.01 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.LHalfEquivalent, λ: 0.01, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.TanhHalf);
+            //PopNotExperiment(outDir: @"C:\M4MExperiments\VBlock1", customPrefix: "LHalf λ=0.015 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.LHalfEquivalent, λ: 0.015, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.TanhHalf);
+            //PopNotExperiment(outDir: @"C:\M4MExperiments\VBlock1", customPrefix: "LHalf λ=0.02 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.LHalfEquivalent, λ: 0.02, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.TanhHalf);
+            // MMSO - looks like a slow ride to hierarchy: should do a long run, possibly higher lambda (NOTE: wrong λ were used!!!!!)
+            //PopNotExperiment(outDir: @"C:\M4MExperiments\VBlock1", customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd);
+            //PopNotExperiment(outDir: @"C:\M4MExperiments\VBlock1", customPrefix: "L1 λ=0.11 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd);
+            //PopNotExperiment(outDir: @"C:\M4MExperiments\VBlock1", customPrefix: "L1 λ=0.33 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 5000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd);
+
+            //DocTests();
+
+            // A new era of being orangised:
+            //RunMMSOLong();
+            //RunMMSOMMSOShort();
+            //ClassicCbbnkCuel();
+            //RunMMSOR_B1();
+            //HeavyFuelRuns();
+            //OneKCbbnkCruel();
+            //ClassicM4M();
+            //ClassicM4MLong();
+            //M4MPerfectG();
+            //OneKCbbnkCruelBiased();
+            //MMSOVaryA();
+            //MMSOLinVaryQAB(); // q=5 seems to give good results for a=5 and a=2
+            //MMSOLinVaryHighQHighA();
+            //M4MPerfectGSingleModule();
+
+            //Fear(FileStuff.CreateNow(@"C:\M4MExperiments\Fear\Fear", "Fear ", "Fear "));
+
+            //List<string> classifications = new List<string>();
+            //foreach (var dir in System.IO.Directory.EnumerateDirectories(@"C:\M4MExperiments\ClassicCbbnkCuel\CbbnkCruel2x2P0.5_0.5_4MVG"))
+            //{
+            //    var g = LoadGenome(dir + "/terminalgenome.dat");
+            //    classifications.Add(DtmClassification.Type1Classify(g.TransMat));
+            //}
+
+            //using (var writer = new System.IO.StreamWriter(@"C:\M4MExperiments\ClassicCbbnkCuel\CbbnkCruel2x2P0.5_0.5_4MVG\classifications.txt"))
+            //{
+            //    foreach (var classification in classifications)
+            //    {
+            //        writer.WriteLine(classification);
+            //    }
+
+            //    writer.WriteLine();
+
+            //    var groups = classifications.GroupBy(v => v);
+            //    foreach (var grp in groups)
+            //    {
+            //        writer.WriteLine(grp.Key + "\t" + grp.Count());
+            //    }
+            //}
+
+            //Console.Beep((int)Pitch("Eb3"), 200);
+            //Console.Beep((int)Pitch("Bb4"), 200);
+            //Console.Beep((int)Pitch("F4"), 200);
+            //Console.Beep((int)Pitch("Bb3"), 200);
+            //Console.Beep((int)Pitch("F3"), 200);
+            //Console.Beep((int)Pitch("D3"), 200);
+            //Console.ReadKey(true);
+        }
+
+        private static void DocTests()
+        {
+            Reporting.Report report = new Reporting.Report("Everyone likes \n WOLVES", "Ergeaux M.", "Today");
+
+            var s1 = new Reporting.Section("Ehst");
+            report.Add(s1);
+
+            var s11 = new Reporting.Section("Seht");
+            s1.Add(s11);
+            s11.Add(new Reporting.RawParagraph("Hello. This is a paragraph"));
+
+            var il = new Reporting.ItemList();
+            s11.Add(il);
+            il.Add(new Reporting.RawInlineText("Hello1"));
+            il.Add(new Reporting.RawInlineText("Hello2"));
+
+            var fig = new Reporting.Figure();
+            s11.Add(fig);
+            fig.Caption = "Twee wolven!";
+            fig.Graphics.Add(new Reporting.FigureGraphic("C:/WORK/RAW/raw/Documents/Templates/Report/figures/aidanAndDenali", "0.8\\linewidth"));
+
+            var s2 = new Reporting.Section("Voll");
+            report.Add(s2);
+            s2.Add(new Reporting.RawParagraph("This is also a paragarph."));
+
+            report.RenderTex(@"C:\M4MExperiments\meh.tex", true);
+            report.WriteOut(@"C:\M4MExperiments\meh.txt");
+            System.Diagnostics.Process.Start(@"C:\M4MExperiments\meh.txt");
+        }
+
+        private static void ClassicM4M()
+        {
+            string objectives = "Some classic (low mutation rate) based on configs from 17G03";
+            string topdir = @"C:\M4MExperiments\ClassicM4M";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 16;
+            int epochs = 150;
+
+            double gNoiseTerm = 0.1; //p22
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+
+            double bProb = 1.0 / 2; //p9/OLD
+
+            Action[] expActs = new Action[]
+            {
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "OLD LNo", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.ConstantEquivalent, λ: 0.00, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "OLD L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "OLD L2 λ=38 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+            };
+
+            Parallel.Invoke(expActs);
+
+            bProb = 1.0 / 15; //p22
+
+            expActs = new Action[]
+            {
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "17G03 LNo", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.ConstantEquivalent, λ: 0.00, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "17G03 L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "17G03 L2 λ=38 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L2Equivalent, λ: 38, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+            };
+
+            Parallel.Invoke(expActs);
+        }
+
+        private static void ClassicM4MLong()
+        {
+            string objectives = "Some long classic (low mutation rate) based on configs from 17G03";
+            string topdir = @"C:\M4MExperiments\ClassicM4MLong";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 16;
+            int epochs = 10000;
+
+            double gNoiseTerm = 0.1; //p22
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+
+            double bProb = 1.0 / 2; //p9/OLD
+
+            Action[] expActs = new Action[]
+            {
+                 () => PopNotExperiment(outDir: topdir, customPrefix: "OLD L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: epochs, noRandomIO: true, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false),
+            };
+
+            Parallel.Invoke(expActs);
+        }
+
+        private static void M4MPerfectG()
+        {
+            string objectives = "Verify that setting G=S, and M_G=0 still leads to hierarchy";
+            string topdir = @"C:\M4MExperiments\M4MPerfectG\longruns\";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 16;
+            int epochs = 50000;
+
+            double gNoiseTerm = 0;
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+
+            double bProb = 1.0 / 2; //p9/OLD
+
+            foreach (double λ in new[] { 0.4 })
+            {
+                Action[] expActs = new Action[]
+                {
+                     //() => PopNotExperiment(outDir: topdir, customPrefix: $"PT L1 λ={λ} ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveB: false, noRandomIO: true),
+                     //() => PopulationTest(superDir: topdir, customPrefix: $"PT L1 λ={λ} ", dirPrefix: "PT_", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: false, disableTransientIO: false,
+                     //   epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true),
+                     () => PopulationTest(superDir: topdir, customPrefix: $"PTPG L1 λ={λ} ", dirPrefix: "PTPG_", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: false, disableTransientIO: false,
+                        epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true, gResetProb: 1.0, populationResetOperation: PopulationResetOperations.MatchTargetVector),
+                };
+
+                Parallel.Invoke(expActs);
+            }
+        }
+
+        private static void M4MPerfectGSingleModule()
+        {
+            string objectives = "Verify that setting G=S, and M_G=0 still leads to hierarchy with a single module";
+            string topdir = @"C:\M4MExperiments\M4MPerfectGSingleModule\ohdear\";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 4;
+            int epochs = 1000;
+
+            double gNoiseTerm = 0.1; //p22
+            double bNoiseTerm = 0.1 / (15 * N * N); //p22
+
+            double bProb = 1.0 / 2; //p9/OLD
+
+            List<Action> expActs = new List<Action>();
+            foreach (double λ in new[] { 0.075 })
+            {
+                //expActs.Add(() => PopulationTest(superDir: topdir, customPrefix: $"PTPGSM L1 λ={λ} ", dirPrefix: "PTPGSM_", targetPackage: ModularTargetPackage.Standard4x1, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: true, disableTransientIO: false,
+                //    epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true, gResetProb: 1.0, populationResetOperation: PopulationResetOperations.MatchTargetVector));
+                expActs.Add(() => PopulationTest(superDir: topdir, customPrefix: $"PTSM L1 λ={λ} ", dirPrefix: "PTSM_", targetPackage: ModularTargetPackage.Standard4x1, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: false, disableTransientIO: false,
+                    epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true, gResetProb: 0.0, populationResetOperation: PopulationResetOperations.None));
+                expActs.Add(() => PopulationTest(superDir: topdir, customPrefix: $"PTSMBEx L1 λ={λ} ", dirPrefix: "PTSMBEx_", targetPackage: ModularTargetPackage.Standard4x1, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: true, disableTransientIO: false,
+                    epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true, gResetProb: 0.0, populationResetOperation: PopulationResetOperations.None));
+                expActs.Add(() => PopulationTest(superDir: topdir, customPrefix: $"PTZSM L1 λ={λ} ", dirPrefix: "PTZSM_", targetPackage: ModularTargetPackage.Standard4x1, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: false, disableTransientIO: false,
+                    epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true, gResetProb: 1.0, populationResetOperation: PopulationResetOperations.Zero));
+                expActs.Add(() => PopulationTest(superDir: topdir, customPrefix: $"PTZSMBEx L1 λ={λ} ", dirPrefix: "PTZSMBEx_", targetPackage: ModularTargetPackage.Standard4x1, regularisationFunction: JudgementRules.L1Equivalent, λ: λ, K: 20_000, epochs: epochs, gNoiseTerm: gNoiseTerm, bNoiseTerm: bNoiseTerm, bProb: bProb, exclusiveBMutation: true, disableTransientIO: false,
+                    epigenetic: false, popSize: 1, eliteCount: 1, hillclimberMode: true, gResetProb: 1.0, populationResetOperation: PopulationResetOperations.Zero));
+
+                Parallel.Invoke(expActs.ToArray());
+            }
+        }
+
+        private static void HeavyFuelRuns(bool scoping = true)
+        {
+            if (scoping)
+            {
+                string objectives = "Scoping examples of a population resulting in a strong hierarchy... ideally we would also show that they switch faster over time... that's what trs are for, I suppose";
+                string topdir = @"C:\M4MExperiments\HeavyFuelRuns\Scoping4";
+                EnsureDirectory(topdir);
+                System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+                //int N = 16;
+
+                // scope it out...
+                Action[] expActs1 = new Action[]
+                {
+                    // this one I C&P'd from the past works... but it's a 3x3... and 3x3 is for chumps
+                    //() => PopulationTest(superDir: topdir, customPrefix: "old", targetPackage: ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 1000, K: 600, columns: false, T: 10, popSize: 20),
+                    //() => PopulationTest(superDir: topdir, customPrefix: "oldLong", targetPackage: ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 5000, K: 600, columns: false, T: 10, popSize: 20),
+                    
+                    () => PopulationTest(superDir: topdir, customPrefix: "new4x4", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0010, epochs: 50000, K: 600, columns: false, T: 10, popSize: 20),
+                    () => PopulationTest(superDir: topdir, customPrefix: "new4x4", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0005, epochs: 50000, K: 600, columns: false, T: 10, popSize: 20),
+                    () => PopulationTest(superDir: topdir, customPrefix: "new4x4", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0002, epochs: 50000, K: 600, columns: false, T: 10, popSize: 20),
+                    () => PopulationTest(superDir: topdir, customPrefix: "new4x4", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0001, epochs: 50000, K: 600, columns: false, T: 10, popSize: 20),
+                };
+
+                Parallel.Invoke(expActs1);
+
+                // scope it out...
+                //Action[] expActs2 = new Action[]
+                //{
+                //    () => PopulationTest(superDir: topdir, customPrefix: "SB P5 λ=0.10 K=600", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.10, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.1 / (15 * N * N), epochs: 5000, K: 600, columns: false, T: 10, g0: null, b0: null, popSize: 5),
+                //    () => PopulationTest(superDir: topdir, customPrefix: "SB P5 λ=0.10 K=1000", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.10, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.1 / (15 * N * N), epochs: 5000, K: 1000, columns: false, T: 10, g0: null, b0: null, popSize: 5),
+                //    () => PopulationTest(superDir: topdir, customPrefix: "SB P5 λ=0.18 K=600", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.18, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.1 / (15 * N * N), epochs: 5000, K: 600, columns: false, T: 10, g0: null, b0: null, popSize: 5),
+                //    () => PopulationTest(superDir: topdir, customPrefix: "SB P5 λ=0.18 K=1000", targetPackage: ModularTargetPackage.Standard4x4, λ: 0.18, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.1 / (15 * N * N), epochs: 5000, K: 1000, columns: false, T: 10, g0: null, b0: null, popSize: 5),
+                //};
+
+                //Parallel.Invoke(expActs2);
+            }
+            else
+            {
+                string objectives = "Produce examples of a population resulting in a strong hierarchy... ideally we would also show that they switch faster over time... that's what trs are for, I suppose";
+                string topdir = @"C:\M4MExperiments\HeavyFuelRuns\Proper";
+                EnsureDirectory(topdir);
+                System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+                // need to scope out some sensible things to do here before we try running them
+            }
+        }
+
+        private static void ClassicCbbnkCuel()
+        {
+            string objectives = "Produce detailed samples of (theorectically) unbiased CbbnkCruel runs with traditional parameters, in order to provide a new foundation in advance of further investiation";
+            string topdir = @"C:\M4MExperiments\ClassicCbbnkCuel";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            double ψ = 0.5;
+            double ω = 0.5;
+            ManyCbbnks(topdir, "", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(ω, ψ), λ: 0.20, count: 50, disableTransientIo: false);
+        }
+
+        private static void OneKCbbnkCruel()
+        {
+            string objectives = "Produce detailed samples of (theorectically) early dynamics of unbiased CbbnkCruel runs with traditional parameters";
+            string topdir = @"C:\M4MExperiments\OneKCbbnkCruel3";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            double ψ = 0.5;
+            double ω = 0.5;
+            ManyCbbnks(topdir, "", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(ω, ψ), λ: 0.20, K: 1000, epochs: 1000, count: 50, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor());
+        }
+
+        private static DensePopulationExperimentFeedbackConfigurator IsWatchPopConfigor()
+        {
+            return popConfig =>
+            {
+                ITarget lastTarget = null;
+
+                List<KeyValuePair<long, IndividualJudgement<DenseIndividual>>> bestSamples = new List<KeyValuePair<long, IndividualJudgement<DenseIndividual>>>();
+                List<KeyValuePair<long, IndividualJudgement<DenseIndividual>>> medianSamples = new List<KeyValuePair<long, IndividualJudgement<DenseIndividual>>>();
+                List<long> targetChanges = new List<long>();
+                List<KeyValuePair<long, double>> correls = new List<KeyValuePair<long, double>>();
+                List<KeyValuePair<long, double>> correlBiases = new List<KeyValuePair<long, double>>();
+
+                int samplePeriod = 1000;
+
+                void judged(FileStuff stuff, IReadOnlyList<IndividualJudgement<DenseIndividual>> populationJudgements, int epochCount, long generationCount, ITarget target)
+                {
+                    lastTarget = target;
+
+                    if (generationCount % samplePeriod == 0)
+                    {
+                        //bestSamples.Add(new KeyValuePair(generationCount, populationJudgements.ArgMax(j => j.Judgement.CombinedFitness)));
+                        //medianSamples.Add(new KeyValuePair(generationCount, populationJudgements.ArgMedian(j => j.Judgement.CombinedFitness)));
+                        bestSamples.Add(new KeyValuePair<long, IndividualJudgement<DenseIndividual>>(generationCount, populationJudgements.ArgMax(j => j.Judgement.CombinedFitness)));
+                        medianSamples.Add(new KeyValuePair<long, IndividualJudgement<DenseIndividual>>(generationCount, populationJudgements.ArgMedian(j => j.Judgement.CombinedFitness)));
+                    }
+                }
+
+                void endTarget(FileStuff stuff, Population<DenseIndividual> population, ITarget target, int epoch, long generationCount, IReadOnlyList<IndividualJudgement<DenseIndividual>> oldPopulationJudgements)
+                {
+                    targetChanges.Add(generationCount);
+
+                    // analuse IS, work out correlation
+                    var phVec = population.PeekAll()[0].Phenotype.Vector;
+                    var ph0 = phVec.First();
+                    var ph1 = phVec.Last();
+
+                    var correl = Math.Sign(ph0 * ph1);
+                    correls.Add(new KeyValuePair<long, double>(generationCount, correl));
+
+                    int mas = 50;
+                    if (correls.Count > mas)
+                    {
+                        double movingAverage = correls.Skip(correls.Count - mas).Average(s => s.Value);
+                        correlBiases.Add(new KeyValuePair<long, double>(generationCount, movingAverage));
+                    }
+                }
+
+                void finished(FileStuff stuff, Population<DenseIndividual> population, int epochCount, long generationCount)
+                {
+                    PlotModel model = new PlotModel() { Title = stuff.Title("CTr") };
+
+                    model.Axes.Add(new LinearAxis() { Key = "Left", Position = AxisPosition.Left, Title = "Fitness" });
+                    model.Axes.Add(new LinearAxis() { Key = "Right", Position = AxisPosition.Right, Title = "Correl" });
+                    model.Axes.Add(new LinearAxis() { Key = "X", Position = AxisPosition.Bottom, Title = "Generations" });
+
+                    void plotLine<TSample>(string title, OxyColor color, List<KeyValuePair<long, TSample>> samples, Func<TSample, double> metric, string yaxis)
+                    {
+                        LineSeries ls = new LineSeries() { Title = title, Color = color, YAxisKey = yaxis };
+
+                        foreach (var sample in samples)
+                        {
+                            ls.Points.Add(new DataPoint((double)sample.Key, metric(sample.Value)));
+                        }
+
+                        model.Series.Add(ls);
+                    }
+
+                    //plotLine("Median Fitness (Combined)", OxyColors.Purple, medianSamples, s => s.Judgement.CombinedFitness, "Left");
+                    //plotLine("Median Fitness (Benefit)", OxyColors.Blue, medianSamples, s => s.Judgement.Benefit, "Left");
+                    plotLine("Median Fitness (Cost)", OxyColors.Red, medianSamples, s => s.Judgement.Cost, "Left");
+
+                    plotLine("Phenotype Correls (mas50)", OxyColors.Orange, correlBiases, s => s, "Right");
+
+                    //foreach (int ts in targetChanges)
+                    //    model.Annotations.Add(new OxyPlot.Annotations.LineAnnotation() { Type = OxyPlot.Annotations.LineAnnotationType.Vertical, X = (double)ts, Color = OxyColors.Gray, LineStyle = LineStyle.Dash });
+
+                    Plotting.OldOxyPlotting.ExportToPdf(model, stuff.File("CTr.pdf"));
+                }
+
+                popConfig.Feedback.Judged.Register(judged);
+                popConfig.Feedback.EndTarget.Register(endTarget);
+                popConfig.Feedback.Finished.Register(finished);
+            };
+        }
+
+        private static void OneKCbbnkCruelBiased()
+        {
+            string objectives = "Produce detailed samples of early dynamics of biased CbbnkCruel runs with traditional parameters and various population configurations";
+            string topdir = @"C:\M4MExperiments\OneKCbbnkCruelBiased";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            //double ψ = 0.5;
+            //double ω = 0.5;
+            //ManyCbbnks(topdir, "HF", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.20, K: 1000, epochs: 2000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+            //    popSize: 5, eliteCount: 1, hillclimberMode: false);
+            //ManyCbbnks(topdir, "HT", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.20, K: 1000, epochs: 2000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+            //    popSize: 5, eliteCount: 1, hillclimberMode: true);
+            //ManyCbbnks(topdir, "HF21", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.20, K: 1000, epochs: 2000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+            //    popSize: 2, eliteCount: 1, hillclimberMode: false);
+            //ManyCbbnks(topdir, "HT11", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.25, K: 1000, epochs: 2000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+            //    popSize: 1, eliteCount: 1, hillclimberMode: true);
+            //ManyCbbnks(topdir, "HT11MMSO", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.2, K: 1000, epochs: 2000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+            //    popSize: 1, eliteCount: 1, hillclimberMode: true, regularisationFunction: JudgementRules.MMSO1);
+            // MMSO example is interesting... should do some more long runs of it with varying A
+        }
+
+        private static void MMSOVaryA()
+        {
+            string objectives = "Produce long-run samples of MMSO with varying a=b (keeping gradient at x=0 constant of 1)";
+            string topdir = @"C:\M4MExperiments\MMSOVaryAB";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            double ψ = 0.5;
+            double ω = 0.5;
+
+            // gradient a 0 kept constant at 1
+            foreach (double a in new[] { 0.1, 0.2, 0.5, 1.0, 2.0, 5.0 })
+                ManyCbbnks(topdir, "VAeqB", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.2, K: 1000, epochs: 20000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+                    popSize: 1, eliteCount: 1, hillclimberMode: true, regularisationFunction: JudgementRules.MMSO(a, a));
+        }
+
+        private static void MMSOLinVaryQAB()
+        {
+            string objectives = "Produce long-run samples of MMSO+L1 with varying a=qb (keeping gradient at x=0 constant of 1)";
+            string topdir = @"C:\M4MExperiments\MMSOLinVaryQAB";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            double ψ = 0.5;
+            double ω = 0.5;
+
+            List<Action> expActs = new List<Action>();
+
+            // gradient a 0 kept constant at 1
+            foreach (double q in new[] { 0.1, 0.2, 0.3, 0.4, 0.5 }) // proportion linear
+            {
+                foreach (double a in new[] { 0.1, 0.2, 0.5, 1.0, 2.0, 5.0 }) // shape of MMSO
+                {
+                    expActs.Add(() =>
+                    {
+                        try
+                        {
+                            ManyCbbnks(topdir, $"q{q}a{a}", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.2, K: 1000, epochs: 50000, count: 5, disableTransientIo: true, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+                                popSize: 1, eliteCount: 1, hillclimberMode: true, regularisationFunction: JudgementRules.MMSOLin(a, a * (1 - q), q));
+                        }
+                        catch (Exception ex)
+                        {
+                            Console.WriteLine(ex);
+                        }
+                    }
+                    );
+                }
+            }
+
+            Parallel.Invoke(expActs.ToArray());
+        }
+
+        private static void MMSOLinVaryHighQHighA()
+        {
+            string objectives = "Produce even longer runs of MMSO+L1 with high q and high a";
+            string topdir = @"C:\M4MExperiments\MMSOLinVaryHighQAB\uncruelshort7";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            double ω = 1.0; // Slow cut-off // 1.0 -> no cutoff
+            double ψ = 0.5; // Slow gradient
+
+            List<Action> expActs = new List<Action>();
+
+            // gradient a 0 kept constant at 1
+            foreach (double q in new[] { 0.5 }) // proportion linear (1.0 is no MMSO: the control)
+            {
+                foreach (double a in new[] { 2.0, 3.0, 4.0, 5.0 }) // shape of MMSO
+                {
+                    expActs.Add(() =>
+                    {
+                        try
+                        {
+                            ManyCbbnks(topdir, $"q{q}a{a}", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2(ω, ψ), λ: 0.2, K: 1000, epochs: 10000, count: 20, disableTransientIo: false, densePopulationExperimentFeedbackConfigurator: IsWatchPopConfigor(),
+                                popSize: 1, eliteCount: 1, hillclimberMode: true, regularisationFunction: JudgementRules.MMSOLin(a, a * (1 - q), q));
+                        }
+                        catch (Exception ex)
+                        {
+                            Console.WriteLine(ex);
+                        }
+                    }
+                    );
+                }
+            }
+
+            Parallel.Invoke(expActs.ToArray());
+        }
+
+        private static void CbbnkNot()
+        {
+            // this is pointless... it just forms a 1/4 immediately because there is no correlation
+            string objectives = "See what happens when we disable the asymetry in the payoff function: is there is no asynmetry, there can be no bias... right?";
+            string topdir = @"C:\M4MExperiments\CbbnkNot";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            double ψ = 1.0;
+            double ω = 1.0;
+            ManyCbbnks(topdir, "", "", Epistatics.BBNKTargetPackages.CbbnkCruel2x2Variable(ω, ψ), λ: 0.20, count: 50, disableTransientIo: false);
+        }
+
+        private static void RunMMSOMMSOShort()
+        {
+            string objectives = "Verify whether MMSO if a viable regularisation function (it doesn't have the undeseriable feature of having an infinite gradient at 0, which LHalf has; but it is asymptotic like tanh)";
+            string topdir = @"C:\M4MExperiments\MMSO_RegShort";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 16;
+            Action[] expActs = new Action[]
+            {
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.MMSO1, λ: 0.22, K: 20_000, epochs: 1000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd, disableTransientIO: true),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.33 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.MMSO1, λ: 0.33, K: 20_000, epochs: 1000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd, disableTransientIO: true),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.44 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.MMSO1, λ: 0.44, K: 20_000, epochs: 1000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd, disableTransientIO: true),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.55 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.MMSO1, λ: 0.55, K: 20_000, epochs: 1000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd, disableTransientIO: true),
+            };
+
+            Parallel.Invoke(expActs);
+        }
+        private static void RunMMSOR_B1()
+        {
+            string objectives = "Determine whether R_B=1 changes the shape of the trajectories";
+            string topdir = @"C:\M4MExperiments\MMSOR_B1";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 16;
+            Action[] expActs = new Action[]
+            {
+                () => PopNotExperiment(outDir: topdir, customPrefix: "MMSO L1 λ=1.00 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 1.00, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0, squash: DevelopmentRules.MichaelisMentenSymOdd),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "MMSO L1 λ=2.00 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 2.00, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0, squash: DevelopmentRules.MichaelisMentenSymOdd),
+            };
+
+            Parallel.Invoke(expActs);
+        }
+
+        private static void RunMMSOLong()
+        {
+            string objectives = "Determine whether MichaelisMenthon (SymOdd) can produce hierarchy";
+            string topdir = @"C:\M4MExperiments\MMSO";
+            EnsureDirectory(topdir);
+            System.IO.File.WriteAllText(topdir + @"\objectives.txt", objectives);
+
+            int N = 16;
+            Action[] expActs = new Action[]
+            {
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.22 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.22, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.33 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.33, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.44 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.44, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=0.55 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 0.55, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd),
+                () => PopNotExperiment(outDir: topdir, customPrefix: "L1 λ=2.00 ", targetPackage: ModularTargetPackage.Original4x4, regularisationFunction: JudgementRules.L1Equivalent, λ: 2.00, K: 20_000, epochs: 25000, noRandomIO: true, bNoiseTerm: 0.1 / (15 * N * N), bProb: 1.0 / 2, squash: DevelopmentRules.MichaelisMentenSymOdd),
+            };
+
+            Parallel.Invoke(expActs);
+
+            // ja, wir haben hierarchy
+        }
+
+        public static void SayInts(int[] ints)
+        {
+            string intToKey(int i)
+            {
+                i--;
+                string[] table = new[] { "C", "E", "G", "B" };
+                return table[i % 4] + ((i + 1) / 4 + 3);
+            }
+
+            string[] keys = ints.Select(i => intToKey(i)).ToArray();
+            int[] pitches = keys.Select(k => (int)Pitch(k)).ToArray();
+
+            foreach (var p in pitches)
+            {
+                Console.Beep(p, 200);
+            }
+        }
+
+        private static void ManyCbbnks(string topdir, string prefix, string suffix, Epistatics.EpistaticTargetPackage target, int epochs = 100000, int K = 1000, double λ = 0.2, double gNoiseTerm = 0.6, int count = 10, bool disableTransientIo = true, DensePopulationExperimentFeedbackConfigurator densePopulationExperimentFeedbackConfigurator = null, int popSize = 1, int eliteCount = 1, bool hillclimberMode = true, IRegularisationFunction<IGenome> regularisationFunction = null, bool plotRegFunc = true)
+        {
+            var nowTime = Misc.NowTime;
+
+            regularisationFunction = regularisationFunction ?? JudgementRules.L1Equivalent;
+
+            if (plotRegFunc)
+            {
+                int N = target.TargetSize;
+
+                string regfname = topdir + "\\reg" + regularisationFunction.Name + suffix + nowTime + ".pdf";
+                double regx0 = -5.0;
+                double regx1 = +5.0;
+                double regdx = 0.05;
+
+                List<double> regdat = new List<double>();
+
+                var regGenome = DenseGenome.CreateDefaultGenomeDense(N);
+                Linear.Matrix<double> regB = Linear.CreateMatrix.Dense<double>(N, N);
+
+                for (double x = regx0; x <= regx1; x += regdx)
+                {
+                    for (int i = 0; i < N; i++)
+                        for (int j = 0; j < N; j++)
+                            regB[i, j] = x;
+
+                    regGenome.CopyOverTransMat(regB);
+                    regdat.Add(regularisationFunction.ComputeCost(regGenome));
+                }
+
+                PlotLine("Reg " + regularisationFunction.Name, regfname, regdat.ToArray(), regx0, regdx, "Element Connection Weight (total/N^{2})", "Total Cost");
+            }
+
+            void saySummary(DtmInfo dtmInfo)
+            {
+                string summary = dtmInfo.Summary;
+                int[] ints = summary.Split(new[] { '/', ',', '_', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(s => int.Parse(s)).ToArray();
+
+                SayInts(ints);
+            }
+
+            List<DtmInfo> dtmInfos = new List<DtmInfo>();
+
+            topdir = topdir + "\\" + prefix + target.Name + suffix;
+
+            for (int i = 0; i < count; i++)
+            {
+                Console.Title = i + " " + topdir;
+
+                var res1 = Epistatics.EpistaticStuff.GeneralEpistaticExperiment("BBNKCruel", target, topdir, null, null, disableTransientIo, λ: λ, K: K, epochs: epochs, gNoiseTerm: gNoiseTerm, regularisationFunction: regularisationFunction, densePopulationExperimentFeedbackConfigurator: densePopulationExperimentFeedbackConfigurator, popSize: popSize, eliteCount: eliteCount, hillclimberMode: hillclimberMode);
+                var dtm = res1.ExtractAll()[0].Genome.TransMat;
+
+                var dtmInfo = new DtmInfo(dtm, DtmClassification.DiscerneTrueModules(dtm, dtm.Enumerate().Max(e => Math.Abs(e)) / 10.0));
+                dtmInfos.Add(dtmInfo);
+
+                Console.WriteLine(dtmInfo.Description);
+
+                try
+                {
+                    saySummary(dtmInfo);
+                }
+                catch
+                {
+                    Console.Beep(440, 500);
+                }
+            }
+
+            StringBuilder sb = new StringBuilder();
+
+            foreach (var dtmInfo in dtmInfos)
+                sb.AppendLine(dtmInfo.Summary);
+
+            var dtmSummaries = sb.ToString();
+            System.IO.File.WriteAllText(topdir + "\\dtmSummaries" + suffix + nowTime + ".txt", dtmSummaries);
+            Console.WriteLine(dtmSummaries);
+
+            Console.Title = "- " + topdir;
+            Console.WriteLine("Finished ManyCbbnk.");
+        }
+
+        private static void SupportingFigures()
+        {
+            var o4x4 = ModularTargetPackage.Original4x4;
+            FileStuff stuff = FileStuff.CreateNow(o4x4.Name, "", "");
+            int ti = 1;
+            foreach (var t in o4x4.Targets)
+                PlotVectorFolded($"Target {ti++}", stuff.File(t.FriendlyName), t.Vector, "Sub-trait j", "Module i", 0.6);
+        }
+
+        private static void WeirdStuff()
+        {
+            // And9
+            //PopulationTest(ModularTargetPackage.And9, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 10, popSize: 5, tracePeriod: 2500, decayRate: 0.2); // nomral: bleh
+            //PopulationTest(ModularTargetPackage.And9, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 10, popSize: 5, tracePeriod: 2500, decayRate: 0.2, gOpenEntries: new[] { 0, 1, 2, 3, 4, 5 }); // constrain G: learns AND! :D
+            //PopulationTest(ModularTargetPackage.And9, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 10000, K: 600, columns: false, T: 10, popSize: 5, tracePeriod: 500, decayRate: 0.2, gOpenEntries: new[] { 0, 1, 2, 3, 4, 5 }); // run it for longer: eh, much the same
+            //PopulationTest(ModularTargetPackage.And9, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0001, epochs: 10000, K: 500, columns: false, T: 10, popSize: 10, tracePeriod: 500, decayRate: 0.2, gOpenEntries: new[] { 0, 1, 2, 3, 4, 5 }); // larger population, low BM:  miserable failure... no hierarchy, lumped c and b together, how disappointing (is this beacause it takes too long to evolve, or something nefarious?)
+            //PopulationTest(ModularTargetPackage.And9, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 10000, K: 600, columns: false, T: 10, popSize: 10, tracePeriod: 500, decayRate: 0.2, gOpenEntries: new[] { 0, 1, 2, 3, 4, 5 }); // same larger population, default BM:  eh, much the same, reaches something sensible sooner, doesn't seem to improve much
+
+            // Bool12
+            PopulationTest(ModularTargetPackage.Bool12, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 10000, K: 600, columns: false, T: 10, popSize: 10, tracePeriod: 500, decayRate: 0.2, gOpenEntries: new[] { 0, 1, 2, 3, 4, 5 }); // 
+        }
+
+        private static void MorePopulationStuff()
+        {
+            // high T - we arn't allowed high T without columns!!! what is wrong with you?!
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 1000, K: 600, columns: false, T: 10, popSize: 20, tracePeriod: 500);
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 10, K: 600, columns: false, T: 10, popSize: 20, tracePeriod: 5);
+
+            // max out decay term instead - nope... initial states disconnected from phenotypic states
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 1000, K: 600, columns: false, T: 1, popSize: 20, tracePeriod: 100, decayRate: 0.9);
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 1000, K: 600, columns: false, T: 1, popSize: 20, tracePeriod: 100, decayRate: 0.99);
+
+            //Task[] tasks = {
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: true, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: true, T: 10, popSize: 50, tracePeriod: 100, decayRate: 0.2), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: true, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.6), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: true, T: 10, popSize: 50, tracePeriod: 100, decayRate: 0.6), TaskCreationOptions.LongRunning)
+            //};
+
+            //Task[] tasks = {
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: true, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, squash: DevelopmentRules.TanhTen), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: true, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.6, squash: DevelopmentRules.TanhTen), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, squash: DevelopmentRules.TanhTen), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.6, squash: DevelopmentRules.TanhTen), TaskCreationOptions.LongRunning),
+            //};
+
+            //foreach (var t in tasks)
+            //    t.Start();
+            //Task.WaitAll(tasks);
+
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0001, epochs: 10000, K: 600, columns: false, T: 1, popSize: 250, tracePeriod: 100, decayRate: 0.2, squash: DevelopmentRules.TanhHalf); // fail (atlast, not in timeframe)
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, bNoiseTerm: 0.0001, epochs: 10000, K: 400, columns: false, T: 1, popSize: 250, tracePeriod: 100, decayRate: 0.2, squash: DevelopmentRules.TanhHalf); // fail (atleast, not in timeframe)
+
+            // Release/Ereht
+            //Task[] tasks = {
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, superDir: "Ehreht", gUpdateCount: 1), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, superDir: "Ehreht", gUpdateCount: 2), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, superDir: "Ehreht", gUpdateCount: 4), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, superDir: "Ehreht", gUpdateCount: 8), TaskCreationOptions.LongRunning),
+            //};
+
+            // Debug/Tacky
+            //Task[] tasks = {
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.1, superDir: "Tacky", gUpdateCount: 1), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.2, superDir: "Tacky", gUpdateCount: 1), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.4, superDir: "Tacky", gUpdateCount: 1), TaskCreationOptions.LongRunning),
+            //    new Task(() => PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 2500, K: 600, columns: false, T: 1, popSize: 50, tracePeriod: 100, decayRate: 0.8, superDir: "Tacky", gUpdateCount: 1), TaskCreationOptions.LongRunning),
+            //};
+
+            //foreach (var t in tasks)
+            //    t.Start();
+            //Task.WaitAll(tasks);
+        }
+
+        private static double Pitch(string name)
+        {
+            Dictionary<string, int> keys = new Dictionary<string, int>
+            {
+                { "A", 0 },
+                { "A#", 1 },
+                { "Bb", 1 },
+                { "B", 2 },
+                { "C", 3 },
+                { "C#", 4 },
+                { "Db", 4 },
+                { "D", 5 },
+                { "D#", 6 },
+                { "Eb", 6 },
+                { "E", 7 },
+                { "F", 8 },
+                { "F#", 9 },
+                { "Gb", 9 },
+                { "G", 10 },
+                { "G#", 11 },
+                { "Ab", 11 },
+            };
+
+            bool adjusted = name.Contains("#") || name.Contains("b");
+            string keyName = name.Substring(0, adjusted ? 2 : 1);
+            int octave = int.Parse(name.Substring(keyName.Length));
+
+            double a4 = 440;
+
+            int diff = (octave - 4) * 12 + keys[keyName];
+
+            double l12 = Math.Exp(Math.Log(2) / 12);
+            return a4 * Math.Pow(l12, diff);
+        }
+
+
+        private static void GradientMeasuring()// int T, int k, double s, double r, double q, double ys, double yr, double λ, double innerSquash)
+        {
+            // need to measure Δf/Δs and Δf/Δq for varyious s, q, ys, and yr, for varying T; eh, don't forget r, I suppose
+
+            var model = new PlotModel { Title = "Gradients" };
+            //model.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "Δs/Δq" });
+            model.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom, Title = "τ2" });
+
+            model.PlotAreaBorderColor = OxyColors.Transparent;
+
+            int k = 3;
+            //MultiMeasureJudgement J(int T, double s, double q, double yDiff, double λ)
+            //{
+            //    return Judge(T, k, s, s, q, -1 + yDiff, -1, λ, 0.5, 1.0);
+            //}
+
+            double ΔsΔq(int T, double s, double q, double y0, double yDiff, double λ, double τ2)
+            {
+                var dc = 0.00000001;
+                var z = Judge(T, k, s, s, q, y0 + yDiff, y0, λ, 0.5, 1.0, τ2).CombinedFitness;
+                var Δs = Judge(T, k, s + dc, s + dc, q, y0 + yDiff, y0, λ, 0.5, 1.0, τ2).CombinedFitness;
+                var Δq = Judge(T, k, s, s, q + dc / k, y0 + yDiff, y0, λ, 0.5, 1.0, τ2).CombinedFitness;
+                return (Δs - z) / (Δq - z);
+            }
+
+            double[] y0s = { -1.0, -0.5, 0.0, 0.5, 1.0 };
+            int yk = 0;
+            foreach (double y0 in y0s)
+            {
+                var yaxe = new LinearAxis { Position = AxisPosition.Left, Title = "Δs/Δq (" + y0 + ")", Key = "yk" + yk, StartPosition = 0.01 + (double)yk++ / y0s.Length, EndPosition = -0.01 + (double)yk / y0s.Length, AxislineColor = OxyColors.Black };
+                model.Axes.Add(yaxe);
+                for (int T = 2; T <= 10; T++)
+                {
+                    var c1 = OxyColor.FromHsv((T - 1) / 11.0, 0.9, 0.9);
+                    var s0 = 3;
+                    model.Series.Add(new FunctionSeries(x => ΔsΔq(T, s0, s0, y0, 0.1, 0.1, x), 0, 1, 1000) { Title = "f" + T, Color = c1, YAxisKey = yaxe.Key, RenderInLegend = yk == 1 });
+                    foreach (double ds in new[] { 0.5, 1.0, 1.5, 2.0, 2.5 })
+                        model.Series.Add(new FunctionSeries(x => ΔsΔq(T, s0 + ds, s0 - ds / k, y0, 0.1, 0.1, x), 0, 1, 1000) { Color = c1, LineStyle = LineStyle.Dash, YAxisKey = yaxe.Key });
+                }
+
+                FudgeYAxis(model, yaxe);
+            }
+
+            //model.RenderingDecorator = rc => new XkcdRenderingDecorator(rc);
+
+            string fname = "bleh.pdf";
+            Plotting.OldOxyPlotting.ExportToPdf(model, fname);
+            System.Diagnostics.Process.Start(fname);
+        }
+
+        static void FudgeYAxis(PlotModel model, LinearAxis yaxis)
+        {
+            model.InvalidatePlot(true);
+
+            var min = model.Series.OfType<FunctionSeries>().Where(s => s.YAxisKey == yaxis.Key).Min(s => s.Points.Min(p => p.Y));
+            var max = model.Series.OfType<FunctionSeries>().Where(s => s.YAxisKey == yaxis.Key).Min(s => s.Points.Max(p => p.Y));
+
+            //double dd = (max - min);
+            //yaxis.Minimum = min - dd * 0.1;
+            //yaxis.Maximum = max + dd * 0.1;
+            //yaxis.MajorStep = dd / 4.0;
+
+            Plotting.OxyPlotModelShim.Sillyify(5, yaxis, min, max);
+        }
+
+        private static MultiMeasureJudgement Judge(int T, int k, double s, double r, double q, double ys, double yr, double λ, double innerSquash, double e, double τ2)
+        {
+            var g = OneModule.PrepareG(k, ys, yr);
+            var b = OneModule.PrepareB(k, s, r, q);
+            DenseGenome genome = new DenseGenome(g, b);
+            DevelopmentRules drules = new DevelopmentRules(T, 1.0, τ2, new TanhSquash(innerSquash));
+            var phenotype = genome.Develop(null, drules); // hopefully it will crash if it tries any non-determinism
+            JudgementRules jrules = new JudgementRules(λ, JudgementRules.L1Equivalent, 0.0);
+            VectorTarget target = OneModule.PrepareE(k, e, e);
+            target.NextGeneration(null, jrules);
+            return MultiMeasureJudgement.Judge(genome, phenotype, jrules, target);
+        }
+
+        private static void Ev2Tests()
+        {
+            Type2EvolvabilityTest4(1);
+            Type2EvolvabilityTest4(2);
+            Type2EvolvabilityTest4(4);
+            Type2EvolvabilityTest4(8);
+            Type2EvolvabilityTest4(16);
+        }
+
+        private static void Ev2PopTests()
+        {
+            //Type2PopulationEvolvabilityTest4(1, 1);
+            //Type2PopulationEvolvabilityTest4(1, 2);
+            Type2PopulationEvolvabilityTest4(1, 4); // this one is beautiful enough
+            //Type2PopulationEvolvabilityTest4(1, 8);
+            //Type2PopulationEvolvabilityTest4(1, 16);
+        }
+
+        private class DistanceFitnessJudger : IVectorTargetJudger
+        {
+            public DistanceFitnessJudger(double magnitude)
+            {
+                Magnitude = magnitude;
+                Name = "DistanceFitnessJudger" + magnitude;
+            }
+
+            public string Name { get; }
+
+            public string Description => $"DistanceFitnessJudger (Magnitude={Magnitude})";
+
+            public double Magnitude { get; }
+
+            public double Judge(Linear.Vector<double> vector, Phenotype phenotype)
+            {
+                return vector.Zip(phenotype.Vector, (vi, pi) => vi * Magnitude - Math.Abs(vi * Magnitude - pi)).Sum();
+            }
+        }
+
+        public static void DistanceFitnessExperiments()
+        {
+            for (double mag = 0.86; mag < 0.9; mag += 0.01)
+            {
+                DistanceFitnessExperiment(mag);
+            }
+        }
+
+        public static void DistanceFitnessExperiment(double mag = 0.85)
+        {
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("++++", "3_3", new DistanceFitnessJudger(mag)),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 30000;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(0.2, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "DF1Module" + mag;
+            Experiment.RunExperiment(rand, config, "DistanceFitness_" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false);
+        }
+
+        private static void ToplogyExperiments()
+        {
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+            string outDir = "Topevol";
+
+            TopologicalExperiment(rand, outDir, "Flat", TopologicalGenome.Empty(4, GeneralTopology.Flat));
+            TopologicalExperiment(rand, outDir, "Identity", TopologicalGenome.Empty(4, GeneralTopology.Identity));
+            TopologicalExperiment(rand, outDir, "Column", TopologicalGenome.Empty(4, GeneralTopology.Column));
+        }
+
+        private static void TopologicalExperiment(RandomSource rand, string outDir, string prefix, TopologicalGenome genome)
+        {
+            TopologicalGenome.RunExperiment(outDir + "_" + prefix, prefix, prefix + ": ", rand, genome, 1000, 50, 10, DevelopmentRules.TanhHalf.Delegate, 0.001, 0.2);
+        }
+
+        private static void HorrifyingStuff()
+        {
+            // first, check EnumerateParameterisations is working
+            //CheckEnumerateParameterisationsIsWorking();
+            // it is
+
+            int T = 10;
+            double dd = 0.05;
+            //TopologyStuff.SolveTopology("FlatTop", GeneralTopology.Flat, 0, 10, dd, T, DevelopmentRules.TanhHalf.Value);
+            //TopologyStuff.SolveTopology("IdentityTop", GeneralTopology.Identity, 0, 10, dd, T, DevelopmentRules.TanhHalf.Value);
+            TopologyStuff.SolveTopology("ColumnTop", GeneralTopology.Column, 0, 10, dd, T, DevelopmentRules.TanhHalf.Delegate, 200);
+        }
+
+        private static void CheckEnumerateParameterisationsIsWorking()
+        {
+            // first, check EnumerateParameterisations is working
+
+            var plotModel = Plotting.OldOxyPlotting.LinearAxes("EnumerateParameterisations", "Meh", "Weights");
+
+            int n = 4;
+
+            OxyPlot.Series.LineSeries[] ls = new OxyPlot.Series.LineSeries[n];
+            for (int i = 0; i < n; i++)
+                ls[i] = new OxyPlot.Series.LineSeries() { Title = "" + i };
+
+            double x = 0;
+            foreach (var p in Combinatorics.EnumerateOrderedParameterisations(n, 1.0, 10))
+            {
+                for (int i = 0; i < n; i++)
+                    ls[i].Points.Add(new DataPoint(x, p[i]));
+
+                x++;
+            }
+
+            for (int i = 0; i < n; i++)
+                plotModel.Series.Add(ls[i]);
+            plotModel.InvalidatePlot(true);
+
+            Plotting.OldOxyPlotting.ExportToPdf(plotModel, "EnumerateParameterisations.pdf");
+        }
+
+        public static void ColumnExperiment()
+        {
+            // sooooo much duplication...
+            VectorTarget[] targets = new[]
+            {
+                new VectorTarget("++++", "plus"), // standard
+            };
+
+            // config
+            int N = targets[0].Size;
+            int epochs = 1000;
+            int K = 50;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(0.2, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // TransMat-Constrained Empty Genome
+            Linear.Vector<double> emptyIS = Linear.CreateVector.Dense<double>(N, 0.0);
+            Linear.Matrix<double> emptyGRN = Linear.CreateMatrix.SparseDiagonal<double>(N, 0.0);
+            IReadOnlyList<MatrixEntryAddress> column0 = Enumerable.Range(0, N).Select(idx => new MatrixEntryAddress(idx, 0)).ToArray();
+            DenseGenome emptyIdentity = new DenseGenome(emptyIS, emptyGRN, null, column0);
+
+            // go
+            string catchyName = "StdCol";
+            Experiment.RunExperiment(rand, config, "Column_" + catchyName, startingGenome: emptyIdentity, titlePrefix: catchyName + " ", openTerminalsPlots: false);
+        }
+
+        public static void IdentityExperiment()
+        {
+            VectorTarget[] targets = new[]
+            {
+                new VectorTarget("++++", "plus"), // standard
+            };
+
+            // config
+            int N = targets[0].Size;
+            int epochs = 1000;
+            int K = 50;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(0.2, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // TransMat-Constrained Empty Genome
+            Linear.Vector<double> emptyIS = Linear.CreateVector.Dense<double>(N, 0.0);
+            Linear.Matrix<double> emptyGRN = Linear.CreateMatrix.SparseDiagonal<double>(N, 0.0);
+            IReadOnlyList<MatrixEntryAddress> diagonals = Enumerable.Range(0, N).Select(idx => new MatrixEntryAddress(idx, idx)).ToArray();
+            DenseGenome emptyIdentity = new DenseGenome(emptyIS, emptyGRN, null, diagonals);
+
+            // go
+            string catchyName = "StdId";
+            Experiment.RunExperiment(rand, config, "Identity_" + catchyName, startingGenome: emptyIdentity, titlePrefix: catchyName + " ", openTerminalsPlots: false);
+        }
+
+        public static void LeaderTest()
+        {
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(1.0, JudgementRules.L1Equivalent, 0.0);
+
+            double l = 0.3;
+            double s = 0.9;
+
+            int N = 4;
+            double[,] dtm = new double[4, 4] {
+                { 0, 0, 0, 0},
+                { l, l, l, l},
+                { 0, 0, s, 0},
+                { 0, 0, 0, 0}
+            };
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            VectorTarget target = new VectorTarget("++++", "t");
+            DenseGenome genome = new DenseGenome(target.Vector, MathNet.Numerics.LinearAlgebra.CreateMatrix.DenseOfArray(dtm));
+            Phenotype phenotype = genome.Develop(context, drules);
+            target.NextGeneration(rand, jrules);
+            MultiMeasureJudgement judgement = MultiMeasureJudgement.Judge(genome, phenotype, jrules, target);
+
+            string title = $"l{l}s{s}λ{jrules.RegularisationFactor}";
+            string topDir = title + "_" + Misc.NowTime;
+
+            if (!System.IO.Directory.Exists(topDir))
+            {
+                System.IO.Directory.CreateDirectory(topDir);
+            }
+
+            System.IO.File.WriteAllText(topDir + "/config.txt", $"l = {l}, s = {s}, λ = {jrules.RegularisationFactor}");
+
+            Console.WriteLine(topDir);
+
+            double[][] trajectories = null;
+            int step = 100000;
+            int maxGenerations = 10000000;
+            double[][] regulationCoefs = Misc.CreateEmpty<double>(N * N, maxGenerations / step + 1);
+            for (int generation = 0; generation < maxGenerations;)
+            {
+                if (generation != 0)
+                {
+                    Experiment.SpinMk2(ref genome, ref phenotype, ref judgement, context, target, rrules, drules, jrules, step);
+                    generation += step - 1;
+                }
+
+                Console.WriteLine(generation);
+                MiscPlotting.PlotMatrix(title + ": DTM " + generation, topDir + "/dtm" + generation + ".pdf", genome.TransMat, "Feedee", "Feeder", true);
+                genome.DevelopWithTrajectories(rand, drules, ref trajectories);
+                string[] seriesLabels = Enumerable.Range(0, N).Select(n => "Trait " + n).ToArray();
+                MiscPlotting.PlotLines(title + ": Traj " + generation, topDir + "/traj" + generation + ".pdf", trajectories, 0, 1, "Developmental Time Step", "Trait Weight", seriesLabels);
+
+                int o = 0;
+                for (int i = 0; i < N; i++)
+                {
+                    for (int j = 0; j < N; j++)
+                    {
+                        regulationCoefs[o][generation / step] = genome.TransMat[i, j];
+                        o++;
+                    }
+                }
+
+                generation++;
+            }
+
+            MiscPlotting.PlotLines(title + "; Regulation Coefs", topDir + "/rcs.pdf", regulationCoefs, 0, step, "Generation", "Reg Coefs");
+        }
+
+        public static void EvolvabilityTest()
+        {
+            for (int gv = /*1*/5; gv <= 10; gv++)
+                EvolvabilityTest4(gv);
+        }
+
+        public static void EvolvabilityTest4(int gvecUpdates)
+        {
+            VectorTarget oldTarget = new VectorTarget("+---", "o");
+            VectorTarget newTarget = new VectorTarget("-+++", "n");
+            DenseGenome fox = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("Aaaa", 0.25, 0.25, 0.25));
+            DenseGenome husky4 = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("Aaaa", 0.0, 1.0, 1.0));
+
+            EvolvabilityTest("evtest4_" + "g" + gvecUpdates, 500, 5, 20, oldTarget, newTarget, fox, husky4, gvecUpdates);
+        }
+
+        public static void EvolvabilityTest162mod()
+        {
+            VectorTarget oldTarget = new VectorTarget("+---+---+-------", "o");
+            VectorTarget newTarget = new VectorTarget("-+++-++++-------", "n");
+            DenseGenome fox = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("AaaaBbbbCcccdddd", 0.25, 0.25, 0.25));
+            DenseGenome husky4 = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("AaaaBbbbCcccdddd", 0.0, 1.0, 1.0));
+
+            EvolvabilityTest("evtest162mod", 2000, 10, 200, oldTarget, newTarget, fox, husky4);
+        }
+
+        public static void Type2EvolvabilityTest4(int gvecUpdates)
+        {
+            VectorTarget oldTarget = new VectorTarget("+---", "o");
+            VectorTarget newTarget = new VectorTarget("-+++", "n");
+            DenseGenome fox = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("Aaaa", 0.25, 0.25, 0.25));
+            DenseGenome husky4 = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("Aaaa", 0.0, 1.0, 1.0));
+
+            EvolvabilityTest("evtest4type3_" + "g" + gvecUpdates, 500, 5, 10, oldTarget, newTarget, fox, husky4, gvecUpdates, 0.1 / Math.Sqrt(gvecUpdates), true);
+        }
+
+        public static void EvolvabilityTest(string prefix, int generations, int samplePeriod, int mutantSamplePeriod, VectorTarget oldTarget, VectorTarget newTarget, DenseGenome fox, DenseGenome husky4, int gvecUpdates = 1, double gMutationMagnitude = 0.1, bool noMA = false)
+        {
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            //ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            ReproductionRules rrules = new ReproductionRules(gMutationMagnitude, 0, 0, false, gvecUpdates); // disable B mutation
+            JudgementRules jrules = new JudgementRules(0.0, JudgementRules.ConstantEquivalent, 0.0);
+
+            Evolvability.EnvironmentTransitionConfiguration config = new Evolvability.EnvironmentTransitionConfiguration(generations, samplePeriod, drules, rrules, jrules);
+
+            int repeats = 1000;
+
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            StringBuilder maTable = new StringBuilder();
+
+            int unsulliedMutantAnalysisPeriod = mutantSamplePeriod;
+            int mutantAnalysisPeriod = (int)Math.Ceiling((double)unsulliedMutantAnalysisPeriod / config.SamplePeriod) * config.SamplePeriod;
+            void subSampler(int generation, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement, string name, string outDir, Func<string, string> file)
+            {
+                if (!noMA && generation % mutantAnalysisPeriod == 0)
+                {
+                    Console.WriteLine("(MutantAnalysis" + generation + ")");
+                    MutantAnalysis ma = new MutantAnalysis(genome, 100000, context, rrules, drules, jrules, newTarget);
+                    ma.Plot(outDir, name + "MA" + generation, name + " MA" + generation + ": ");
+                    MiscPlotting.PlotVectorFolded(name + " IS" + generation + ": ", file("IS" + generation), genome.InitialState);
+
+                    int pCount = ma.FitnessChanges.Count(df => df > 0);
+                    double pAvg = pCount > 0 ? ma.FitnessChanges.Where(df => df > 0).Average() * pCount / (double)ma.FitnessChanges.Length : 0.0;
+                    maTable.AppendLine(generation + "\t" + pAvg + "\t" + pCount);
+                }
+            }
+
+            string topDir = prefix + "_" + Misc.NowTime;
+
+            Evolvability.SimpleSampler fs = new Evolvability.SimpleSampler(topDir, "Dense", subSampler);
+            Evolvability.SimpleSampler hs = new Evolvability.SimpleSampler(topDir, "Column", subSampler);
+
+            maTable.AppendLine("Dense");
+            var fr = Evolvability.RunManyEnvironmentTransitionExperiments(context, repeats, fox, newTarget, fs, config, true);
+            maTable.AppendLine("Column");
+            var hr = Evolvability.RunManyEnvironmentTransitionExperiments(context, repeats, husky4, newTarget, hs, config, true);
+
+            Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string> ft = new Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string>(fr, OxyColors.Blue, "Dense");
+            Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string> ht = new Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string>(hr, OxyColors.Red, "Column");
+
+            Evolvability.PlotAreas(new[] { ht, ft }, topDir + "\\ehst.pdf", prefix + " Fitness Distribution (Ehst)", 1);
+            Evolvability.PlotAreas(new[] { ht, ft }, topDir + "\\dfjs.pdf", $"Distribution of Fitness Trajectories (C_{{G}} = {gvecUpdates})", 1);
+
+            using (var cw = new System.IO.StreamWriter(topDir + "\\config.txt"))
+            {
+                cw.WriteLine("# Config for " + topDir);
+
+                config.WriteOut(cw);
+            }
+
+            System.IO.File.WriteAllText(topDir + "\\matab.txt", maTable.ToString());
+        }
+
+
+        public static void Type2PopulationEvolvabilityTest4(int gvecUpdates, int populationSize)
+        {
+            VectorTarget oldTarget = new VectorTarget("+---", "o");
+            VectorTarget newTarget = new VectorTarget("-+++", "n");
+            DenseGenome fox = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("Aaaa", 0.25, 0.25, 0.25));
+            DenseGenome husky4 = new DenseGenome(oldTarget.Vector, Extensions.CreateGRS("Aaaa", 0.0, 1.0, 1.0));
+
+            PopulationEvolvabilityTest("evptest4type3_" + "g" + gvecUpdates + "p" + populationSize, 500, 5, 100, oldTarget, newTarget, fox, husky4, populationSize, gvecUpdates, 0.1 / Math.Sqrt(gvecUpdates), true);
+        }
+
+        public static void PopulationEvolvabilityTest(string prefix, int generations, int samplePeriod, int mutantSamplePeriod, VectorTarget oldTarget, VectorTarget newTarget, DenseGenome fox, DenseGenome husky4, int populationSize, int gvecUpdates = 1, double gMutationMagnitude = 0.1, bool noMA = false)
+        {
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            //ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            ReproductionRules rrules = new ReproductionRules(gMutationMagnitude, 0, 0, false, gvecUpdates); // disable B mutation
+            JudgementRules jrules = new JudgementRules(0.0, JudgementRules.ConstantEquivalent, 0.0);
+
+            Evolvability.EnvironmentTransitionConfiguration config = new Evolvability.EnvironmentTransitionConfiguration(generations, samplePeriod, drules, rrules, jrules);
+            Evolvability.EnvironmentPopulationTransitionConfiguration popConfig = new Evolvability.EnvironmentPopulationTransitionConfiguration(config, SelectorPreparers<DenseIndividual>.Ranked);
+
+            int repeats = 1000;
+
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            StringBuilder maTable = new StringBuilder();
+
+            int unsulliedMutantAnalysisPeriod = mutantSamplePeriod;
+            int mutantAnalysisPeriod = (int)Math.Ceiling((double)unsulliedMutantAnalysisPeriod / config.SamplePeriod) * config.SamplePeriod;
+            void subSampler(int generation, DenseGenome genome, Phenotype p, MultiMeasureJudgement judgement, string name, string outDir, Func<string, string> file)
+            {
+                if (!noMA && generation % mutantAnalysisPeriod == 0)
+                {
+                    Console.WriteLine("(MutantAnalysis" + generation + ")");
+                    MutantAnalysis ma = new MutantAnalysis(genome, 100000, context, rrules, drules, jrules, newTarget);
+                    ma.Plot(outDir, name + "MA" + generation, name + " MA" + generation + ": ");
+                    MiscPlotting.PlotVectorFolded(name + " IS" + generation + ": ", file("IS" + generation), genome.InitialState);
+
+                    int pCount = ma.FitnessChanges.Count(df => df > 0);
+                    double pAvg = pCount > 0 ? ma.FitnessChanges.Where(df => df > 0).Average() * pCount / (double)ma.FitnessChanges.Length : 0.0;
+                    maTable.AppendLine(generation + "\t" + pAvg + "\t" + pCount);
+                }
+            }
+
+            string topDir = prefix + "_" + Misc.NowTime;
+
+            Evolvability.SimpleSampler fs = new Evolvability.SimpleSampler(topDir, "Dense", subSampler);
+            Evolvability.SimpleSampler hs = new Evolvability.SimpleSampler(topDir, "Column", subSampler);
+
+            int exposureEpoch = 0;
+
+            maTable.AppendLine("Dense");
+            var fr = Evolvability.RunManyEnvironmentPopulationTransitionExperiments(context, repeats, fox, populationSize, newTarget, fs, popConfig, true, exposureEpoch);
+            maTable.AppendLine("Column");
+            var hr = Evolvability.RunManyEnvironmentPopulationTransitionExperiments(context, repeats, husky4, populationSize, newTarget, hs, popConfig, true, exposureEpoch);
+
+            Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string> ft = new Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string>(fr, OxyColors.Blue, "Dense");
+            Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string> ht = new Tuple<Evolvability.EnvironmentTransitionResults[], OxyColor, string>(hr, OxyColors.Red, "Column");
+
+            Evolvability.PlotAreas(new[] { ht, ft }, topDir + "\\ehstp.pdf", prefix + " Fitness Distribution (Ehst)", 1);
+            Evolvability.PlotAreas(new[] { ht, ft }, topDir + "\\dfjsp.pdf", $"Distribution of Fitness Trajectories, Population of {populationSize} (C_{{G}} = {gvecUpdates})", 1);
+
+            using (var cw = new System.IO.StreamWriter(topDir + "\\config.txt"))
+            {
+                cw.WriteLine("# Config for " + topDir);
+
+                popConfig.WriteOut(cw);
+            }
+
+            System.IO.File.WriteAllText(topDir + "\\matab.txt", maTable.ToString());
+        }
+
+        public static void DiffModExperiment()
+        {
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("+------", "a"),
+                new VectorTarget("-++----", "b"),
+                new VectorTarget("---++++", "c"),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 10000;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(0.1, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "DiffMods";
+            Experiment.RunExperiment(rand, config, "DiffMods_" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false);
+        }
+
+        public static void L1SpikyTargetCountingExperiments()
+        {
+            Dictionary<Tuple<double, double>, string> Terminals = new Dictionary<Tuple<double, double>, string>();
+
+            string topDir = "STCExp2";
+
+            int repeats = 100;
+            double huskyThreshold = 0.6;
+
+            int[] beamCheck = new int[4];
+            int[] huskyBeamCheck = new int[4];
+
+            foreach (double ε in new[] { 0.0, 0.01, 0.02, 0.05, 0.10 })
+            {
+                foreach (var l in new[] { 0.0, 0.1, 0.2, 0.3 })
+                {
+                    int zeroHuskies = 0;
+                    int huskies = 0;
+                    for (int i = 0; i < repeats; i++)
+                    {
+                        var t = L1SpikyTargetExperiment(topDir, "ST:", ε, l, 0.001, 200, i > 0); // print one copy
+                        var ma = new ModuleAnalysis(t.Genome);
+                        if (ma.Huskyness > huskyThreshold)
+                        {
+                            huskies++;
+
+                            if (ma.BeamIndex == 0) // ε is at zero
+                            {
+                                zeroHuskies++;
+                            }
+
+                            huskyBeamCheck[ma.BeamIndex]++;
+                        }
+
+                        beamCheck[ma.BeamIndex]++;
+                    }
+
+                    Terminals.Add(new Tuple<double, double>(ε, l), $"{zeroHuskies}/{huskies}");
+                }
+            }
+
+            // print out stuff
+            using (var cw = new System.IO.StreamWriter(System.IO.Path.Combine(topDir, "STCReport.txt")))
+            {
+                cw.WriteLine("Repeats: " + repeats);
+                cw.WriteLine("Husky Threshold: " + huskyThreshold);
+                cw.WriteLine("BeamCheck: " + string.Join(", ", beamCheck));
+                cw.WriteLine("HuskyBeamCheck: " + string.Join(", ", huskyBeamCheck));
+                WriteStuff(cw, Terminals, "ε", "L");
+            }
+        }
+
+        public static void L1SpikyTargetExperiments()
+        {
+            Dictionary<Tuple<double, double>, ModuleAnalysis> Terminals = new Dictionary<Tuple<double, double>, ModuleAnalysis>();
+
+            string topDir = "STExp2";
+
+            foreach (double ε in new[] { 0.0, 0.1, 0.2, 0.3, 1.0 })
+            {
+                foreach (var l in new[] { 0.1, 0.2, 0.3 })
+                {
+                    var t = L1SpikyTargetExperiment(topDir, "ST:", ε, l, 0.001, 200);
+                    Terminals.Add(new Tuple<double, double>(ε, l), new ModuleAnalysis(t.Genome));
+                }
+            }
+
+            string cstr(ModuleAnalysis ma)
+            {
+                return ma.BeamIndex + "@" + ma.Huskyness + "/" + ma.SnowflakeBeamRatio;
+            }
+
+            // print out stuff
+            using (var cw = new System.IO.StreamWriter(System.IO.Path.Combine(topDir, "STReport.txt")))
+            {
+                cw.WriteLine("beamIndex@huskyness/snowlakeBeamRatio");
+                WriteStuff(cw, Terminals.ToDictionary(k => k.Key, k => cstr(k.Value)), "ε", "L");
+            }
+        }
+
+        public static TerminalInfo L1SpikyTargetExperiment(string topDir, string titlePrefix, double ε, double lambda = 0.0, double M_B = 0.001, int epochs = 200, bool disableIO = false)
+        {
+            if (!System.IO.Directory.Exists(topDir))
+            {
+                System.IO.Directory.CreateDirectory(topDir);
+            }
+
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget(new[] { 1.0 + ε, 1.0, 1.0, 1.0 }, "T+"),
+                new VectorTarget(new[] { -1.0 - ε, -1.0, -1.0, -1.0 }, "T-"),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, M_B, 1, false);
+            JudgementRules jrules = new JudgementRules(lambda, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "L1_" + lambda.ToString("0.00") + "_E" + ε.ToString("0.00");
+            string dir = System.IO.Path.Combine(topDir, "Exp_" + catchyName);
+            var terminals = Experiment.RunExperiment(rand, config, dir, titlePrefix: titlePrefix + catchyName + " ", bigSaveCount: 5, openTerminalsPlots: false, disableIO: disableIO);
+
+            return terminals;
+        }
+
+        public static void FoxHuskyExperiments()
+        {
+            Dictionary<Tuple<double, double>, TerminalInfo> Terminals = new Dictionary<Tuple<double, double>, TerminalInfo>();
+
+            string topDir = "FHExp1";
+
+            foreach (var mb in new[] { 0.01, 0.001, 0.0001 })
+                foreach (var l in new[] { 0.1, 0.2, 0.3, 0.32, 0.35 })
+                {
+                    var t = FoxHuskyExperiment(topDir, "FHL:", l, mb);
+                    Terminals.Add(new Tuple<double, double>(mb, l), t);
+                }
+
+            // print out stuff
+            using (var cw = new System.IO.StreamWriter(System.IO.Path.Combine(topDir, "FHReport.txt")))
+            {
+                WriteStuff(cw, Terminals.ToDictionary(k => k.Key, k => k.Value.Fitness.ToString()), "M_B", "L");
+            }
+        }
+
+        public static TerminalInfo FoxHuskyExperiment(string topDir, string titlePrefix, double lambda, double M_B)
+        {
+            if (!System.IO.Directory.Exists(topDir))
+            {
+                System.IO.Directory.CreateDirectory(topDir);
+            }
+
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("++--", "3_2"),
+                new VectorTarget("++++", "3_3"),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 1000;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, M_B, 1, false);
+            JudgementRules jrules = new JudgementRules(lambda, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "L1_" + lambda.ToString("0.00") + "_MB" + M_B.ToString("0.0000");
+            string dir = System.IO.Path.Combine(topDir, "Exp_" + catchyName);
+            var terminals = Experiment.RunExperiment(rand, config, dir, titlePrefix: titlePrefix + catchyName + " ", bigSaveCount: 5, openTerminalsPlots: false);
+
+            return terminals;
+        }
+
+
+
+        public static void TestExperiment_SoloTarget()
+        {
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("++++", "3_3"),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 300;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(0.0, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "Solo4";
+            Experiment.RunExperiment(rand, config, "SoloExp_" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false, mutantAnalysisPeriod: 20);
+        }
+
+        public static void TestExperiment()
+        {
+            VectorTarget[] allMTargets = new[]
+            {
+                new VectorTarget("    ", "0_0"), // netural
+
+                new VectorTarget("  --", "1_0"), // what happens when we only select on one module at a time?
+                new VectorTarget("  ++", "1_1"),
+
+                new VectorTarget("--  ", "2_0"),
+                new VectorTarget("++  ", "2_1"),
+
+                new VectorTarget("----", "3_0"),
+                new VectorTarget("--++", "3_1"),
+                new VectorTarget("++--", "3_2"),
+                new VectorTarget("++++", "3_3"),
+            };
+
+            //Target[] allMTargets = new[]
+            //{
+            //    new Target("         ", "0_0"), // netural
+
+            //    new Target("  --     ", "1_0"), // what happens when we only select on one module at a time?
+            //    new Target("  ++     ", "1_1"),
+
+            //    new Target("--       ", "2_0"),
+            //    new Target("++       ", "2_1"),
+
+            //    new Target("----  +++", "3_0"),
+            //    new Target("--++  ---", "3_1"),
+            //    new Target("++--  ---", "3_2"),
+            //    new Target("++++  +++", "3_3"),
+            //};
+
+            VectorTarget[] mtargets = new[]
+            {
+                allMTargets[5],
+                allMTargets[6],
+
+                allMTargets[7],
+                allMTargets[8],
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 300;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(0.0, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "SlowB";
+            Experiment.RunExperiment(rand, config, "TestExp_" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false);
+        }
+
+        public static void HierarchyForProfitExperiments()
+        {
+            // first, let's ignore gv and k
+            //HierarchyForProfitExperiment(300, 1, false, 0.1, 20_000);
+            //HierarchyForProfitExperiment(300, 1, true, 0.1, 20_000);
+
+            // ok, reduce K a lot
+            //HierarchyForProfitExperiment(300, 1, false, 0.1, 2_000);
+            //HierarchyForProfitExperiment(300, 1, true, 0.1, 2_000);
+
+            // eerrmm, go all out
+            HierarchyForProfitExperiment(1000, 4, false, 0.1, 2_000);
+            HierarchyForProfitExperiment(1000, 4, true, 0.1, 2_000);
+        }
+
+        public static void HierarchyForProfitExperiment(int epochs, int gv, bool intraModuleColumnConstraint, double λ, int K = 20_000)
+        {
+            VectorTarget[] allMTargets = new[]
+            {
+                //          Typ1Typ1Typ2Typ3
+                new VectorTarget("----------------", "llll"),
+                new VectorTarget("----++++++++----", "lhhl"),
+                new VectorTarget("++++++++--------", "hhll"),
+                new VectorTarget("++++----++++----", "hlhl"),
+            };
+
+            VectorTarget[] mtargets = new[]
+            {
+                allMTargets[0],
+                allMTargets[1],
+                allMTargets[2],
+                allMTargets[3],
+            };
+
+            // config
+            int N = mtargets[0].Size;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.1 / (15 * N * N), 1, false, gv);
+            JudgementRules jrules = new JudgementRules(λ, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            ITransMatMutator tmm = intraModuleColumnConstraint ? Mutators.Columns(16, 4) : null;
+
+            DenseGenome genome = DenseGenome.CreateDefaultGenomeDense(N, tmm);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = $"gv{gv}λ{λ}c{intraModuleColumnConstraint}";
+            Experiment.RunExperiment(rand, config, "HFP_" + catchyName, titlePrefix: "HFP" + catchyName + " ", openTerminalsPlots: false, startingGenome: genome);
+        }
+
+        public static void FourTwoFourWheelers()
+        {
+            //foreach (double λ in new[] { 0.1, 0.2, 0.5, 1.0, 5.0, 0.0 })
+            foreach (double λ in new[] { 0.0, 0.01, 0.1, 1.0 })
+                FourTwoFourWheeler(λ);
+        }
+
+        private class ClampedJudger : IVectorTargetJudger
+        {
+            public string Name => "ClampedJudger";
+
+            public string Description => $"ClampedJudger (Magnitude={Magnitude})";
+
+            public double Magnitude { get; }
+
+            public double Judge(Linear.Vector<double> vector, Phenotype phenotype)
+            {
+                return vector.DotProduct(phenotype.Vector.Map(te => ReproductionRules.ClampZeroOne.Clamp(te), Linear.Zeros.Include));
+            }
+        }
+
+        public static void FourTwoFourWheeler(double λ)
+        {
+            IVectorTargetJudger clampedJudger = new ClampedJudger();
+
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("+---     ", "1.of4in9", clampedJudger),
+                new VectorTarget("-+--     ", "2.of4in9", clampedJudger),
+                new VectorTarget("--+-     ", "3.of4in9", clampedJudger),
+                new VectorTarget("---+     ", "4.of4in9", clampedJudger),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 300;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false, 1);
+            JudgementRules jrules = new JudgementRules(λ, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "4in9PoziCJλ" + λ;
+            Experiment.RunExperiment(rand, config, "FourTwoFour" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false, bigSaveCount: 50);
+        }
+
+        public static void Ands()
+        {
+            foreach (double λ in new[] { 0.0, 0.01, 0.1, 1.0 })
+                And(λ);
+        }
+
+        public static void And(double λ)
+        {
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("---------", "L0R0"),
+                new VectorTarget("+++------", "L1R0"),
+                new VectorTarget("---+++---", "L0R1"),
+                new VectorTarget("+++++++++", "L1R1"),
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 300;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false, 1);
+            JudgementRules jrules = new JudgementRules(λ, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.RandomNoRepeat, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "Andλ" + λ;
+            Experiment.RunExperiment(rand, config, "And" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false, bigSaveCount: 50);
+        }
+
+        public static void Paushes()
+        {
+            //foreach (var λ in new[] { 0.0, 0.01, 0.1, 1.0 })
+            foreach (var λ in new[] { 0.0, 0.01, 0.1, 0.2, 0.3, 0.5, 0.8, 1.0 })
+                Paush(λ);
+        }
+
+        public static void Paush(double λ)
+        {
+            // I think this is basically the changes needed... I don't need to write any new code! :D
+
+            VectorTarget[] mtargets = new[]
+            {
+                new VectorTarget("++++++---", "L1R0"),
+                new VectorTarget("------+++", "L0R1"),
+                new VectorTarget("---------", "L0R0"),
+                new VectorTarget("+++---+++", "L1R1"), // don't need a special judger, because drules and rrules ensure the Phenotype will only contain posistive trait expressions
+            };
+
+            // config
+            int N = mtargets[0].Size;
+            int epochs = 3000;
+            int K = 100;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.MichaelisMentenPaush);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false, 1, ReproductionRules.ClampZeroOne);
+            JudgementRules jrules = new JudgementRules(λ, JudgementRules.L1Equivalent, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, mtargets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "LMRShortK_λ" + λ;
+            Experiment.RunExperiment(rand, config, "Paush" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false, bigSaveCount: 20, bigSavePeriod: 1000);
+        }
+
+
+        public static void SharingExperiments()
+        {
+            SharingExperiment(0.01, true, JudgementRules.L2Equivalent);
+            SharingExperiment(0.001, true, JudgementRules.L2Equivalent);
+            SharingExperiment(0.1, true, JudgementRules.L2Equivalent);
+            //SharingExperiment(0.1, true, JudgementRules.L2Equivalent);
+            //SharingExperiment(0.1, false, JudgementRules.L2Equivalent);
+            //SharingExperiment(1.0, true, JudgementRules.L2Equivalent);
+            //SharingExperiment(1.0, false, JudgementRules.L2Equivalent);
+        }
+
+        public static void SharingExperiment(double λ, bool sharing, IRegularisationFunction<IGenome> regularisationFunction)
+        {
+            VectorTarget[] targets = new[]
+            {
+                new VectorTarget("----", "L0R0"),
+                new VectorTarget("--++", "L0R1"),
+                new VectorTarget("++++", "L1R1"),
+                new VectorTarget("++--", "L1R0"),
+            };
+
+            // config
+            int N = targets[0].Size;
+            int epochs = 300;
+            int K = 1000;
+
+            DevelopmentRules drules = new DevelopmentRules(10, 1.0, 0.2, DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(0.1, 0.001, 1, false);
+            JudgementRules jrules = new JudgementRules(λ, regularisationFunction, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            DenseGenome genome = DenseGenome.CreateDefaultGenome(N, customDevelopmentStep: sharing ? Development.SharingDevelopmentStep(N) : Development.DefaultDevelopmentStep(N));
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+
+            // go
+            string catchyName = "S" + sharing + "λ" + λ + regularisationFunction.Name;
+            Experiment.RunExperiment(rand, config, "Sharing_" + catchyName, titlePrefix: catchyName + " ", openTerminalsPlots: false, bigSavePeriod: 30, bigSaveCount: 3, startingGenome: genome);
+        }
+
+        public static void PopulationTests()
+        {
+            // TODO: varying gClamp
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 5000, K: 1000, columns: false, T: 1, gClamp: new Range(-0.5, 0.5)); // same config: nope
+            //
+
+            // 3x3
+            //PopulationTest(ModularTargetPackage.Standard3x3, 0.1, false, 0.1, 2000, 600, false, 1, 1, 0, false); // nah...
+            //PopulationTest(ModularTargetPackage.Standard3x3, 0.1, false, 0.1, 5000, 600, true, 1, 1, 0, false); // eh...
+            //PopulationTest(ModularTargetPackage.Standard3x3, 0.1, false, 0.1, 5000, 800, true, 1, 1, 0, false); // yes, fuzzy
+            //PopulationTest(ModularTargetPackage.Standard3x3, 0.1, false, 0.1, 5000, 1000, true, 1, 1, 0, false); // yes
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 5000, K: 1200, columns: true, T: 1); // just about
+            //
+
+            // col -> !col, config otherwise the same
+            var colSample = LoadGenome(@"..\Release\PopTestStd3x3_λ0.1EpiFalseNoise0.1E5000K1200CT1PF_20180424T195904\genome5000.dat");
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 5000, K: 1000, columns: false, T: 1, g0: colSample.InitialState, b0: colSample.TransMat); // same config: nope
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 5000, K: 1000, columns: false, T: 1, g0: colSample.InitialState, b0: colSample.TransMat, popSize: 25); // larger population: switches faster
+            //PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, noiseTerm: 0.1, epochs: 5000, K: 500, columns: false, T: 1, g0: colSample.InitialState, b0: colSample.TransMat, popSize: 25); // larger population with shorter K:
+
+            PopulationTest(ModularTargetPackage.Standard3x3, λ: 0.1, epigenetic: false, gNoiseTerm: 0.1, epochs: 5000, K: 600, columns: true, T: 1, g0: colSample.InitialState, b0: colSample.TransMat, popSize: 200); // larger population with shorter K:
+                                                                                                                                                                                                                       //
+
+
+            // TODO: run a few of these for good luck
+            //for (int i = 0; i < 5; i++)
+            //{
+            //    PopulationTest(ModularTargetPackage.Standard3x3, 0.1, false, 0.1, 5000, 1000, true, 1, 1, null, false); // yes
+            //}
+            //
+
+            // s8
+            //PopNotExperiment(ModularTargetPackage.Single8, 0.1, 0.1, 1000, 500, false, 10, false, 1, 0, true, false);
+            //PopulationTest(ModularTargetPackage.Single8, 0.1, false, 0.1, 1000, 500, false, 10, 1, 0, false);
+            //
+
+
+            // COLUMN
+
+            // 2x2
+            // Column Time
+            //PopulationTest(0.2, false, 0.1, 5000, 100, true); // nothing
+            //PopulationTest(0.2, false, 0.1, 5000, 125, true); // jumped the pit of dispair, too late to see any progress
+            //PopulationTest(0.2, false, 0.1, 5000, 200, true); // hierarchy
+            //PopulationTest(0.2, false, 0.1, 5000, 500, true); // hierarchy (1 switch)
+            //PopulationTest(0.2, false, 0.1, 5000, 1000, true); // hierarhcy (many switches)
+
+            // pop-not equivalents (shouldn't evolve hierarchy, because it can't exploit it)
+            //PopNotExperiment(0.2, 0.1, 5000, 100, true); // nothing
+            //PopNotExperiment(0.2, 0.1, 5000, 125, true); // nothing
+            //PopNotExperiment(0.2, 0.1, 5000, 200, true); // nothing
+            //PopNotExperiment(0.2, 0.1, 5000, 500, true); // ... iffy hierarchy...
+            //PopNotExperiment(0.2, 0.1, 5000, 1000, true); // seems to evolve hierarchy... and then give up on it
+            // end 2x2
+
+            // 4x4
+            //PopulationTest(0.2, false, 0.1, 10000, 500, true); // no
+            //PopulationTest(0.2, false, 0.1, 10000, 800, true); // yes
+            //PopulationTest(0.2, false, 0.1, 10000, 1000, true); // yes
+            //PopulationTest(0.2, false, 0.1, 10000, 1500, true); // yes, subordinates generally complete switching
+
+            //PopNotExperiment(0.2, 0.1, 10000, 500, true); // not happening (can't get past 0.5 fitness: too much B? L1 divides by N^2, so no, probably not)
+            //PopNotExperiment(0.2, 0.1, 10000, 800, true); // not happening (can't get past 0.5 fitness: too much B?)
+            //PopNotExperiment(0.2, 0.1, 10000, 1000, true); // not happening (can't get past 0.5 fitness: too much B?)
+            //PopNotExperiment(0.2, 0.1, 10000, 1500, true); // not happening (can't get past 0.5 fitness: too much B?)
+            //end 4x4
+
+            // 4x1
+            //PopulationTest(0.2, false, 0.1, 2000, 250, true); // no
+            //PopulationTest(0.2, false, 0.1, 2000, 500, true); // yes
+            //PopulationTest(0.2, false, 0.1, 2000, 800, true); // yes
+            //PopulationTest(0.2, false, 0.1, 2000, 1000, true); // yes
+
+            //PopNotExperiment(0.2, 0.1, 2000, 250, true); // no
+            //PopNotExperiment(0.2, 0.1, 2000, 500, true); // not clear
+            //PopNotExperiment(0.2, 0.1, 2000, 800, true); // looks like it...
+            //PopNotExperiment(0.2, 0.1, 2000, 1000, true); // yeah... probably
+            //PopNotExperiment(0.2, 0.1, 2000, 1600, true, 10, true, 0.5); // very much so
+            //PopNotExperiment(0.2, 0.1, 2000, 3200, true, 10, true, 0.5); // yes
+            //PopNotExperiment(0.2, 0.1, 2000, 6400, true, 10, true, 0.5); // yes
+            //PopNotExperiment(0.2, 0.1, 1000, 100000, true, 10, true, 0.5); // yes
+            //PopNotExperiment(0.2, 0.1, 2000, 1600, true, 10, true, 0.5); // very much so
+            //PopNotExperiment(0.02, 0.1, 100000, 1600, true, 1, true, 0.5); // oh dear
+            //PopNotExperiment(0.02, 0.0, 100000, 1600, true, 1, true, 0.5, 1.0); // hmm
+            //PopNotExperiment(0.02, 0.0, 100000, 1000, true, 1, true, 1, 1.0, true); // oh dear (nightmare)
+            //end 4x1
+
+            // 4x1: not cols, T = 1
+            //PopulationTest(0.2, false, 0.1, 2000, 800, false, 1); // no... in 2k epochs, atleast
+            //PopulationTest(ModularTargetPackage.Standard4x1, 0.02, false, 0.1, 5000, 800, false, 1); // kinda...
+            //PopulationTest(ModularTargetPackage.Standard4x1, 0.02, false, 0.1, 5000, 400, false, 1); // absolutely
+            //PopulationTest(ModularTargetPackage.Standard2x2, 0.02, false, 0.1, 5000, 400, false, 1); // run it longer
+            //PopulationTest(ModularTargetPackage.HighJudge4x1, 0.02, false, 0.1, 5000, 800, false, 1); // errr...
+            //PopulationTest(ModularTargetPackage.HighJudge4x1, 0.02, false, 0.1, 5000, 400, false, 1, 1.0, 1.0, true); // not really...
+            //PopulationTest(ModularTargetPackage.Standard2x2, 0.02, false, 0.1, 25000, 400, false, 1);
+            //
+
+            // 4x4s: not cols, T = 1
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.02, false, 0.1, 20000, 800, false, 1);
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.2, false, 0.1, 10000, 1600, false, 1);
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.4, false, 0.1, 1000, 1600, false, 1);
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.8, false, 0.1, 1000, 1600, false, 1);
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.2, false, 0.1, 1000, 1000, false, 1);
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.4, false, 0.1, 1000, 1000, false, 1);
+            //PopulationTest(ModularTargetPackage.Standard4x4, 0.8, false, 0.1, 1000, 1000, false, 1);
+            //
+
+            // a 'traditional' 4x1 run (looking for negative RCSing) (note: using continous B)
+            //PopNotExperiment(0.2, 0.1, 1000, 1600, false, 10, false, 1.0); // not noticable... but we do have ISes going the wrong way
+
+            // 4x1, T=1 (TODO: work out params)
+            //PopulationTest(0.2, false, 0.1, 5000, 250, true, 1);
+            //PopulationTest(0.2, false, 0.1, 5000, 500, true, 1);
+            //PopulationTest(0.2, false, 0.1, 5000, 800, true, 1);
+            //PopulationTest(0.2, false, 0.1, 5000, 1000, true, 1);
+
+            //PopNotExperiment(0.2, 0.1, 5000, 250, true, 1);
+            //PopNotExperiment(0.2, 0.1, 5000, 500, true, 1);
+            //PopNotExperiment(0.2, 0.1, 5000, 800, true, 1);
+            //PopNotExperiment(0.2, 0.1, 5000, 1000, true, 1);
+            //end 4x1, T=1
+
+            // TODO: run these columns with an f-sparse initial genome
+
+            // NOT COLUMN
+
+            //PopulationTest(0.2, false, 0.1, 5000, 1000); // iffy hierarchy, but switches in ~50 generations
+            //PopulationTest(0.2, false, 0.02, 5000, 1000); // beautiful hierarchy, but takes 400 generations to switch
+            //PopulationTest(0.2, false, 0.1, 5000, 100); // just noise (can't evolve properly)
+            //PopulationTest(0.2, false, 0.1, 5000, 200); // good hierarchy, subordinate initial expression doesn't quite switch (nice)
+            //PopulationTest(0.2, false, 0.1, 5000, 150); // reasonable hierarchy, subrodinate initial expressions don't really switch (v. nice)
+
+            // IDEA: it looks like we can encourage a better hierarhcy by switching more often ---> evolving to be evolvable?!
+
+            //PopulationTest(0.2, false, 0.1, 5000, 125);
+            //PopulationTest(0.2, false, 0.1, 5000, 125);
+            //PopulationTest(0.2, false, 0.1, 5000, 500);
+            //PopulationTest(0.2, false, 0.1, 5000, 1000);
+
+            // OK, looks like the previous runs were not indictaive of a clear-cut reality
+
+            //PopulationTest(0.2, false, 0.1, 5000, 150);
+            //PopulationTest(0.2, false, 0.1, 5000, 200);
+            //PopulationTest(0.2, false, 0.1, 5000, 150);
+            //PopulationTest(0.2, false, 0.1, 5000, 200);
+            //PopulationTest(0.2, false, 0.1, 5000, 500);
+
+            // more noise
+
+            //PopulationTest(0.2, false, 0.2, 5000, 200);
+            //PopulationTest(0.2, false, 0.3, 5000, 200);
+            //PopulationTest(0.2, false, 0.4, 5000, 200);
+            //PopulationTest(0.2, false, 0.2, 5000, 500);
+            //PopulationTest(0.2, false, 0.3, 5000, 500);
+            //PopulationTest(0.2, false, 0.4, 5000, 500);
+
+            //PopulationTest(0.2, false, 0.2, 5000, 500);
+
+            // low noise
+
+            //PopulationTest(0.2, false, 0.05, 5000, 100); // excepting ~200gen switch time
+            //PopulationTest(0.2, false, 0.05, 5000, 200);
+            //PopulationTest(0.2, false, 0.05, 5000, 300);
+            //PopulationTest(0.2, false, 0.05, 5000, 500);
+
+            //foreach (double nt in new[] { 0.001, 0.01, 0.1, 0.2, 0.5, 1.0 })
+            //    PopulationTest(0.0, true, nt);
+
+            //PopulationTest(0.0, false);
+            //PopulationTest(0.0, true, 1.0);
+            //PopulationTest(0.0, true, 2.0);
+            //PopulationTest(0.1, false);
+            //PopulationTest(0.1, true);
+            //PopulationTest(1.0, false);
+            //PopulationTest(1.0, true);
+        }
+
+        public static void PopulationTest(ModularTargetPackage targetPackage, double λ, bool epigenetic, double gNoiseTerm = 0.1, int epochs = 5000, int K = 1000, bool columns = false, int T = 10, double bProb = 1.0, Linear.Vector<double> g0 = null, bool gFixed = false, bool paretoSelection = false, Linear.Matrix<double> b0 = null, int popSize = 5, Range gClamp = null, int tracePeriod = 500, double decayRate = 0.2, ISquash squash = null, string superDir = "", double bNoiseTerm = 0.001, int gUpdateCount = 1, ICycler targetCycler = null, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, double gResetProb = 0, Range gResetRange = null, IRegularisationFunction<IGenome> regularisationFunction = null, string customPrefix = null, bool exclusiveBMutation = false, int eliteCount = 0, bool hillclimberMode = false, bool disableTransientIO = false, IPopulationResetOperation<DenseIndividual> populationResetOperation = null, string dirPrefix = "")
+        {
+            bool disableIO = false;
+            disableTransientIO = disableIO || disableTransientIO;
+
+            VectorTarget[] targets = targetPackage.Targets;
+
+            // config
+            int N = targetPackage.TargetSize;
+            int M = targetPackage.ModuleCount;
+
+            gClamp = gClamp ?? new Range(-1.0, 1.0);
+            regularisationFunction = regularisationFunction ?? JudgementRules.L1Equivalent;
+            populationResetOperation = populationResetOperation ?? PopulationResetOperations.RandomIndependent;
+
+            DevelopmentRules drules = new DevelopmentRules(T, 1.0, decayRate, squash ?? DevelopmentRules.TanhHalf);
+            ReproductionRules rrules = new ReproductionRules(gFixed ? 0.0 : (epigenetic ? 0.0 : gNoiseTerm), bNoiseTerm, bProb, exclusiveBMutation, gUpdateCount, gClamp);
+            JudgementRules jrules = new JudgementRules(λ, regularisationFunction, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, targetCycler ?? Cyclers.Loop, epochs, K, gResetProb, gResetRange, false, drules, rrules, jrules);
+
+            var mutator = columns ? Mutators.Columns2(N, M) : null;
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister();
+            RandomSource ioRand = new MathNet.Numerics.Random.MersenneTwister();
+            ModelExecutionContext context = new ModelExecutionContext(rand);
+
+            g0 = g0 ?? Linear.CreateVector.Dense(N, 0.0);
+            b0 = b0 ?? Linear.CreateMatrix.Dense(N, N, 0.0);
+
+            DenseIndividual CreateDefaultIndividual(ModelExecutionContext _context)
+            {
+                var customDevelopmentStep = Development.DefaultDevelopmentStep(N, epigenetic ? Math.Sqrt(gNoiseTerm) : 0.0);
+                var genome = new DenseGenome(g0, b0, gOpenEntries, bOpenEntries, mutator, null, null, null, customDevelopmentStep, null, false);
+                var individual = DenseIndividual.Develop(genome, _context, drules, epigenetic);
+                return individual;
+            }
+
+            // population stuff
+            ISelectorPreparer<DenseIndividual> selector = paretoSelection
+                ? SelectorPreparers<DenseIndividual>.Pareto
+                : SelectorPreparers<DenseIndividual>.Ranked;
+            Population<DenseIndividual> population = new Population<DenseIndividual>(context, _context => CreateDefaultIndividual(_context), popSize);
+            PopulationExperimentConfig<DenseIndividual> popConfig = new PopulationExperimentConfig<DenseIndividual>(config, selector, eliteCount, hillclimberMode, null, populationResetOperation, DefaultPopulationSpinner<DenseIndividual>.Instance);
+            PopulationExperimentFeedback<DenseIndividual> feedback = new DefaultDensePopulationExperimentFeedback(ioRand, popConfig, disableTransientIO, disableIO, Math.Max(1, epochs / 25), 500, tracePeriod, null, Math.Max(1, epochs / 1000), populationResetOperation).Feedback;
+
+            // go
+            string expName = $"PopTest{targetPackage.Name}";
+            string subName = "λ" + λ + "Epi" + epigenetic + "mG" + gNoiseTerm + "mB" + bNoiseTerm + "E" + epochs + "K" + K + "C" + (columns ? "T" : "F") + T + "P" + (paretoSelection ? "T" : "F") + decayRate.ToString(".000") + "G" + gUpdateCount;
+            PopulationExperimentRunners.RunExperiment<DenseIndividual>(System.Console.Out, "", context, population, popConfig, outDir: System.IO.Path.Combine(superDir, dirPrefix + expName + "_" + subName), disableTransientIO: disableTransientIO, feedback: feedback, filePrefix: "", titlePrefix: customPrefix ?? (subName + " "));
+        }
+
+        // doesn't support Epi
+        public static void PopNotExperiment(ModularTargetPackage targetPackage, double λ, double gNoiseTerm = 0.1, int epochs = 5000, int K = 1000, bool columns = false, int T = 10, bool exclusiveB = false, double bProb = 1.0, Linear.Vector<double> g0 = null, bool noRandomIO = false, bool gFixed = false, Linear.Matrix<double> b0 = null, Range gClamp = null, double decayRate = 0.2, double bNoiseTerm = 0.001, int gUpdateCount = 1, ICycler targetCycler = null, IReadOnlyList<int> gOpenEntries = null, IReadOnlyList<MatrixEntryAddress> bOpenEntries = null, IRegularisationFunction<IGenome> regularisationFunction = null, string customPrefix = null, ISquash squash = null, string outDir = "", bool disableTransientIO = false)
+        {
+            bool disableIO = false;
+
+            VectorTarget[] targets = targetPackage.Targets;
+
+            // config
+            int N = targetPackage.TargetSize;
+            int M = targetPackage.ModuleCount;
+
+            gClamp = gClamp ?? new Range(-1.0, 1.0);
+            regularisationFunction = regularisationFunction ?? JudgementRules.L1Equivalent;
+            squash = squash ?? DevelopmentRules.TanhHalf;
+
+            DevelopmentRules drules = new DevelopmentRules(T, 1.0, decayRate, squash);
+            ReproductionRules rrules = new ReproductionRules(gFixed ? 0.0 : gNoiseTerm, bNoiseTerm, bProb, exclusiveB, gUpdateCount, gClamp);
+            JudgementRules jrules = new JudgementRules(λ, regularisationFunction, 0.0);
+
+            ExperimentConfiguration config = new ExperimentConfiguration(N, targets, targetCycler ?? Cyclers.Loop, epochs, K, 0, null, false, drules, rrules, jrules);
+
+            ITransMatMutator tmm = columns ? Mutators.Columns2(N, M) : null;
+
+            g0 = g0 ?? Linear.CreateVector.Dense(N, 0.0);
+            b0 = b0 ?? Linear.CreateMatrix.Dense(N, N, 0.0);
+
+            var genome = new DenseGenome(g0, b0, gOpenEntries, bOpenEntries, tmm, null, null, null, null, null, false);
+
+            // essentials
+            RandomSource rand = new MathNet.Numerics.Random.MersenneTwister(25000);
+            RandomSource ioRand = new MathNet.Numerics.Random.MersenneTwister(25000);
+            //RandomSource rand = new MathNet.Numerics.Random.CryptoRandomSource();
+
+            // go
+            string catchyName = $"PopNot{targetPackage.Name}" + "λ" + λ + "Noise" + gNoiseTerm + "E" + epochs + "K" + K + "C" + (columns ? "T" : "F") + T;
+            outDir = System.IO.Path.Combine(outDir, catchyName);
+            Experiment.RunExperiment(rand, config, outDir, titlePrefix: customPrefix ?? (catchyName + " "), openTerminalsPlots: false, startingGenome: genome, disableIO: disableIO, mutantAnalysisPeriod: 0, bigSaveCount: 10, ioRand: ioRand, disableTransientIO: disableTransientIO);
+        }
+
+        private static void Fear(FileStuff stuff)
+        {
+            //Random rnd = new Random(1000);
+            RandomSource rnd = new MathNet.Numerics.Random.MersenneTwister(25001);
+
+            int N = 4;
+            double[] G = new double[N];
+            double[] B = new double[N];
+            for (int i = 0; i < N; i++)
+            {
+                G[i] = 1;
+                B[i] = 0;
+            }
+
+            double λ = 0.02;
+            int generationsPerEnvironmentPerEpoch = 1000;
+
+            double[] E = new double[] { 1.1, 1 };
+
+            double[][] rcs = new double[N][];
+
+            double[][] trRcs = new double[N][];
+            double[][] trFitDiff = new double[N][];
+            double[][] trFitDiffMean = new double[N][];
+            double[][] trFitDiffNorm = new double[N][];
+
+            int samplePeriod = 100;
+            int tracePeriod = 10000;
+
+            int epochs = 100000;
+
+            for (int i = 0; i < N; i++)
+            {
+                rcs[i] = new double[epochs / samplePeriod + 1];
+
+                trRcs[i] = new double[generationsPerEnvironmentPerEpoch];
+                trFitDiff[i] = new double[generationsPerEnvironmentPerEpoch];
+                trFitDiffMean[i] = new double[generationsPerEnvironmentPerEpoch];
+                trFitDiffNorm[i] = new double[generationsPerEnvironmentPerEpoch];
+            }
+
+            double squash(double x) => Math.Tanh(x * 0.5);
+
+            double fitness(double[] _B, double e)
+            {
+                double weightedSum = 0;
+                for (int i = 0; i < N; i++)
+                {
+                    weightedSum += G[i] * _B[i];
+                }
+
+                double squashed = squash(weightedSum);
+
+                double b = 0;
+                for (int i = 0; i < N; i++)
+                {
+                    b += 0.8 * G[i] + squashed;
+                }
+
+                b /= 5;
+                b = 0.5 * (1.0 + b / N);
+
+                double c = 0;
+                for (int i = 0; i < N; i++)
+                {
+                    c += Math.Abs(_B[i] * N);
+                }
+                c /= (N * N);
+
+                return b * e - c * λ;
+            }
+
+            double f = 0;
+            for (int epoch = 0; epoch <= epochs; epoch++)
+            {
+                bool sampleThis = samplePeriod > 0 && (epoch % samplePeriod == 0);
+                bool traceThis = tracePeriod > 0 && (epoch % tracePeriod == 0);
+
+                foreach (var e in E)
+                {
+                    for (int generation = 0; generation < generationsPerEnvironmentPerEpoch; generation++)
+                    {
+                        rnd.NextDouble(); // burn1
+
+                        double[] B2 = new double[N];
+                        for (int i = 0; i < N; i++)
+                            B2[i] = Misc.Clamp(B[i] + rnd.NextSignedUniform(0.001), double.NegativeInfinity, double.PositiveInfinity); // clamping (or not)
+
+                        f = fitness(B, e);
+                        double f2 = fitness(B2, e);
+
+                        if (f2 > f)
+                        {
+                            B = B2;
+                            f = f2;
+                        }
+
+                        if (traceThis)
+                        {
+                            // fit diff
+                            double[] bNudged = B.ToArray();
+                            double baseFitness = fitness(bNudged, E.Last());
+
+                            double[] fdiff = new double[N];
+                            for (int i = 0; i < N; i++)
+                            {
+                                double dC = 0.0001;
+                                double old = bNudged[i];
+
+                                trRcs[i][generation] = B[i];
+
+                                bNudged[i] -= dC;
+                                trFitDiff[i][generation] = fdiff[i] = (fitness(bNudged, e) - baseFitness) / -dC;
+                                bNudged[i] = old;
+                            }
+
+                            fdiff.MeanCentreInplace();
+
+                            for (int i = 0; i < N; i++)
+                            {
+                                trFitDiffMean[i][generation] = fdiff[i];
+                            }
+
+                            fdiff.NormaliseInplace();
+
+                            for (int i = 0; i < N; i++)
+                            {
+                                trFitDiffNorm[i][generation] = fdiff[i];
+                            }
+                            //
+                        }
+                    }
+
+                    if (traceThis)
+                    {
+                        PlotLines(stuff.Title("TrRcs" + epoch + e), stuff.File("TrRcs" + epoch + e + ".pdf"), trRcs, 0, samplePeriod, xTitle: "Epoch", yTitle: "Weight");
+                        PlotLines(stuff.Title("TrFitDiff" + epoch + e), stuff.File("TrFitDiff" + epoch + e + ".pdf"), trFitDiff, 0, samplePeriod, xTitle: "Epoch", yTitle: "dFitness/dColumn");
+                        PlotLines(stuff.Title("TrFitDiffMean" + epoch + e), stuff.File("TrFitDiffMean" + epoch + e + ".pdf"), trFitDiffMean, 0, samplePeriod, xTitle: "Epoch", yTitle: "dFitness/dColumn (l1 mean centered)");
+                        PlotLines(stuff.Title("TrFitDiffNorm" + epoch + e), stuff.File("TrFitDiffNorm" + epoch + e + ".pdf"), trFitDiffNorm, 0, samplePeriod, xTitle: "Epoch", yTitle: "dFitness/dColumn (l1 mc'd & normed)");
+                    }
+                }
+
+                if (sampleThis)
+                {
+                    int k = epoch / samplePeriod;
+
+                    for (int i = 0; i < N; i++)
+                    {
+                        rcs[i][k] = B[i];
+                    }
+
+                    double p = 100 * (double)epoch / epochs;
+                    Console.WriteLine(epoch + " " + p.ToString("00.0") + "% " + f);
+                }
+
+                if (epoch % 10000 == 0)
+                {
+                    PlotLines(stuff.Title("nrcs" + epoch), stuff.File("nrcs" + epoch + ".pdf"), rcs, 0, samplePeriod, xTitle: "Epoch", yTitle: "Weight");
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/OneModule.cs b/M4MCode/M4M_MkI/M4M.Old/OneModule.cs
new file mode 100644
index 0000000..da8fdf0
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/OneModule.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Linear = MathNet.Numerics.LinearAlgebra;
+
+namespace M4M
+{
+    public class OneModule
+    {
+        public static Linear.Vector<double> PrepareG(int k, double ys, double yr)
+        {
+            return Linear.CreateVector.Dense(k + 1, i => i == 0 ? ys : yr);
+        }
+
+        public static Linear.Matrix<double> PrepareB(int k, double s, double r, double q)
+        {
+            return Linear.CreateMatrix.Dense(k + 1, k + 1, (i, j) => j == 0 ? i == 0 ? s : r : q);
+        }
+
+        public static VectorTarget PrepareE(int k, double es, double er, IVectorTargetJudger customJudger = null)
+        {
+            var vec = Linear.CreateVector.Dense(k + 1, i => i == 0 ? es : er);
+            return new VectorTarget(vec, $"E{k} {es} {er}", customJudger);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/OxyPlotting.cs b/M4MCode/M4M_MkI/M4M.Old/OxyPlotting.cs
new file mode 100644
index 0000000..2917870
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/OxyPlotting.cs
@@ -0,0 +1,354 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static Plotting.Sillyness;
+using static Plotting.Misc;
+
+namespace Plotting
+{
+    public static partial class Misc
+    {
+        public static Tuple<TL, TR> Label<TL, TR>(TL l, TR r)
+        {
+            return new Tuple<TL, TR>(l, r);
+        }
+    }
+
+    public static class OxyPlotting
+    {
+        public static OxyPlotModelShim Space(string title)
+        {
+            return new OxyPlotModelShim(title);
+        }
+
+        public static OxyPlotModelShim Plot(string title, Func<double, double> func, double x0, double x1, int count)
+        {
+            var opms = Space(title);
+
+            opms.LinearX();
+            opms.LinearY();
+
+            opms.PlotLine(title, func, x0, x1, count);
+
+            // no legend
+
+            opms.Sillyify();
+
+            return opms;
+        }
+
+        public static OxyPlotModelShim Plot(string title, double[] x, double[] y)
+        {
+            var opms = Space(title);
+
+            opms.LinearX();
+            opms.LinearY();
+
+            opms.PlotLine(title, x.Zip(y, (a, b) => Label(a, b)));
+
+            // no legend
+
+            opms.Sillyify();
+
+            return opms;
+        }
+    }
+
+    public class OxyPlotModelShim
+    {
+        public OxyPlot.PlotModel PlotModel { get; }
+
+        public string Title
+        {
+            get
+            {
+                return PlotModel.Title;
+            }
+            set
+            {
+                PlotModel.Title = value;
+            }
+        }
+
+        public OxyPlotModelShim(string title)
+        {
+            PlotModel = new OxyPlot.PlotModel() { Title = title };
+        }
+
+        public void LinearX(string label = "X", double paddingLow = 0, double paddingHigh = 0, string key = "x", bool flip = false)
+        {
+            var axis = new OxyPlot.Axes.LinearAxis() { Key = key, Title = label, Position = OxyPlot.Axes.AxisPosition.Bottom, IsPanEnabled = false, IsZoomEnabled = false };
+
+            if (flip)
+            {
+                axis.StartPosition = 1;
+                axis.EndPosition = 0;
+            }
+
+            PlotModel.Axes.Add(axis);
+        }
+
+        public void LinearY(string label = "Y", double paddingLow = 0, double paddingHigh = 0, string key = "y", bool flip = false)
+        {
+            var axis = new OxyPlot.Axes.LinearAxis() { Key = key, Title = label, Position = OxyPlot.Axes.AxisPosition.Left, IsPanEnabled = false, IsZoomEnabled = false };
+
+            if (flip)
+            {
+                axis.StartPosition = 1;
+                axis.EndPosition = 0;
+            }
+
+            PlotModel.Axes.Add(axis);
+        }
+
+        public void LogarithmicX(string label = "X", double paddingLow = 0, double paddingHigh = 0, string key = "x", bool flip = false)
+        {
+            var axis = new OxyPlot.Axes.LogarithmicAxis() { Key = key, Title = label, Position = OxyPlot.Axes.AxisPosition.Bottom, IsPanEnabled = false, IsZoomEnabled = false };
+            
+            if (flip)
+            {
+                axis.StartPosition = 1;
+                axis.EndPosition = 0;
+            }
+
+            PlotModel.Axes.Add(axis);
+        }
+
+        public void LogarithmicY(string label = "Y", double paddingLow = 0, double paddingHigh = 0, string key = "y", bool flip = false)
+        {
+            var axis = new OxyPlot.Axes.LogarithmicAxis() { Key = key, Title = label, Position = OxyPlot.Axes.AxisPosition.Left, IsPanEnabled = false, IsZoomEnabled = false };
+            
+            if (flip)
+            {
+                axis.StartPosition = 1;
+                axis.EndPosition = 0;
+            }
+
+            PlotModel.Axes.Add(axis);
+        }
+
+        public void Colours(string label = "Colours", string key = "c")
+        {
+            var axis = new OxyPlot.Axes.LinearColorAxis() { Key = key, Title = label, Position = OxyPlot.Axes.AxisPosition.Right, IsPanEnabled = false, IsZoomEnabled = false };
+            PlotModel.Axes.Add(axis);
+        }
+
+        public void ColoursGrey(string key = "c", double? min = null, double? max = null)
+        {
+            var axis = new OxyPlot.Axes.LinearColorAxis { Key = key, Title = "", Position = OxyPlot.Axes.AxisPosition.Right, IsPanEnabled = false, IsZoomEnabled = false, HighColor = OxyPlot.OxyColors.White, LowColor = OxyPlot.OxyColors.Black, Palette = OxyPlot.OxyPalettes.Gray(100) };
+
+            if (max.HasValue)
+                axis.Maximum = max.Value;
+            if (min.HasValue)
+                axis.Minimum = min.Value;
+
+            PlotModel.Axes.Add(axis);
+        }
+
+        public void ShowLegend()
+        {
+            PlotModel.IsLegendVisible = true;
+        }
+
+        public void HideLegend()
+        {
+            PlotModel.IsLegendVisible = false;
+        }
+
+        public void Legend(string title, bool show = true)
+        {
+            PlotModel.IsLegendVisible = show;
+            PlotModel.LegendTitle = title;
+        }
+
+        public void ExportPdf(string filename, double width, bool smAlso = true)
+        {
+            OldOxyPlotting.ExportToPdf(PlotModel, filename, width, false, false, null);
+
+            if (smAlso)
+                ExportPdf(filename + "_sm", width * 0.9, false);
+        }
+
+        private void ForceCompute()
+        {            
+            PlotModel.ResetAllAxes();
+            PlotModel.InvalidatePlot(true);
+
+            int failCount = 0;
+        again:
+            try
+            {
+                ExportPdf("nil.pdf", 1); // HACK HACK: only way I can find to get it to compute everything...
+            }
+            catch
+            {
+                if (failCount == 0)
+                    Console.WriteLine("nil err");
+
+                failCount++;
+
+                if (failCount < 10)
+                {
+                    System.Threading.Thread.Sleep(10 * failCount);
+                    goto again;
+                }
+                else
+                {
+                    Console.WriteLine("nil failed, plot may be invalid (" + PlotModel.Title + ")");
+                    return;
+                }
+            }
+        }
+        
+        public void Sillyify(int sillyTickCount, string key)
+        {
+            ForceCompute(); // hack hack
+
+            var a = PlotModel.Axes.First(x => x.Key == key);
+            Sillyify(sillyTickCount, a);
+
+            PlotModel.InvalidatePlot(true);
+        }
+
+        public static void Sillyify(int sillyTickCount, OxyPlot.Axes.Axis a)
+        {
+            double min = a.DataMinimum;
+            double max = a.DataMaximum;
+
+            Sillyify(sillyTickCount, a, min, max);
+        }
+
+        public static void Sillyify(int sillyTickCount, OxyPlot.Axes.Axis a, double min, double max)
+        {
+            double tickStep = SillyCeil10((max - min) / sillyTickCount);
+            max = SillyCeil(max, tickStep / 2.0); // TODO: replace these with something based on the tickStep
+            min = SillyFloor(min, tickStep / 2.0);
+
+            a.Maximum = max;
+            a.Minimum = min;
+            a.MajorStep = tickStep;
+            a.MajorGridlineStyle = OxyPlot.LineStyle.Solid;
+        }
+
+        public void Sillyify(int sillyTickCount = 10)
+        {
+            ForceCompute(); // hack hack
+
+            if (PlotModel.Series.Count > 0)
+            {
+                foreach (var a in PlotModel.Axes)
+                {
+                    Sillyify(sillyTickCount, a);
+                }
+            }
+            
+            PlotModel.InvalidatePlot(true);
+        }
+
+        public OxyPlot.Series.LineSeries PlotLine(string title, Func<double, double> func, double x0, double x1, int count)
+        {
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.FunctionSeries(func, x0, x1, count, title) { LineStyle = OxyPlot.LineStyle.Solid, MarkerType = OxyPlot.MarkerType.None, XAxisKey = "x", YAxisKey = "y", RenderInLegend = true };
+            
+            PlotModel.Series.Add(series);
+            return series;
+        }
+
+        public OxyPlot.Series.LineSeries PlotScatter(string title, Func<double, double> func, double x0, double x1, int count)
+        {
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.FunctionSeries(func, x0, x1, count, title) { LineStyle = OxyPlot.LineStyle.None, MarkerType = OxyPlot.MarkerType.Diamond, XAxisKey = "x", YAxisKey = "y", RenderInLegend = true };
+            
+            PlotModel.Series.Add(series);
+            return series;
+        }
+
+        public OxyPlot.Series.LineSeries PlotSteps(string title, IEnumerable<Tuple<double, double>> steps)
+        {
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.LineSeries() { Title = title, LineStyle = OxyPlot.LineStyle.Solid, MarkerType = OxyPlot.MarkerType.None, XAxisKey = "x", YAxisKey = "y", RenderInLegend = true };
+            
+            double prev = 0;
+            bool first = true;
+            foreach (System.Tuple<double, double> d in steps)
+            {
+                if (first)
+                {
+                    first = false;
+                }
+                else
+                {
+                    series.Points.Add(new OxyPlot.DataPoint(d.Item1, prev));
+                }
+                
+                series.Points.Add(new OxyPlot.DataPoint(d.Item1, d.Item2));
+                prev = d.Item2;
+            }
+            
+            PlotModel.Series.Add(series);
+            return series;
+        }
+
+        public OxyPlot.Series.LineSeries PlotLine(string title, IEnumerable<Tuple<double, double>> points)
+        {
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.LineSeries() { Title = title, LineStyle = OxyPlot.LineStyle.Solid, MarkerType = OxyPlot.MarkerType.None, XAxisKey = "x", YAxisKey = "y", RenderInLegend = true };
+            
+            foreach (System.Tuple<double, double> d in points)
+            {
+                series.Points.Add(d == null ? OxyPlot.DataPoint.Undefined : new OxyPlot.DataPoint(d.Item1, d.Item2));
+            }
+
+            PlotModel.Series.Add(series);
+            return series;
+        }
+
+        public OxyPlot.Series.LineSeries PlotLine(string title, IEnumerable<double> points, double x0 = 0, double dx = 1)
+        {
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.LineSeries() { Title = title, LineStyle = OxyPlot.LineStyle.Solid, MarkerType = OxyPlot.MarkerType.None, XAxisKey = "x", YAxisKey = "y", RenderInLegend = true };
+            
+            double x = x0;
+            foreach (double d in points)
+            {
+                series.Points.Add(new OxyPlot.DataPoint(x, d));
+                x += dx;
+            }
+
+            PlotModel.Series.Add(series);
+            return series;
+        }
+
+        public OxyPlot.Series.LineSeries PlotScatter(string title, IEnumerable<Tuple<double, double>> points)
+        {
+            OxyPlot.Series.LineSeries series = new OxyPlot.Series.LineSeries() { Title = title, LineStyle = OxyPlot.LineStyle.None, MarkerType = OxyPlot.MarkerType.Diamond, XAxisKey = "x", YAxisKey = "y", RenderInLegend = true };
+            
+            foreach (System.Tuple<double, double> d in points)
+            {
+                series.Points.Add(d == null ? OxyPlot.DataPoint.Undefined : new OxyPlot.DataPoint(d.Item1, d.Item2));
+            }
+
+            var s = new OxyPlot.Series.ScatterSeries();
+
+            PlotModel.Series.Add(series);
+            return series;
+        }
+        
+        public OxyPlot.Series.HeatMapSeries PlotHeatmap(string title, double[,] darr, double x0, double x1, double y0, double y1)
+        {
+            OxyPlot.Series.HeatMapSeries series = new OxyPlot.Series.HeatMapSeries() { Title = title, RenderMethod = OxyPlot.Series.HeatMapRenderMethod.Rectangles, X0 = x0, X1 = x1, Y0 = y0, Y1 = y1, XAxisKey = "x", YAxisKey = "y", ColorAxisKey = "c" };
+            series.Data = darr;
+
+            PlotModel.Series.Add(series);
+            return series;
+        }
+        
+        private IEnumerable<OxyPlot.Series.Series> EnumerateDataPointSeries(OxyPlot.Axes.Axis axis)
+        {
+            foreach (var s in PlotModel.Series)
+            {
+                if (s is OxyPlot.Series.DataPointSeries dps)
+                {
+                    yield return s;
+                }
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Properties/AssemblyInfo.cs b/M4MCode/M4M_MkI/M4M.Old/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..53563a8
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("M4M.Old")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("M4M.Old")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("8b27e40d-9db6-4179-a2bd-e9de907509b0")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/M4MCode/M4M_MkI/M4M.Old/Reporting/Basics.cs b/M4MCode/M4M_MkI/M4M.Old/Reporting/Basics.cs
new file mode 100644
index 0000000..8f23fa3
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Reporting/Basics.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4M.Reporting
+{
+    public class Section : ParentDocElem, IDocElem
+    {
+        public string Title { get; }
+
+        public Section(string title)
+        {
+            Title = title;
+        }
+
+        public void WriteOut(DocWriteInfo dwi, TextWriter writer)
+        {
+            writer.WriteLine();
+            writer.WriteLine();
+
+            writer.Write(new string('#', dwi.SectionDepth + 2));
+            writer.Write(' ');
+            writer.WriteLine(Title);
+
+            var subDwi = dwi.SubSection();
+            foreach (var subElem in SubElements)
+                subElem.WriteOut(subDwi, writer);
+        }
+
+        public void WriteOutTex(TexDocWriteInfo dwi, TexWriter writer)
+        {
+            string cmd = "";
+            for (int i = 0; i < dwi.DocWriteInfo.SectionDepth; i++)
+                cmd += "sub";
+            cmd += "section";
+
+            writer.Write(cmd, Title);
+
+            var subDwi = dwi.SubSection();
+            foreach (var subElem in SubElements)
+                subElem.WriteOutTex(subDwi, writer);
+        }
+    }
+
+    public class RawParagraph : IDocElem
+    {
+        public string Text { get; }
+
+        public RawParagraph(string text)
+        {
+            Text = text;
+        }
+
+        public void WriteOut(DocWriteInfo dwi, TextWriter writer)
+        {
+            writer.WriteLine();
+            writer.WriteLine(Text);
+        }
+
+        public void WriteOutTex(TexDocWriteInfo dwi, TexWriter writer)
+        {
+            writer.WriteEmptyLine();
+            writer.WriteRawLine(Text);
+        }
+
+        public void AccumulateDocumentInfo(GlobalDocWriteInfo globalDocWriteInfo)
+        {
+        }
+    }
+
+    public class RawInlineText : IDocElem
+    {
+        public string Text { get; }
+
+        public RawInlineText(string text)
+        {
+            Text = text;
+        }
+
+        public void WriteOut(DocWriteInfo dwi, TextWriter writer)
+        {
+            writer.Write(Text);
+        }
+
+        public void WriteOutTex(TexDocWriteInfo dwi, TexWriter writer)
+        {
+            writer.WriteRaw(Text);
+        }
+
+        public void AccumulateDocumentInfo(GlobalDocWriteInfo globalDocWriteInfo)
+        {
+        }
+
+        public static implicit operator RawInlineText(string text)
+        {
+            return new RawInlineText(text);
+        }
+    }
+
+    public class ItemList : IDocElem
+    {
+        public List<IDocElem> Items { get; } = new List<IDocElem>();
+
+        public ItemList()
+        {
+        }
+
+        public void Add(IDocElem item)
+        {
+            Items.Add(item);
+        }
+
+        public void WriteOut(DocWriteInfo dwi, TextWriter writer)
+        {
+            writer.WriteLine();
+
+            foreach (var item in Items)
+            {
+                writer.Write(" - ");
+                item.WriteOut(dwi, writer);
+
+                writer.WriteLine();
+            }
+        }
+
+        public void WriteOutTex(TexDocWriteInfo dwi, TexWriter writer)
+        {
+            using (var enumeration = writer.Begin("itemize"))
+            {
+                foreach (var item in Items)
+                {
+                    writer.Write("item");
+                    item.WriteOutTex(dwi, writer);
+
+                    writer.WriteEmptyLine();
+                }
+            }
+        }
+
+        public void AccumulateDocumentInfo(GlobalDocWriteInfo globalDocWriteInfo)
+        {
+            foreach (var item in Items)
+                item.AccumulateDocumentInfo(globalDocWriteInfo);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Reporting/Figure.cs b/M4MCode/M4M_MkI/M4M.Old/Reporting/Figure.cs
new file mode 100644
index 0000000..1cd3145
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Reporting/Figure.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4M.Reporting
+{
+    public class FigureGraphic
+    {
+        public string Path { get; }
+        public string Width { get; }
+
+        public FigureGraphic(string path, string width)
+        {
+            Path = path;
+            Width = width;
+        }
+    }
+
+    public class Figure : IDocElem
+    {
+        public string Caption { get; set; }
+        public string Label { get; set; }
+        public List<FigureGraphic> Graphics { get; }  = new List<FigureGraphic>();
+
+        public Figure()
+        {
+        }
+
+        public void AccumulateDocumentInfo(GlobalDocWriteInfo globalDocWriteInfo)
+        {
+        }
+
+        public void WriteOut(DocWriteInfo dwi, TextWriter writer)
+        {   
+            writer.WriteLine();
+            writer.WriteLine("Figure: " + Caption);
+            foreach (var graphic in Graphics)
+                writer.WriteLine(" Graphic: " + graphic.Path);
+        }
+
+        public void WriteOutTex(TexDocWriteInfo dwi, TexWriter writer)
+        {
+            using (var figure = writer.Begin("figure", null, new[] { "htbp" }))
+            {
+                writer.Write("centering");
+
+                foreach (var graphic in Graphics)
+                    writer.Write("includegraphics", new[] { "width=" + graphic.Width }, graphic.Path, null);
+
+                string captext = Caption + (Label != null ? $"\\label{{{Label}}}" : "");
+                writer.Write("caption", captext);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Reporting/Report.cs b/M4MCode/M4M_MkI/M4M.Old/Reporting/Report.cs
new file mode 100644
index 0000000..4ee894e
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Reporting/Report.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4M.Reporting
+{
+    public class Packages
+    {
+        private List<PackageInfo> PackageInfos = new List<PackageInfo>();
+
+        public void Add(PackageInfo packageInfo)
+        {
+            PackageInfos.Add(packageInfo);
+        }
+
+        public void WriteOutTex(TexWriter writer)
+        {
+            foreach (var package in PackageInfos)
+            {
+                writer.Write("usepackage", package.PreParameters, package.Package, package.PostParameters);
+            }
+        }
+
+        public static readonly Packages DefaultPackages;
+
+        static Packages()
+        {
+            DefaultPackages = new Packages();
+            DefaultPackages.Add(new PackageInfo("url"));
+            DefaultPackages.Add(new PackageInfo("ulem"));
+            //DefaultPackages.Add(new PackageInfo("geometry", new[] { "margin=0.5in" }));
+            DefaultPackages.Add(new PackageInfo("geometry", new[] { "a4paper" }));
+            DefaultPackages.Add(new PackageInfo("graphicx"));
+            DefaultPackages.Add(new PackageInfo("epstopdf"));
+            DefaultPackages.Add(new PackageInfo("amsmath"));
+            DefaultPackages.Add(new PackageInfo("amssymb"));
+            DefaultPackages.Add(new PackageInfo("color"));
+            DefaultPackages.Add(new PackageInfo("listings"));
+            DefaultPackages.Add(new PackageInfo("placeins"));
+            DefaultPackages.Add(new PackageInfo("fontenc", new[] { "T1" }));
+        }
+    }
+
+    public class Report : ParentDocElem
+    {
+        public string Title { get; }
+        public string Author { get; }
+        public string NominalDate { get; }
+
+        public Packages Packages { get; } = Packages.DefaultPackages;
+        public List<string> GraphicsExtensions = new List<string> { ".png", ".jpg", ".pdf" };
+
+        public Report(string title, string author, string nominalDate)
+        {
+            Title = title;
+            Author = author;
+            NominalDate = nominalDate;
+        }
+
+        public void WriteOut(TextWriter writer)
+        {
+            // accumulate gdwi
+            var globalDocWriteInfo = new GlobalDocWriteInfo();
+            foreach (var subElem in SubElements)
+                subElem.AccumulateDocumentInfo(globalDocWriteInfo);
+
+            // header
+            writer.WriteLine("# " + Title);
+            writer.WriteLine();
+            writer.WriteLine(Author);
+            writer.WriteLine();
+            writer.WriteLine(NominalDate);
+            
+            // content
+            foreach (var subElem in SubElements)
+                subElem.WriteOut(new DocWriteInfo(globalDocWriteInfo), writer);
+        }
+
+        public void WriteOut(string fileName)
+        {
+            using (var writer = new StreamWriter(fileName))
+            {
+                WriteOut(writer);
+            }
+        }
+
+        public void WriteOutTex(TextWriter writer)
+        {
+            var tw = new TexWriter(writer);
+
+            // accumulate gdwi
+            var globalDocWriteInfo = new GlobalDocWriteInfo();
+            foreach (var subElem in SubElements)
+                subElem.AccumulateDocumentInfo(globalDocWriteInfo);
+
+            // preamble
+            tw.Write("documentclass", "article");
+
+            tw.WriteEmptyLine();
+            tw.WriteComment("packages");
+            Packages.WriteOutTex(tw);
+
+            tw.WriteEmptyLine();
+            tw.WriteComment("document");
+            using (tw.Begin("document"))
+            {
+                // header
+                tw.Write("title", Title);
+                tw.Write("author", Author);
+                tw.Write("date", NominalDate);
+                tw.Write("maketitle");
+
+                tw.WriteEmptyLine();
+
+                // content
+                foreach (var subElem in SubElements)
+                    subElem.WriteOutTex(new TexDocWriteInfo(globalDocWriteInfo), tw);
+
+                tw.WriteEmptyLine();
+            }
+        }
+
+        public void WriteOutTex(string fileName)
+        {
+            using (var writer = new StreamWriter(fileName))
+            {
+                WriteOutTex(writer);
+            }
+        }
+
+        public void RenderTex(string fileName, bool openPdf = false, string latexProcessor = "pdflatex")
+        {
+            WriteOutTex(fileName);
+
+            string dir = Path.GetDirectoryName(fileName);
+            string path = Path.GetFullPath(dir);
+
+            System.Diagnostics.ProcessStartInfo latexPsi = new System.Diagnostics.ProcessStartInfo();
+            latexPsi.WorkingDirectory = path;
+            latexPsi.UseShellExecute = false;
+            latexPsi.FileName = latexProcessor;
+            latexPsi.Arguments = "\"" + fileName + "\"";
+
+            var pdfLatex = System.Diagnostics.Process.Start(latexPsi);
+
+            pdfLatex.WaitForExit();
+
+            if (openPdf)
+                System.Diagnostics.Process.Start("\"" + fileName.Replace(".tex", ".pdf") + "\"");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Reporting/Reporting.cs b/M4MCode/M4M_MkI/M4M.Old/Reporting/Reporting.cs
new file mode 100644
index 0000000..db61131
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Reporting/Reporting.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4M.Reporting
+{
+    public class Empty
+    {
+        public static readonly string[] String = new string[0];
+    }
+
+    public class TexOpenClose : IDisposable
+    {
+        public string EndString { get; }
+        private readonly TexWriter TexWriter;
+        private IEnumerable<string> PostParams;
+
+        public TexOpenClose(TexWriter texWriter, string command, IEnumerable<string> preParams, IEnumerable<string> postParams)
+        {
+            TexWriter = texWriter;
+            PostParams = postParams;
+
+            TexWriter.WriteOpen(command, preParams);
+        }
+
+        public void Dispose()
+        {
+            TexWriter.WriteClose(PostParams);
+        }
+    }
+
+    public class TexBeginEnd : IDisposable
+    {
+        public string Command { get; }
+        private readonly TexWriter TexWriter;
+
+        public TexBeginEnd(TexWriter texWriter, string command, IEnumerable<string> preParams, IEnumerable<string> postParams)
+        {
+            TexWriter = texWriter;
+            Command = command;
+
+            TexWriter.WriteBegin(Command, preParams, postParams);
+        }
+
+        public void Dispose()
+        {
+            TexWriter.WriteEnd(Command);
+        }
+    }
+
+    public class TexWriter
+    {
+        private TextWriter Writer { get; }
+
+        public void Texify(ref string str)
+        {
+            if (str.Contains("\n"))
+            {
+                str = str.Replace("\r", "");
+                str = str.Replace("\n", "\\\\");
+            }
+        }
+
+        public TexWriter(TextWriter writer)
+        {
+            Writer = writer;
+        }
+
+        public void WriteEmptyLine()
+        {
+            Writer.WriteLine();
+        }
+
+        public void WriteRaw(string misc)
+        {
+            Writer.Write(misc);
+        }
+
+        public void WriteTexy(string misc)
+        {
+            Texify(ref misc);
+            Writer.Write(misc);
+        }
+
+        public void WriteRawLine(string misc)
+        {
+            Writer.WriteLine(misc);
+        }
+
+        public void WriteTexyLine(string misc)
+        {
+            Texify(ref misc);
+            Writer.WriteLine(misc);
+        }
+
+        public void WriteComment(string comment)
+        {
+            Writer.Write("% ");
+            Writer.WriteLine(comment);
+        }
+
+        public void Write(string command)
+        {
+            Writer.WriteLine($"\\{command}");
+        }
+
+        public void Write(string command, string content)
+        {
+            Texify(ref content);
+            Writer.WriteLine($"\\{command}{{{content}}}");
+        }
+        public void Write(string command, IEnumerable<string> preParams, string content, IEnumerable<string> postParams)
+        {
+            Texify(ref content);
+            preParams = preParams ?? Empty.String;
+            postParams = postParams ?? Empty.String;
+            Writer.WriteLine($"\\{command}{string.Join("", preParams.Select(p => $"[{p}]"))}{{{content}}}{string.Join("", postParams.Select(p => $"[{p}]"))}");
+        }
+
+        public TexOpenClose Open(string command, IEnumerable<string> preParams, IEnumerable<string> postParams)
+        {
+            return new TexOpenClose(this, command, preParams, postParams);
+        }
+
+        public void WriteOpen(string command, IEnumerable<string> preParams)
+        {
+            preParams = preParams ?? Empty.String;
+            Writer.WriteLine($"\\{command}{string.Join("", preParams.Select(p => $"[{p}]"))}{{");
+        }
+
+        public void WriteClose(IEnumerable<string> postParams = null)
+        {
+            postParams = postParams ?? Empty.String;
+            Writer.WriteLine($"}}{string.Join("", postParams.Select(p => $"[{p}]"))}");
+        }
+
+        public TexBeginEnd Begin(string command, IEnumerable<string> preParams = null, IEnumerable<string> postParams = null)
+        {
+            return new TexBeginEnd(this, command, preParams, postParams);
+        }
+
+        public void WriteBegin(string command, IEnumerable<string> preParams, IEnumerable<string> postParams)
+        {
+            preParams = preParams ?? Empty.String;
+            postParams = postParams ?? Empty.String;
+            Writer.WriteLine($"\\begin{string.Join("", preParams.Select(p => $"[{p}]"))}{{{command}}}{string.Join("", postParams.Select(p => $"[{p}]"))}");
+        }
+
+        public void WriteEnd(string command)
+        {
+            Writer.WriteLine($"\\end{{{command}}}");
+        }
+    }
+
+    public class GlobalDocWriteInfo
+    {
+        public Dictionary<string, object> GlobalUserInfo { get; }
+
+        public GlobalDocWriteInfo()
+        {
+        }
+    }
+
+    public class DocWriteInfo
+    {
+        public GlobalDocWriteInfo GlobalDocWriteInfo { get; }
+
+        public int SectionDepth { get; }
+
+        public Dictionary<string, object> ScopedUserInfo { get; }
+
+        private DocWriteInfo(GlobalDocWriteInfo globalDocWriteInfo, int sectionDepth, Dictionary<string, object> scopedUserInfo)
+        {
+            SectionDepth = sectionDepth;
+            ScopedUserInfo = scopedUserInfo;
+        }
+
+        public DocWriteInfo(GlobalDocWriteInfo globalDocWriteInfo) : this(globalDocWriteInfo, 0, new Dictionary<string, object>())
+        {
+            // nix
+        }
+
+        public DocWriteInfo SubSection()
+        {
+            return new DocWriteInfo(GlobalDocWriteInfo, SectionDepth + 1, new Dictionary<string, object>(ScopedUserInfo));
+        }
+    }
+
+    public class TexDocWriteInfo
+    {
+        public DocWriteInfo DocWriteInfo { get; }
+
+        private TexDocWriteInfo(DocWriteInfo docWriteInfo)
+        {
+            DocWriteInfo = docWriteInfo;
+        }
+
+        public TexDocWriteInfo(GlobalDocWriteInfo globalDocWriteInfo) : this(new DocWriteInfo(globalDocWriteInfo))
+        {
+            // nix
+        }
+
+        public TexDocWriteInfo SubSection()
+        {
+            return new TexDocWriteInfo(DocWriteInfo.SubSection());
+        }
+    }
+
+    public interface IDocElem
+    {
+        void WriteOut(DocWriteInfo dwi, TextWriter writer);
+        void WriteOutTex(TexDocWriteInfo dwi, TexWriter writer);
+        void AccumulateDocumentInfo(GlobalDocWriteInfo globalDocWriteInfo);
+    }
+
+    public abstract class ParentDocElem
+    {
+        protected List<IDocElem> SubElements { get; private set; } = new List<IDocElem>();
+        
+        public void Add(IDocElem subelem)
+        {
+            SubElements.Add(subelem);
+        }
+
+        public virtual void AccumulateDocumentInfo(GlobalDocWriteInfo globalDocWriteInfo)
+        {
+            foreach (var subElem in SubElements)
+                subElem.AccumulateDocumentInfo(globalDocWriteInfo);
+        }
+    }
+
+    public class PackageInfo
+    {
+        public IReadOnlyList<string> PreParameters { get; }
+        public string Package { get; }
+        public IReadOnlyList<string> PostParameters { get; }
+
+        public PackageInfo(string package, IEnumerable<string> preParameters = null, IEnumerable<string> postPrameters = null)
+        {
+            Package = package;
+            PreParameters = preParameters?.ToArray() ?? Empty.String;
+            PostParameters = postPrameters?.ToArray() ?? Empty.String;
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Sillyness.cs b/M4MCode/M4M_MkI/M4M.Old/Sillyness.cs
new file mode 100644
index 0000000..9d11691
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Sillyness.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Plotting
+{
+    public class Sillyness
+    {
+        public static double SignedSillyFloor10(double d)
+        {
+            if (d == 0)
+                return 0;
+            else if (d < 0)
+                return -SillyCeil10(-d);
+            else if (d > 0)
+                return SillyFloor10(d);
+            else
+                return double.NaN;
+        }
+        
+        public static double SignedSillyCeil10(double d)
+        {
+            if (d == 0)
+                return 0;
+            else if (d < 0)
+                return -SillyFloor10(-d);
+            else if (d > 0)
+                return SillyCeil10(d);
+            else
+                return double.NaN;
+        }
+
+        public static double SillyFloor10(double d)
+        {
+            double l10 = Math.Log10(d);
+            l10 = Math.Floor(l10);
+            double e = Math.Pow(10, l10);
+
+            if (e * 5 <= d)
+                return e * 5;
+            if (e * 2 <= d)
+                return e * 2;
+            return e;
+        }
+
+        public static double SillyCeil10(double d)
+        {
+            double l10 = Math.Log10(d);
+            l10 = Math.Ceiling(l10);
+            double e = Math.Pow(10, l10);
+
+            if (e / 5 >= d)
+                return e / 5;
+            if (e / 2 >= d)
+                return e / 2;
+            return e;
+        }
+
+        public static double SillyFloor(double d, double step)
+        {
+            return step * Math.Floor(d / step);
+        }
+
+        public static double SillyCeil(double d, double step)
+        {
+            return step * Math.Ceiling(d / step);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Old/Topology.cs b/M4MCode/M4M_MkI/M4M.Old/Topology.cs
new file mode 100644
index 0000000..12130a4
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Old/Topology.cs
@@ -0,0 +1,213 @@
+using OxyPlot;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MathNet.Numerics.LinearAlgebra;
+using MathNet.Numerics.Random;
+using static M4M.Misc;
+using static M4M.MiscPlotting;
+
+namespace M4M
+{
+    public interface ITopology
+    {
+        double[][] Develop(double[] coefs, int steps, Func<double, double> squash);
+    }
+    
+    public delegate double[] DifferentialFunction(double[] coeficients, double[] state, Func<double, double> squash);
+
+    public class TopologyStuff
+    {
+        public static double Judge(ITopology topology, double[] p, int T, Func<double, double> squash, double lambda)
+        {
+            int N = p.Length;
+            // TODO: /5 term is nasty, sort it out (need proper rules)
+            double b = 0.5 * (1 + topology.Develop(p, T, squash).Last().Sum() / (5 * N));
+
+            // TODO: explicit L1 is nasty, for it out (need proper rules)
+            double c = p.Sum(r => Math.Abs(r)) / (N * N);
+
+            return b - c * lambda;
+        }
+
+        public static void SolveTopology(string name, ITopology topology, double min, double max, double dd, int T, Func<double, double> squash, int res = 50)
+        {
+            var plotModel = Plotting.OldOxyPlotting.LinearAxes(name, "Total Cost", "Weights");
+
+            int n = 4;
+
+            OxyPlot.Series.LineSeries[] ls = new OxyPlot.Series.LineSeries[n];
+            for (int i = 0; i < n; i++)
+                ls[i] = new OxyPlot.Series.LineSeries() { Title = ""+i };
+
+            for (double x = min; x <= max; x += dd)
+            {
+                double[] p = Misc.ArgMax(Combinatorics.EnumerateOrderedParameterisations(n, x, res), d => Judge(topology, d, T, squash, 0.0));
+                
+                for (int i = 0; i < n; i++)
+                    ls[i].Points.Add(new DataPoint(x, p[i]));
+            }
+
+            for (int i = 0; i < n; i++)
+                plotModel.Series.Add(ls[i]);
+            plotModel.InvalidatePlot(true);
+
+            Plotting.OldOxyPlotting.ExportToPdf(plotModel, name + ".pdf");
+        }
+    }
+
+    public class GeneralTopology : ITopology
+    {
+        public static readonly GeneralTopology Flat = new GeneralTopology((coefs, state, squash) =>
+        {
+            return Misc.Create<double>(state.Length, i =>
+            {
+                return -0.2 * state[i] + squash(state.Sum() * coefs[i] / 4.0);
+            });
+        });
+        
+        public static readonly GeneralTopology Identity = new GeneralTopology((coefs, state, squash) =>
+        {
+            return Misc.Create<double>(state.Length, i =>
+            {
+                return -0.2 * state[i] + squash(state[i] * coefs[i]);
+            });
+        });
+        
+        public static readonly GeneralTopology Column = new GeneralTopology((coefs, state, squash) =>
+        {
+            return Misc.Create<double>(state.Length, i =>
+            {
+                return -0.2 * state[i] + squash(state[0] * coefs[i]);
+            });
+        });
+        
+        public GeneralTopology(DifferentialFunction differentialFunction)
+        {
+            DifferentialFunction = differentialFunction;
+        }
+        
+        public DifferentialFunction DifferentialFunction { get; }
+
+        public double[][] Develop(double[] coefs, int steps, Func<double, double> squash)
+        {
+            double[][] trajs = new double[steps + 1][];
+
+            double[] state = Misc.Create<double>(coefs.Length, i => 1.0);
+            trajs[0] = state;
+
+            for (int i = 1; i <= steps; i++)
+            {
+                var diff = DifferentialFunction(coefs, state, squash);
+
+                state = state.Zip(diff, (y, dy) => y + dy).ToArray();
+
+                trajs[i] = state;
+            }
+            
+            return trajs;
+        }
+    }
+
+    /// <summary>
+    /// Reduces the genome to a single vector, which has a topological development and judgement
+    /// </summary>
+    public class TopologicalGenome
+    {
+        public TopologicalGenome(double[] regulationCoefs, ITopology topology)
+        {
+            RegulationCoefs = regulationCoefs;
+            Topology = topology;
+        }
+
+        public static TopologicalGenome Empty(int size, ITopology topology)
+        {
+            return new TopologicalGenome(new double[size], topology);
+        }
+
+        public double[] RegulationCoefs { get; }
+        public ITopology Topology { get; }
+        
+        public int Size => RegulationCoefs.Length;
+
+        public double Judge(int T, Func<double, double> squash, double lambda)
+        {
+            return TopologyStuff.Judge(Topology, RegulationCoefs, T, squash, lambda);
+        }
+
+        public TopologicalGenome Mutate(RandomSource rand, double mutationMagnitude)
+        {
+            double[] nrcs = new double[Size];
+
+            for (int i = 0; i < Size; i++)
+            {
+                // draw delta
+                double μ2 = rand.NextSignedUniform(mutationMagnitude);
+
+                // apply (unclamped)
+                nrcs[i] = μ2 + RegulationCoefs[i];
+            }
+
+            return new TopologicalGenome(nrcs, Topology);
+        }
+
+        public static void Spin(ref TopologicalGenome genome, ref double fitness, RandomSource rand, int T, Func<double, double> squash, int generations, double mutationMagnitude, double lambda)
+        {
+            TopologicalGenome g = genome;
+            double f = g.Judge(T, squash, lambda);
+
+            for (int i = 0; i < generations; i++)
+            {
+                // generate and judge
+                TopologicalGenome g2 = g.Mutate(rand, mutationMagnitude);
+                double f2 = g2.Judge(T, squash, lambda);
+
+                // select
+                if (f2 > f)
+                {
+                    g = g2;
+                    f = f2;
+                }
+            }
+
+            genome = g;
+            fitness = f;
+        }
+
+        public static void RunExperiment(string outDir, string filePrefix, string titlePrefix, RandomSource rand, TopologicalGenome initialGenome, int epochs, int generationsPerEpoch, int T, Func<double, double> squash, double mutationMagnitude, double lambda)
+        {
+            outDir += outDir + "_" + Misc.NowTime;
+            System.IO.Directory.CreateDirectory(outDir);
+            
+            string file(string name)
+            {
+                return System.IO.Path.Combine(outDir, filePrefix + name);
+            }
+
+            TopologicalGenome genome = initialGenome;
+            double f = genome.Judge(T, squash, lambda);
+            
+            int N = genome.Size;
+            double[][] regulationCoefs = CreateEmpty<double>(N, epochs + 1);
+
+            for (int epoch = 0; epoch <= epochs; epoch++)
+            {
+                if (epoch > 0)
+                {
+                    Spin(ref genome, ref f, rand, T, squash, generationsPerEpoch, mutationMagnitude, lambda);
+                }
+
+                // record
+                for (int i = 0; i < N; i++)
+                {
+                    regulationCoefs[i][epoch] = genome.RegulationCoefs[i];
+                }
+            }
+
+            // plot
+            PlotLines(titlePrefix + "Regulation Coefs", file("rcs.pdf"), regulationCoefs, 0, 1, xTitle: "Epoch", yTitle: "Weight");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Tests/AssertExtensions.cs b/M4MCode/M4M_MkI/M4M.Tests/AssertExtensions.cs
new file mode 100644
index 0000000..7a66427
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/AssertExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace M4M.Tests
+{
+    public static class MathAssert
+    {
+        public static void AreClose(double expected, double actual, double eps = 1e-10)
+        {
+            if (expected == actual)
+                return; // covers degenerate cases like 0==0
+
+            var diff = Math.Abs(expected - actual);
+            var sum = Math.Abs(expected) + Math.Abs(actual);
+            Assert.IsTrue(diff < sum * eps, $"Expected {expected}, Actual {actual}");
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Tests/DenseGenomeTests.cs b/M4MCode/M4M_MkI/M4M.Tests/DenseGenomeTests.cs
new file mode 100644
index 0000000..259cab4
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/DenseGenomeTests.cs
@@ -0,0 +1,98 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using MathNet.Numerics;
+using System.Linq;
+
+namespace M4M.Tests
+{
+    [TestClass]
+    public class DenseGenomeTests
+    {
+        public static ModelExecutionContext CreateContextNoRandom()
+        {
+            return new ModelExecutionContext(null);
+        }
+
+        [TestMethod]
+        public void TestDrulesDefaults()
+        {
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(DevelopmentRules.TanhHalf);
+            Assert.AreEqual(10, drules.TimeSteps);
+            Assert.AreEqual(1.0, drules.UpdateRate);
+            Assert.AreEqual(0.2, drules.DecayRate);
+            Assert.AreEqual(1.0, drules.RescaleScale);
+            Assert.AreEqual(0.0, drules.RescaleOffset);
+            Assert.AreEqual(DevelopmentRules.TanhHalf, drules.Squash);
+        }
+
+        [TestMethod]
+        public void TestTypicalDrules()
+        {
+            var drules = TypicalConfiguration.CreateStandardDevelopmentRules(DevelopmentRules.TanhHalf, 5, 0.1, 0.2, 0.3, 0.4);
+            Assert.AreEqual(5, drules.TimeSteps);
+            Assert.AreEqual(0.1, drules.UpdateRate);
+            Assert.AreEqual(0.2, drules.DecayRate);
+            Assert.AreEqual(0.3, drules.RescaleScale);
+            Assert.AreEqual(0.4, drules.RescaleOffset);
+            Assert.AreEqual(DevelopmentRules.TanhHalf, drules.Squash);
+        }
+
+        [TestMethod]
+        public void TestDevelopmentNoSquash()
+        {
+            var rnd = new Random(1);
+            for (int _r = 0; _r < 10; _r++)
+            {
+                var T = rnd.Next(0, 20);
+                var dr = rnd.NextDouble();
+                var ur = rnd.NextDouble();
+                var rs = rnd.NextDouble();
+                var ro = rnd.NextDouble();
+
+                int n = 16;
+
+                var g0 = CreateVector.Dense(n, i => (double)i);
+                var b0 = CreateMatrix.DenseDiagonal(n, 1.0); // independent
+
+                var drules = TypicalConfiguration.CreateStandardDevelopmentRules(DevelopmentRules.None, T, ur, dr, rs, ro);
+                var genome = new DenseGenome(g0, b0); // default dev-step
+
+                var context = CreateContextNoRandom();
+
+                var p = genome.Develop(context, drules).Vector;
+
+                var prod =
+                    Math.Pow(1.0 - drules.DecayRate + drules.UpdateRate, drules.TimeSteps) // T steps
+                    * (drules.DecayRate / drules.UpdateRate); // tau2/tau1 product
+
+                for (int i = 0; i < p.Count; i++)
+                {
+                    MathAssert.AreClose(i * prod * drules.RescaleScale + drules.RescaleOffset, p[i]);
+                }
+            }
+        }
+
+        [TestMethod]
+        public void TestDefaultStateCycle()
+        {
+            var rrules = TypicalConfiguration.CreateStandardReproductionRules(0.1, 1e-3, 0.5, true);
+            var context = new ModelExecutionContext(new CustomMersenneTwister(1));
+
+            var genome = DenseGenome.CreateDefaultGenomeSparse(16, 8, context.Rand);
+
+            for (int i = 0; i < 100; i++)
+                genome.Mutate(context, rrules);
+
+            // Assert there is noise: if there is no noise, it's a rubbish test
+            Assert.AreNotSame(0.0, genome.InitialState.Sum());
+            Assert.AreNotSame(0.0, genome.TransMat.L1Norm());
+
+            var cycled = StateExtensions.Cycle(genome);
+
+            CollectionAssert.AreEqual(genome.TransMatIndexOpenEntries.ToList(), cycled.TransMatIndexOpenEntries.ToList());
+            CollectionAssert.AreEqual(genome.InitialState, cycled.InitialState);
+            CollectionAssert.AreEqual(genome.TransMat.ToRowMajorArray(), cycled.TransMat.ToRowMajorArray());
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Tests/DummyConsole.cs b/M4MCode/M4M_MkI/M4M.Tests/DummyConsole.cs
new file mode 100644
index 0000000..cd4b472
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/DummyConsole.cs
@@ -0,0 +1,12 @@
+using System.IO;
+using System.Text;
+
+namespace M4M.Tests
+{
+    public class DummyConsole : TextWriter
+    {
+        public static DummyConsole Instance { get; } = new DummyConsole();
+
+        public override Encoding Encoding => System.Text.Encoding.Unicode;
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Tests/IvmcStackedTests.cs b/M4MCode/M4M_MkI/M4M.Tests/IvmcStackedTests.cs
new file mode 100644
index 0000000..9000bff
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/IvmcStackedTests.cs
@@ -0,0 +1,157 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using M4M.Epistatics;
+
+namespace M4M.Tests
+{
+    [TestClass]
+    public class IvmcStackedTests
+    {
+        public readonly Phenotype PM1 = new Phenotype(CreateVector.Dense<double>(new[] { -1.0, -1, -1, -1, -1, -1, -1, -1 }));
+        public readonly Phenotype PZ0 = new Phenotype(CreateVector.Dense<double>(new[] { 0.0, 0, 0, 0, 0, 0, 0, 0 }));
+        public readonly Phenotype PP1 = new Phenotype(CreateVector.Dense<double>(new[] { +1.0, +1, +1, +1, +1, +1, +1, +1 }));
+        public readonly Phenotype PMixA = new Phenotype(CreateVector.Dense<double>(new[] { 1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0 }));
+
+        [TestMethod]
+        public void TestSplitModuleBenefitFunction()
+        {
+            var eta = new SplitModuleBenefitFunction();
+
+            MathAssert.AreClose(1.0, eta.ComputeModuleBenefit(-1.0, 1.0, 1.0));
+            MathAssert.AreClose(-1.0, eta.ComputeModuleBenefit(-1.0, -1.0, 1.0));
+            MathAssert.AreClose(-0.5, eta.ComputeModuleBenefit(-1.0, -0.5, -0.5));
+
+            MathAssert.AreClose(0.2, eta.ComputeModuleBenefit(-0.2, 1.0, 1.0));
+            MathAssert.AreClose(-0.2, eta.ComputeModuleBenefit(-0.2, -1.0, 1.0));
+            MathAssert.AreClose(-0.1, eta.ComputeModuleBenefit(-0.2, -0.5, -0.5));
+
+            MathAssert.AreClose(0.0, eta.ComputeModuleBenefit(0.0, 1.0, 1.0));
+            MathAssert.AreClose(0.0, eta.ComputeModuleBenefit(0.0, -1.0, 1.0));
+            MathAssert.AreClose(0.0, eta.ComputeModuleBenefit(0.0, -0.5, -0.5));
+
+            MathAssert.AreClose(0.4, eta.ComputeModuleBenefit(0.4, 1.0, 1.0));
+            MathAssert.AreClose(0.4, eta.ComputeModuleBenefit(0.4, -1.0, 1.0));
+            MathAssert.AreClose(-0.2, eta.ComputeModuleBenefit(0.4, -0.5, -0.5));
+
+            MathAssert.AreClose(1.0, eta.ComputeModuleBenefit(1.0, 1.0, 1.0));
+            MathAssert.AreClose(1.0, eta.ComputeModuleBenefit(1.0, -1.0, 1.0));
+            MathAssert.AreClose(-0.5, eta.ComputeModuleBenefit(1.0, -0.5, -0.5));
+        }
+
+        [TestMethod]
+        public void TestProperModuleBenefitFunction()
+        {
+            var eta = new ProperModuleBenefitFunction();
+
+            // negative
+            // -1  -> 1
+            // -0.2 -> 0.36
+            // 0.0 -> 0.25
+            // 0.4 -> 0.09
+            // +1  -> 0
+
+            // positive
+            // -1  -> 0
+            // -0.2 -> 0.16
+            // 0.0 -> 0.25
+            // 0.4 -> 0.49
+            // +1  -> 1
+
+            MathAssert.AreClose(1.0, eta.ComputeModuleBenefit(-1.0, 1.0, 1.0));
+            MathAssert.AreClose(-1.0, eta.ComputeModuleBenefit(-1.0, -1.0, 1.0));
+            MathAssert.AreClose(-0.5, eta.ComputeModuleBenefit(-1.0, -0.5, -0.5));
+
+            MathAssert.AreClose(0.52, eta.ComputeModuleBenefit(-0.2, 1.0, 1.0));
+            MathAssert.AreClose(-0.2, eta.ComputeModuleBenefit(-0.2, -1.0, 1.0));
+            MathAssert.AreClose(-0.26, eta.ComputeModuleBenefit(-0.2, -0.5, -0.5));
+
+            MathAssert.AreClose(0.5, eta.ComputeModuleBenefit(0.0, 1.0, 1.0));
+            MathAssert.AreClose(0.0, eta.ComputeModuleBenefit(0.0, -1.0, 1.0));
+            MathAssert.AreClose(-0.25, eta.ComputeModuleBenefit(0.0, -0.5, -0.5));
+
+            MathAssert.AreClose(0.58, eta.ComputeModuleBenefit(0.4, 1.0, 1.0));
+            MathAssert.AreClose(0.4, eta.ComputeModuleBenefit(0.4, -1.0, 1.0));
+            MathAssert.AreClose(-0.29, eta.ComputeModuleBenefit(0.4, -0.5, -0.5));
+
+            MathAssert.AreClose(1.0, eta.ComputeModuleBenefit(1.0, 1.0, 1.0));
+            MathAssert.AreClose(1.0, eta.ComputeModuleBenefit(1.0, -1.0, 1.0));
+            MathAssert.AreClose(-0.5, eta.ComputeModuleBenefit(1.0, -0.5, -0.5));
+        }
+
+        [TestMethod]
+        public void TestSplitMCGen()
+        {
+            var clips = new CliParams();
+            clips.ConsumeLine("targetstring=++++++++ modulesstring=aabbccdd judgemode=stacked modulebenefitfunction=split decayfactor=0.75 rescalefitness=true scalefactor=4 ch=1 cl=0.9 c0=-1 z=1");
+            var targetPackage = ExperimentParsing.ParseIvmcTargetPackage(DummyConsole.Instance, clips, "custom");
+
+            Assert.AreEqual(1, targetPackage.Targets.Length);
+            Assert.AreEqual(8, targetPackage.TargetSize);
+
+            void AssertAll(IvmcProperChanging target)
+            {
+                Assert.IsNotNull(target);
+
+                Assert.AreEqual(1.0, target.CH);
+                Assert.AreEqual(0.9, target.CL);
+                Assert.AreEqual(-1, target.C0);
+
+                var proper = target.Proper;
+
+                Assert.AreEqual(4, proper.ModuleCount);
+                Assert.AreEqual(8, proper.ElementCount);
+                Assert.AreEqual(IvmcProperModulesType.FreeModules, proper.ModulesType);
+
+                var judger = target.CustomJudger as IvmcStackedVectorTargetJudger;
+                Assert.IsNotNull(judger);
+
+                Assert.AreEqual(0.75, judger.DecayFactor);
+                Assert.AreEqual(proper, judger.Proper);
+                Assert.AreEqual(true, judger.RescaleFitness);
+
+                var eta = judger.ModuleBenefitFunction as SplitModuleBenefitFunction;
+                Assert.IsNotNull(eta);
+            }
+
+            var target0 = targetPackage.Targets[0] as IvmcProperChanging;
+
+            AssertAll(target0);
+            AssertAll(StateExtensions.Cycle(target0));
+        }
+
+        [TestMethod]
+        public void TestImvStackAsMC()
+        {
+            var clips = new CliParams();
+            clips.ConsumeLine("targetstring=++++++++ modulesstring=aabbccdd judgemode=stacked modulebenefitfunction=split decayfactor=0.75 rescalefitness=true scalefactor=4 ch=1 cl=1 c0=-1 z=1");
+            var target = ExperimentParsing.ParseIvmcTargetPackage(DummyConsole.Instance, clips, "custom").Targets[0] as IvmcProperChanging;
+
+            var context = new ModelExecutionContext(new CustomMersenneTwister(1));
+
+            var jrules = TypicalConfiguration.CreateStandardJudgementRules(0.0, JudgementRules.ConstantEquivalent, 0);
+
+            var m4p = new[] { 1.0, 1.0, 1.0, 1.0 };
+
+            // check cminus/cplus
+            for (int i = 0; i < 100; i++)
+            {
+                var exposureInformation = new ExposureInformation(1000);
+                target.NextExposure(context.Rand, jrules, 0, ref exposureInformation);
+                Assert.AreEqual(1000, exposureInformation.ExposureDuration); // shouldn't touch this
+
+                target.NextGeneration(context.Rand, jrules);
+
+                // should always be +1
+                CollectionAssert.AreEqual(m4p, target.Proper.Cplus);
+                CollectionAssert.AreEqual(m4p, target.Proper.Cminus);
+
+                // check choice phenotypes
+                MathAssert.AreClose(0.5, target.Judge(PM1)); // (max)
+                MathAssert.AreClose(0.0, target.Judge(PZ0)); // (min)
+                MathAssert.AreClose(0.5, target.Judge(PP1)); // (max)
+                MathAssert.AreClose(((0.5 + 1 + 0.5 + 0) * 0.5 + (0.5 + 1 + -0.5 + 0) / 4 * 0.75 * 0.5) / (4.75), target.Judge(PMixA)); // (0.5, 1, -0.5, 0)
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Tests/M4M.Tests.csproj b/M4MCode/M4M_MkI/M4M.Tests/M4M.Tests.csproj
new file mode 100644
index 0000000..bf544cc
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/M4M.Tests.csproj
@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.1</TargetFramework>
+
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
+    <PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
+    <PackageReference Include="coverlet.collector" Version="1.3.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\M4M.Model\M4M.Model.csproj" />
+    <ProjectReference Include="..\M4M.New\M4M.New.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NetState">
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  
+  <ItemGroup>
+    <PackageReference Include="MathNet.Numerics" Version="4.9.1" />
+  </ItemGroup>
+
+</Project>
diff --git a/M4MCode/M4M_MkI/M4M.Tests/RegularisationTests.cs b/M4MCode/M4M_MkI/M4M.Tests/RegularisationTests.cs
new file mode 100644
index 0000000..24ff6c6
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/RegularisationTests.cs
@@ -0,0 +1,100 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MathNet.Numerics.LinearAlgebra;
+using System;
+using System.Linq;
+
+namespace M4M.Tests
+{
+    [TestClass]
+    public class RegularisationTests
+    {
+        [TestMethod]
+        public void TestTypicals2x2()
+        {
+            var g0 = CreateVector.Dense<double>(2);
+            var b0 = CreateMatrix.DenseOfArray(new[,] { { 1.0, -0.5 }, { 2.0, 0.0 } });
+            var genome = new DenseGenome(g0, b0);
+
+            var lno = JudgementRules.ConstantEquivalent.ComputeCost(genome);
+            var l1 = JudgementRules.L1Equivalent.ComputeCost(genome);
+            var l2 = JudgementRules.L2Equivalent.ComputeCost(genome);
+
+            Assert.AreEqual(1.0, lno); // 1
+            Assert.AreEqual(3.5 / 4, l1); // sum / n^2
+            Assert.AreEqual(5.25 / 4, l2); // sqr-sum / n^2
+        }
+
+        [TestMethod]
+        public void TestTypicals4x4()
+        {
+            var g0 = CreateVector.Dense<double>(4);
+            var b0 = CreateMatrix.DenseOfArray(new[,] { { 1.0, -0.5, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 2.0, 0.0 } });
+            var genome = new DenseGenome(g0, b0);
+
+            var lno = JudgementRules.ConstantEquivalent.ComputeCost(genome);
+            var l1 = JudgementRules.L1Equivalent.ComputeCost(genome);
+            var l1row = JudgementRules.RowL1Equivalent.ComputeCost(genome);
+            var l1col = JudgementRules.RowL1Equivalent.ComputeCost(genome);
+            var l2 = JudgementRules.L2Equivalent.ComputeCost(genome);
+
+            Assert.AreEqual(1.0, lno); // 1
+            Assert.AreEqual(3.5 / 16, l1); // sum / n^2
+            Assert.AreEqual(3.5 / 16, l1row); // sum / n^2
+            Assert.AreEqual(3.5 / 16, l1col); // sum / n^2
+            Assert.AreEqual(5.25 / 16, l2); // sqr-sum / n^2
+        }
+
+        [TestMethod]
+        public void TestDiagonal()
+        {
+            var g0 = CreateVector.Dense<double>(4);
+            var b0 = CreateMatrix.Diagonal<double>(new[] { 1.0, -0.5, 0.0, 2.0 });
+            var genome = new DenseGenome(g0, b0);
+
+            var lno = JudgementRules.ConstantEquivalent.ComputeCost(genome);
+            var l1 = JudgementRules.L1Equivalent.ComputeCost(genome);
+            var l1row = JudgementRules.RowL1Equivalent.ComputeCost(genome);
+            var l1col = JudgementRules.RowL1Equivalent.ComputeCost(genome);
+            var l2 = JudgementRules.L2Equivalent.ComputeCost(genome);
+
+            Assert.AreEqual(1.0, lno); // 1
+            Assert.AreEqual(3.5 / 16, l1); // sum / n^2
+            Assert.AreEqual(3.5 / 16, l1row); // sum / n^2
+            Assert.AreEqual(3.5 / 16, l1col); // sum / n^2
+            Assert.AreEqual(5.25 / 16, l2); // sqr-sum / n^2
+        }
+
+        [TestMethod]
+        public void TestCandidatesLarge()
+        {
+            var n = 256;
+
+            var ones = CreateVector.Dense(n, 1.0);
+
+            for (int i = 0; i < 10; i++)
+            {
+                var rnd = new Random(i);
+                var b = CreateMatrix.Dense<double>(n, n, (_i, _j) => rnd.NextDouble() * 20 - 10);
+
+                var rh1 = RegularisationHelpers.L1Sum(b);
+                var rh1row = RegularisationHelpers.RowL1Sum(b);
+                var rh1col = RegularisationHelpers.ColL1Sum(b);
+                var l1 = b.Enumerate().Sum(Math.Abs);
+                var mp1 = b.PointwiseAbs().LeftMultiply(ones).DotProduct(ones);
+
+                MathAssert.AreClose(l1, rh1);
+                MathAssert.AreClose(l1, rh1row);
+                MathAssert.AreClose(l1, rh1col);
+                Assert.AreEqual(rh1, rh1row);
+                MathAssert.AreClose(l1, rh1col);
+
+                var rh2 = RegularisationHelpers.L2Sum(b);
+                var l2 = b.Enumerate().Sum(x => x * x);
+                var mp2 = b.PointwiseMultiply(b).LeftMultiply(ones).DotProduct(ones);
+
+                MathAssert.AreClose(l2, rh2);
+                MathAssert.AreClose(l2, mp2);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.Tests/StateExtensions.cs b/M4MCode/M4M_MkI/M4M.Tests/StateExtensions.cs
new file mode 100644
index 0000000..40b2401
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.Tests/StateExtensions.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace M4M.Tests
+{
+    public class StateExtensions
+    {
+        public static T Cycle<T>(T state) where T : class
+        {
+            using (var ms = new MemoryStream())
+            {
+                State.GraphSerialisation.Write<T>(state, ms);
+                ms.Position = 0;
+                return State.GraphSerialisation.Read<T>(ms);
+            }
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M.sln b/M4MCode/M4M_MkI/M4M.sln
new file mode 100644
index 0000000..1c8feb8
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M.sln
@@ -0,0 +1,181 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30523.141
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M", "M4M\M4M.csproj", "{B6E4567C-558D-44D1-9CDC-FD4472E785E6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.Model", "M4M.Model\M4M.Model.csproj", "{6A470149-D199-4B60-9F76-0F01FBD587E0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M.Old", "M4M.Old\M4M.Old.csproj", "{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.New", "M4M.New\M4M.New.csproj", "{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.CoreRunner", "M4M.CoreRunner\M4M.CoreRunner.csproj", "{9F765166-0377-446C-8A23-3D5E08DC51D5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "M4M.Tests", "M4M.Tests\M4M.Tests.csproj", "{BBC537E5-82BC-4101-855D-B9732CC4257D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M4M.Benchmarks", "M4M.Benchmarks\M4M.Benchmarks.csproj", "{1755059D-E854-45CF-BD2B-86878B7ABBA0}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release2|Any CPU = Release2|Any CPU
+		Release2|x64 = Release2|x64
+		Release3|Any CPU = Release3|Any CPU
+		Release3|x64 = Release3|x64
+		Release4|Any CPU = Release4|Any CPU
+		Release4|x64 = Release4|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|x64.ActiveCfg = Debug|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Debug|x64.Build.0 = Debug|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|x64.ActiveCfg = Release|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release|x64.Build.0 = Release|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|Any CPU.ActiveCfg = Release2|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|Any CPU.Build.0 = Release2|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|x64.ActiveCfg = Release2|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release2|x64.Build.0 = Release2|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|Any CPU.ActiveCfg = Release3|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|Any CPU.Build.0 = Release3|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|x64.ActiveCfg = Release3|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release3|x64.Build.0 = Release3|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|Any CPU.ActiveCfg = Release4|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|Any CPU.Build.0 = Release4|Any CPU
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|x64.ActiveCfg = Release4|x64
+		{B6E4567C-558D-44D1-9CDC-FD4472E785E6}.Release4|x64.Build.0 = Release4|x64
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Debug|x64.Build.0 = Debug|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release|x64.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release2|x64.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release3|x64.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|x64.ActiveCfg = Release|Any CPU
+		{6A470149-D199-4B60-9F76-0F01FBD587E0}.Release4|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Debug|x64.Build.0 = Debug|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release2|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release3|x64.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|x64.ActiveCfg = Release|Any CPU
+		{8B27E40D-9DB6-4179-A2BD-E9DE907509B0}.Release4|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Debug|x64.Build.0 = Debug|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release2|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release3|x64.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|x64.ActiveCfg = Release|Any CPU
+		{1E7D2BDD-30E8-48F8-9636-AF93F451EC38}.Release4|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Debug|x64.Build.0 = Debug|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release2|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release3|x64.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|x64.ActiveCfg = Release|Any CPU
+		{9F765166-0377-446C-8A23-3D5E08DC51D5}.Release4|x64.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Debug|x64.Build.0 = Debug|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release|x64.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release|x64.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release2|x64.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release2|x64.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release3|x64.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release3|x64.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release4|x64.ActiveCfg = Release|Any CPU
+		{BBC537E5-82BC-4101-855D-B9732CC4257D}.Release4|x64.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Debug|x64.Build.0 = Debug|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release|x64.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release|x64.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release2|Any CPU.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release2|Any CPU.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release2|x64.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release2|x64.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release3|Any CPU.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release3|Any CPU.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release3|x64.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release3|x64.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release4|Any CPU.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release4|Any CPU.Build.0 = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release4|x64.ActiveCfg = Release|Any CPU
+		{1755059D-E854-45CF-BD2B-86878B7ABBA0}.Release4|x64.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {24F33315-4D5B-43D3-93D4-9B4D6A603384}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/M4M_MkI/M4M/App.config b/M4MCode/M4M_MkI/M4M/App.config
new file mode 100644
index 0000000..731f6de
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M/App.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+    </startup>
+</configuration>
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M/M4M.csproj b/M4MCode/M4M_MkI/M4M/M4M.csproj
new file mode 100644
index 0000000..444ce13
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M/M4M.csproj
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{B6E4567C-558D-44D1-9CDC-FD4472E785E6}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>M4M</RootNamespace>
+    <AssemblyName>M4M</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release2|AnyCPU'">
+    <OutputPath>bin\Release2\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release2|x64'">
+    <OutputPath>bin\x64\Release2\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release3|AnyCPU'">
+    <OutputPath>bin\Release3\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release3|x64'">
+    <OutputPath>bin\x64\Release3\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release4|AnyCPU'">
+    <OutputPath>bin\Release4\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release4|x64'">
+    <OutputPath>bin\x64\Release4\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>x64</PlatformTarget>
+    <UseVSHostingProcess>false</UseVSHostingProcess>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <Prefer32Bit>true</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="MathNet.Numerics, Version=4.9.1.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MathNet.Numerics.4.9.1\lib\net461\MathNet.Numerics.dll</HintPath>
+    </Reference>
+    <Reference Include="MigraDoc.DocumentObjectModel-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\MigraDoc.DocumentObjectModel-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="MigraDoc.Rendering-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\MigraDoc.Rendering-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="MigraDoc.RtfRendering-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\MigraDoc.RtfRendering-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
+    <Reference Include="NetState, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\NetState\NetState\bin\Release\netstandard2.0\NetState.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot, Version=2.0.0.0, Culture=neutral, PublicKeyToken=638079a8f0bd61e9, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.Core.2.0.0\lib\net45\OxyPlot.dll</HintPath>
+    </Reference>
+    <Reference Include="OxyPlot.Pdf, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c167629db8cf8ad9, processorArchitecture=MSIL">
+      <HintPath>..\packages\OxyPlot.Pdf.2.0.0\lib\net452\OxyPlot.Pdf.dll</HintPath>
+    </Reference>
+    <Reference Include="PdfSharp-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\PdfSharp-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="PdfSharp.Charting-gdi, Version=1.50.5147.0, Culture=neutral, PublicKeyToken=f94615aa0424f9eb, processorArchitecture=MSIL">
+      <HintPath>..\packages\PDFsharp-MigraDoc-gdi.1.50.5147\lib\net20\PdfSharp.Charting-gdi.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="PdfExporter.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\M4M.Model\M4M.Model.csproj">
+      <Project>{6a470149-d199-4b60-9f76-0f01fbd587e0}</Project>
+      <Name>M4M.Model</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\M4M.New\M4M.New.csproj">
+      <Project>{1e7d2bdd-30e8-48f8-9636-af93f451ec38}</Project>
+      <Name>M4M.New</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\packages\MathNet.Numerics.OpenBLAS.Win.0.2.0\build\MathNet.Numerics.OpenBLAS.Win.targets" Condition="Exists('..\packages\MathNet.Numerics.OpenBLAS.Win.0.2.0\build\MathNet.Numerics.OpenBLAS.Win.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\MathNet.Numerics.OpenBLAS.Win.0.2.0\build\MathNet.Numerics.OpenBLAS.Win.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MathNet.Numerics.OpenBLAS.Win.0.2.0\build\MathNet.Numerics.OpenBLAS.Win.targets'))" />
+  </Target>
+</Project>
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/M4M/PdfExporter.cs b/M4MCode/M4M_MkI/M4M/PdfExporter.cs
new file mode 100644
index 0000000..62be1e9
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M/PdfExporter.cs
@@ -0,0 +1,172 @@
+using OxyPlot;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace M4M
+{
+    public class PdfPlotExporter : IPlotExporter
+    {
+        public static PlotExportReport Translate(PdfExportReport report)
+        {
+            return new PlotExportReport(report.Filename, report.ExportedWidth, report.ExportedHeight);
+        }
+
+        public PlotExportReport ExportPlot(string filename, PlotModel model, double size, bool forceAspect, PostProcessPlot postProcessor = null)
+        {
+            return Translate(PdfExporter.ExportToPdf(model, filename + ".pdf", size, true, forceAspect, postProcessor));
+        }
+
+        public PlotExportReport ExportPlot(string filename, PlotModel model, double width, double height, bool forceAspect, PostProcessPlot postProcessor = null)
+        {
+            return Translate(PdfExporter.ExportToPdf(model, filename + ".pdf", width, height, true, forceAspect, postProcessor));
+        }
+    }
+
+    public class PdfExportReport
+    {
+        public PdfExportReport(string filename, double exportedWidth, double exportedHeight)
+        {
+            Filename = filename;
+            ExportedWidth = exportedWidth;
+            ExportedHeight = exportedHeight;
+        }
+
+        public string Filename { get; }
+        public double ExportedWidth { get; }
+        public double ExportedHeight { get; }
+    }
+
+    public class PdfExporter
+    {
+        public static System.Diagnostics.Process ViewPdf(string name)
+        {
+            if (!name.EndsWith(".pdf"))
+                name += ".pdf";
+
+            try
+            {
+                return System.Diagnostics.Process.Start(name);
+            }
+            catch
+            {
+                return null; // high quality code
+            }
+        }
+
+        public static void ExportToPdf(OxyPlot.PlotModel model, string ofname, bool bigger = true, bool noHalf = false, bool forceAspect = false, PostProcessPlot postProcessor = null)
+        {
+            ExportToPdf(model, ofname, 1.0, bigger, forceAspect, postProcessor);
+            if (!noHalf)
+                ExportToPdf(model, ofname + "_half", 0.5, bigger, forceAspect, postProcessor);
+        }
+
+        public static void DummyExport(OxyPlot.PlotModel model)
+        {
+            OxyPlot.Pdf.PdfExporter exporter = new OxyPlot.Pdf.PdfExporter();
+            double Width = 297 / 25.4 * 72 * 1.0; // A4
+            double Height = 210 / 25.4 * 72 * 1.0;
+
+            exporter.Width = Width;
+            exporter.Height = Height;
+
+            model.InvalidatePlot(true);
+
+            using (var fs = new System.IO.MemoryStream())
+            {
+                exporter.Export(model, fs);
+            }
+        }
+
+        public static PdfExportReport ExportToPdf(OxyPlot.PlotModel model, string ofname, double size, bool bigger, bool forceAspect, PostProcessPlot postProcessor)
+        {
+            double width = 297 / 25.4 * 72 * size; // A4
+            double height = 210 / 25.4 * 72 * size;
+
+            return ExportToPdf(model, ofname, width, height, bigger, forceAspect, postProcessor);
+        }
+
+        // this is a C&P job of the ExportToPdf which uses the PdfSharp based PdfExporter
+        // a copy appear in M4M.Old: this was introduce to M4M (Framework) so we can finally abandon M4M.Old (but we still want it to compile so we can drag old code out when we need it)
+        public static PdfExportReport ExportToPdf(OxyPlot.PlotModel model, string ofname, double width, double height, bool bigger, bool forceAspect, PostProcessPlot postProcessor)
+        {
+            double fc = 1.2;
+            double prc = 2.0;
+            if (model.Axes.Count > 2)
+                prc = 0.0;
+
+            model.Padding = new OxyPlot.OxyThickness(0.0, 0.0, model.Padding.Right * prc, 0.0); // Yan says there shouldn't be any whitespace
+            model.TitleFontSize *= fc;
+            model.DefaultFontSize *= fc;
+
+            if (bigger)
+            {
+                foreach (var s in model.Series)
+                {
+                    if (s is OxyPlot.Series.LineSeries)
+                        ((OxyPlot.Series.LineSeries)s).StrokeThickness *= 2.0;
+                }
+                model.LegendFontSize *= fc;
+            }
+
+            ofname = ofname.EndsWith(".pdf") ? ofname : ofname + ".pdf";
+
+            OxyPlot.Pdf.PdfExporter exporter = new OxyPlot.Pdf.PdfExporter();
+
+            exporter.Width = width;
+            exporter.Height = height;
+
+            model.InvalidatePlot(true);
+
+            void doPlot()
+            {
+                if (System.IO.File.Exists(ofname))
+                    System.IO.File.Delete(ofname); // something doesn't like writing onto plots apparently
+
+                using (var fs = new System.IO.FileStream(ofname, System.IO.FileMode.OpenOrCreate))
+                {
+                    exporter.Export(model, fs);
+                }
+            }
+
+            void doForceAspect()
+            {
+                if (model.PlotArea.Width > model.PlotArea.Height)
+                {
+                    width = width - model.PlotArea.Width + model.PlotArea.Height;
+                }
+                else
+                {
+                    height = height + model.PlotArea.Width - model.PlotArea.Height;
+                }
+
+                exporter.Width = width;
+                exporter.Height = height;
+            }
+
+            doPlot();
+
+            if (forceAspect)
+            {
+                doForceAspect();
+                doPlot();
+            }
+
+            if (postProcessor != null)
+            {
+                postProcessor(model);
+                doPlot();
+
+                if (forceAspect)
+                {
+                    doForceAspect();
+                    doPlot();
+                }
+            }
+
+            return new PdfExportReport(ofname, width, height);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M/Program.cs b/M4MCode/M4M_MkI/M4M/Program.cs
new file mode 100644
index 0000000..93baa7d
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M/Program.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Linq;
+
+namespace M4M
+{
+    public class Program
+    {
+        private static void ConfigureCulture()
+        {
+            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
+        }
+
+        private static void ConfigureMathNet(bool quiet, bool parallelMath)
+        {
+            if (parallelMath)
+                MathNet.Numerics.Control.UseMultiThreading();
+            else
+                MathNet.Numerics.Control.UseSingleThread();
+
+            if (MathNet.Numerics.Control.TryUseNativeOpenBLAS() && !quiet)
+                Console.WriteLine("OpenBLAS");
+        }
+
+        public static void Main(string[] args)
+        {
+            bool quiet = args.Contains("quiet");
+            bool parallelMath = args.Contains("parallelMath");
+
+            if (!quiet)
+                Console.WriteLine("~~ M4M Framework Running ~~");
+            ConfigureCulture();
+            ConfigureMathNet(quiet, parallelMath);
+
+            Cli cli = Cli.PrepareDefaultCli(
+                plotExporter: new PdfPlotExporter()
+                );
+            CliPrompt cliPrompt = new CliPrompt(cli);
+            cliPrompt.Run(System.Console.Out, args);
+        }
+    }
+}
diff --git a/M4MCode/M4M_MkI/M4M/Properties/AssemblyInfo.cs b/M4MCode/M4M_MkI/M4M/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2ce3cda
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("M4M")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("M4M")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("b6e4567c-558d-44d1-9cdc-fd4472e785e6")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/M4MCode/M4M_MkI/M4M/packages.config b/M4MCode/M4M_MkI/M4M/packages.config
new file mode 100644
index 0000000..159731a
--- /dev/null
+++ b/M4MCode/M4M_MkI/M4M/packages.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="MathNet.Numerics" version="4.9.1" targetFramework="net461" />
+  <package id="MathNet.Numerics.OpenBLAS.Win" version="0.2.0" targetFramework="net461" />
+  <package id="OxyPlot.Core" version="2.0.0" targetFramework="net461" />
+  <package id="OxyPlot.Pdf" version="2.0.0" targetFramework="net461" />
+  <package id="PDFsharp-MigraDoc-gdi" version="1.50.5147" targetFramework="net461" />
+</packages>
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/ReadmeFigures/hebb.pdf.png b/M4MCode/M4M_MkI/ReadmeFigures/hebb.pdf.png
new file mode 100644
index 0000000000000000000000000000000000000000..dfaf51f5d834b3dfb04186a6ff4d88e8f28d58b2
GIT binary patch
literal 12404
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
Rc?=8;44$rjF6*2UngCS_)xrP(

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/m4mdensedev.png b/M4MCode/M4M_MkI/ReadmeFigures/m4mdensedev.png
new file mode 100644
index 0000000000000000000000000000000000000000..6f32e4dadb12665686f6d0f30ade897edc71d176
GIT binary patch
literal 148533
zcmeAS@N?(olHy`uVBq!ia0y~yV2fv9V0^{F#=yYP)fsVwfq{XsILO_JVcj{ImkbOH
zEa{HEjtmSN`?>!lvNA9*a29w(7BevL9R^{><M}I67#RL9^>lFzsfc^?m$N4L`b_(O
zpXc8{V{-qT&!zWgmb{#m={-9$UG47S6r-0u;<wVWXPabBzMximS;hC#1Sh9MeUUxA
zxqA|%*C&)2%#htYQ<ZJ=?FwdTY3b(P@4hAZ`+YzE`6}}=Cf>7O_om0Esb2)we?NU`
z=X1H(lb4>}`~A@}{mxYF@E4~p?)-98|J>5WHTEob<@a{y-PysI!0CCkz5R>NmF|+~
zbIX|+7#J8DTx(u>#|xdlD_i&fcdXAGi@-JFr^41mIPNZcYb0m?>&4=dM@Kkk*;H-{
z+*a_=$*Ss$M(tDW_`uWW=h?14^`$8PhoxTJo)@2N?p$kRX1BTZsHjda_SwGLcc&|7
z-ZQH^^Yl;5o_TdKk&&5u<o521Tle}ynTVK}RB6TSGw+?<+}aA`tNKm!9;e0}ua&g#
zuZ)(=XSd(J?)|@iyVf5*{d42G_3`}IAOC)RZLRSuuk*`ae!Vk&cfS4h#Jc|y`I{^E
zK8!Oi{x9pb{^Xy!Psw%sXZAaP|Me%retw;9jQ@4>{mb8d`Sh^v{x09|Z_T#9{<rJQ
z?T7!QcEr}@)X6h4DA@Eq?mh?#0|th$?>o=ebAs6lDl0y?{^G8Ed)Pae5hRj#%69Xj
zqn`o<R^66cb=y|kINU}*v4%-`Ei*{=f&LxsFfD%8g}dI`?|W~*vgFcY#rEydRkl12
zxk2(QtcRvQaQ*=fYl9+=_$Gdc_yG;Y8cw?weUKUk<@OJ%KLqxH**!uu)WEZN$&!?i
zCEGwYDg03YyTIt>o+IKx(I6p#I)QyG@`t7;dZ;VMgzf(4-1f>;e#s`$@(U~9e7}5g
zOZ_oU9tH*m2NuU4LNy%mDHi3R5Ndb`G3c<wJC<j6_cim~z5P=E?dq5Mx}ICK-~E5Y
z0?JAXO%O#!b9RGlI#2+$iKmn)c;_qTbHdX@zX?xI`Q?4t=a={0&s*x=*;iOLo?Pzv
z?RnklCriIZI@uoVdfQi8mcQu#glW}B&+nZscW%P8-U9`<7f#o_{jyH)^bHsNpN}hN
zX5GKJ?&ROrUzUHr*nq6EbNcb<!#?%87s8o$CvsgW`yEtgd`zoG^6$>>*g)l%x2GDv
z{u7^P^mWtbroU<HK0laQm#%qVDs$BiKl{|RayslFGyFN?WsdbqKL;7{Dg8&%J+t^z
z`EC~uiJx4#=KJdXV!yo~>r^d&$6B@gRrMFYpY_4#w)|f_%{$j}2fM;KgJlNyL);&&
zFwbn=awh!QbGcjr<1=L|CL9&%xgl>~6MQ>+nk)bNXWuh-|NR+zSe!4ehHnGN(my;8
zyPGZ7Nc3%a=kfEmZTWq>@5lccg%$7X`FAH(Ue?HoH~#(Wf+N$5CSOh0T<u|Bw)QaN
zX~pE53VQNdCqNOy0CM)3#G?gEO5IQ0w~aq@Tiy2C^aZW@>Hpq}Y5n|Co%uhu-u<@y
z%O9^dFnj*@s4twi<Im1Fb-zBpJbz(+e|<-g`t8?z)lAAQa_<(lW^769oVowdbj53r
z<1`n)oBCM0uI_VnYQ4;d=PV2i3=&d@#M$!`TkQ|`IP7^|_4~|{DsyFthu!h#W5ebz
z)&86Q((!q+GK=J@In~=^?xn9eZyOe3VHC3S^Ty?+Q?BOo#kXu|W0tyX2J(4BIY+!q
zNra+q^1W&OE&C6PFWY|JXH~gzlh{6)U9b0knY~E%%H)Y&tL@5WYiY-nNbe2)cK^${
zmFF*5FO^?pKYfqCXVp{N!}AYxJZxPY-hOemORHg(gf5>{h3wyo4bzW52#79oJ1_bD
z+u8oFwb#ELi8I%`y<ofVy*|gjr$sxHrT-=WjZ3a|wcma`+iZRQvD}%{iXSYlI&5*>
zRREOd9os$}Js6$v@VtX^!26x|-0E|e?@nmZ|7Tln*sk#Go_5hr_Z5$LV%xOB?T<1X
z{kwCHa`(-AH|tfsZq>HWc^yGHs39EW!32m0T@((9FWWxR^K^dLg+tTV&i}*yDsrAv
zc-$Y&r8Q6FLvOB{S?_-{f6w$wb(P$z{~y`DI->e=>i)0JKOSwXIa-!JLvii$nbUV(
zm(H5G{o9?x3h~#H&pkYN=5_b$f|-1Qccw|NkGyVbygq&Tvj)i+apwt)3=9q%6l)4U
zh(-M{zT)sh>GS`c-Ll_{3ZF^qIoESO{l^vGCg*p?L`HJ`YcrFXd-y;xlED!#v!_-2
z-n4#}L(>yKoIG)=e{cV+x_`l|{;b!(c!>Yrx>I{5-YS~V#KEUCCpFL3Pvok#ON2iw
z|E_s)&*IMisaWwl?~2)uZ*~s@oO#;Y9@WS?uQk4LFCq5)b(@HKf$dN4eLfm;{jVXY
zbZKB!I`F|Qs@z0iAJ3)R>1tPZOCIEi|5NqO=uqNuH<^7ej<dPzPk-nnqMTBgpM1g$
zl++!#T^b(tZh4oXP$Rhno>)}spUltr*#2+kvziBgza0DhJ|J~-S&Z?XtxvgpBg=1E
z6iR+$apTfIlh`g$)A4%e72{TOxwo_B`AhHjZs1ksi+^)PAUUYuiQEL9pR-i&Ok1gU
zNF0>XlR4sLmb{(f@w#uy{=?$m-xk-u(FeJ(@}1GDc{<A&j;t^TrR(O`Hu}znObXvO
z90hxfp~B(EBZb`ME>Y!6{>bi|vE9GwjlLsyyh_cbP2Vg6KiMtHl<t_<r62sXO7?ph
z+s=0JKR0SS);)ag8`y7|a^dsr6(5}Ri)Y)ny_P(ECU-}E`^?*BJ6n_wui2h7dF7pr
zvh$pmeXRYy;ONJieZ1n&_U+r5yZt&S@&s7m?hRS6?e_FfvjuxTxEj4G7Uwg$Q?)}P
zu`6U<o6_attuxv<q*vY9x$k4ly+guR`{J(O>05lf=k$gQPzrd!_^`Wq%Uq$%yAx4L
z!0*5R7;CMa6*Na(|NN4@&i}1{cI2xYoLg|Pc!z<oXxWTc9(&#AI5tP^tG4zvs@ivB
z<pJ0Arzc;W-Q0h$>R8L)Ab-K>xd!&@f0xbJxz}v*-Gik)&#%NVsRaL=us3{%?)DGI
z_UKjZ1tp6KKX;3}8MMCI*8ZxDUAd-ke|_<Dy}mfDV`tZX?<y>Q-}q?H&KAX>otj4q
zGtXBRf9ZQTA@#$vOUt;D=a_$~0wt0Ur{*)dZD@V9?f35I-Wz%{e|&noz3<}xePu20
zepeda{d}kH=yhQUkY_pM!8LWa&XseqJ&H$KQbB?b>cN7i4SuAgm|(3#8;{#YeF;bb
zDHh-XD+ad#2BxkDxq^Z6)`Y&&;ptDS`7ga*eH~QzG2EFlJz1Es#z@~b+QvNbaI1cN
z<TP*llXt+@6$D!xXn62QKsm(ox!RfK^YqR0?lf?0DtM^E&|s+ib~*!t!X}UBDzmoF
z(+{c!>tJY9m^(RxBmO{hiJ=ICLd254LJSNpVlU^oKpRaAVTx2ypv)J)%x9)i(zCAz
ze=X@=$iQ$=3GN;#aK2_>*ueF2j*E2Y52H!aho)apj^$xx;9!w5Ki9k^o{6F1gwou}
z8Fl>eT1hYGRVS@<iUPH37#IwAK$>oUSgO>g)-&6oP&6-9_)$~&r^lB+hX&kfDHI8P
z5hnL0msjW|lO}_Mf<g@=14Czt-y&Ak8b<|ikTTd!IOl1?-J<_EV~*v44`LZVtUr9%
zxVLKur-|aaLz{K9wB9gDpy^)t9GtWmB3euQ7WLNtFjDM$v`CHTo7TpRACDsYs}Jw^
zdW%!|5^E4cgG0xW52<thnldpQ`0%^><s298tn=bujk}T-zbnl9ZoBOBw1}=<$A4Ds
z==;29fzq`Q^J`!Gw=?9m{d)(BA%_Cb<7u->{1!zj)vPpNm(KgXSj{5-NsC6;>H?ns
zPb=QU<j07JuKLEM>9y<qwKl!k3H#+mO%pfh?!Cvt*R*7^%JvPe`OE*k)G59jmu{PX
zJHrs-s15H8l#Oa8o?>KB>fBzt5abKBnw5KRy*n83PPZnovHSC%1qP=gw>Krd-+7!P
zhV8b&{PqvK)*b(SsjNl%Z`8s61w5cUEl_i>@8FVRp|esCyZ_cLeD^nL%1?o^8Lw~n
zxBTe+kkfoxM%~-S=f;i|PREPpWw)K~Et5TXy1+K_jB~%__t<&fH{#acez7KB5#}fn
z!F>!2E(-a-oj|dg`QuPyLy7UL3hwwbr_Vh7cKyKmWPx@=nNv63-#@%Jj_d7LpWIun
zhj<x%9z2i$web~x?2&Eue6BJp^^myxdSO1t5{ab$FN=&0TNo7b>T)fXNn~p`FD(3)
zDzi?+Z6~X8-uZOB@bkt8|J}G}w|@0&ub!_yja@r^Gppa0w%v7mS$S+mNuvmZg1{SV
zFHm10aozIO{e1Z=7G(1_i$C4_mV0TjP_D|uZoarPsd8?z`*<#Q?_G4kGGwM{^)7ay
z`vM18*!mP1{yYy#7Y^NW^`~!m{dVu)rf<JG>rH+&V{X<;k<(k7ebqD44+j=TzIT{^
zx;W<TPQ7cz<=eh8<?boCb9m!3F01QN&aS-anc=aPvMc1*Z2$TwbNcgD89TjZ>(2=|
zeeJs3(okO4Ex$g+rKbLs`&yj)moX|n*eUbrZQfmbZKrF!3~-*CRqpyUaPgnF+M-#~
z+s)?~+?u>5_eTDj!#{Q|sj80ZzjX55=8*cu^UjrpUp%+^+xN@w4m_3m`l~{EORo6&
z;8Qzorxc{M|2i2KzSmcKb>E8LE1#DKC7pHiS`q1dT0b}Bn%TQ$Db<m;j;@^&x&83d
zo#tk;3l`5+kC~Id+rxd`l;F6#d0sm=9KCl<?W^(ee--twId9c{?WnblyPg;uEU0=l
zC#>{GVPrn*-n(^SS5sNTzg=GV?Xlk5&%d{?+tm^_$KEef^ta~UM|`I?<=?KVj-D;e
zCq2J;?ziW+3YK<!iZ;J|OYQgTt@iB3``NZ_%9~&HcenoB#lmMxOLl(V^=pp!wRtA@
z{+IUly^XECdGXw?)9pRqtLl?Qb640o-hR~=oVfRIdG7k{4`1coy0WeN+ZLI{cYe*y
zlC9(a`g>ONe&4CNSvHI6!YxzPHMe~+I2x9-*z)OWjg5)DQS+iUU;ViI;rwsMqrzeZ
zpN8&RdyhHnPJnOgbpF-P<LXnl|2ld5ae<Gm-G#LOnfLSlz4`pS(f<CQ*oWV>);|(h
z8kQ%Y^Z!!Xt%_9fwYy6^_WtADnt%JYg^M^_*46i|w;x|W^>%sq+o0ELR#+_Crn>vg
z$K4l;&Rfgn@2i;;7Hj2kI;-r$o=u@#|KAnAD!U~0`?<mMKaRHw9(LS{`&YZ{ikQUh
z!|#93I_>&xS9NIgYi<AF;Pm#7wQFCkT)cPf@s}6A2EW>UAtO8F>D|AzTb3=l^09J#
z*`D@awpV|AKEHnAe(qg-^~=Jpyfv>ozI*+gu+p%wTlXKoGJaLOXz$$+-=Cr1zOT<Q
zcb&d|@y+Gtm$w|Y&Ukh)HY?hARYm<q_r2*pwQZ-~zMq*XZFF9KL%rCW`<3ak|8zc|
zf4bHBN_N`p^*8Fp&pma1cs}akQ=!MTl3m-A4PMT1*&OiW(Hq}I>Knon+X^;4+SaFh
z<iW#zJI)9`ICAs0VD<DD$DbAbwK=DF_Da>ReeANkcC1~0Tjtf1tR1;iUiBM73g;XD
z*|~3icsetE-a_BmW}T<onz_{(1URjtc4d}7%j~M$(;&B7b();q_6gVA_~(VU)ci=Y
z*MA_VcWjQHn<c;XvUcTS=~+5XA9x>nCp?f~dho2!Vq(z7d2xE3toAc&l=_y1t6w;z
z?X9^{yETYC$X$1-{nVuO>bpDg{l#80wRA6$JO2D%+J5_m3JZ;0_lEt`)Rp%udjI0O
ztY1lT*N^|LkvCfO`ziyDSw2rFY*XA--eLJf;M&E#9y`iA4YzmaIZKuKY=2mH|8)HF
zl?@q(9(+h{e$BseZ`R(2+b&iu;a<YP(0CxR+d6Z9imA-55B2*xJ3AkJdidZFCqv8X
zW3$3;&5H5bthO#NvHRwf>Q5Q}PZhhxo^I9u_V!_}(VgP{=^t9(-|qi-l=J)Io^S6u
z4{(0}+4e!z;D@p5ROW{KGY3y!xuoKy6aUyef38m{r;6CTv!>Jbmf0Lkof(p@vS~GU
z=GUWl9~pgr-l2V3{PoPeMJ1Ad3-V?DzHngr`zByh_wsML3*W!%TAcpobX{FZXI8L&
z^*W2rdG<of^<;L(pLrsXTDW?-YVN$!+NoExw`U$J-SIBq{CTl$Dwmb4W#47&>HA$=
zb@;q^a(?!~ttqqWuQuLJi;=thYHptPI$c-WdBO92jP>-k=C4lP{bPCF<&4`q_3mH)
zYErjm`ti8$dpypH{}8|Z`s25czr1&|Pygtet2etebLHvJ9@Dt?mmA;RS0{hw_6hNe
zrM7kVLv4RoTRab16qUE9{M-CrwNLNQ&O5%xuD$rrtmmc{(~g&0Ondn~`0>{}^~e86
z{n!~9Gdugl^7#J+;=d|u@7mR~iQRhntMB}S2iec8JkPzo_S*BltZ`m;L2aFF-*uJ0
zaXWqTyOZwM)%{b=Tkrk&d++V5w>7Rl{yypUqwkk$XD)j^BmQ;3ThArAF-iB$@`C^8
zExCUBNBOeX8SidC+;>NIzrS(zr6d21&p<1&qld)P9g<oUzX?mp@%8w}ePQ`#&J}vX
z@J8$Xwa2;6uTGM)JM&MbId_JW*vox$Pk;8_v9H*|@bSkDw@>fMS$xz(+=G#U>EOP@
zJUc%u-Me@0v3_~~@As<p4~a8$RFpoS71}>7{N1@v95IXJ<JMR0pV2OTc@MZsJNy12
z_Ycwg0{g(#m)m*Uiie5EmsCw$wqjkI-JTlGTBT~uxjU;|*BH+U-WL2y=|$G<EURp5
zZ{2NT8|SUB%KNfHal)A|1s?yl{e2|)>(;S|U;nm+?7C%ZAroxy;e)?T&()2~*a|B*
ze4J3Y$9C)6>qnnXbolRg>{0E5_q_GjZs%?3Jbjq6e!lzfz1Q9cFZjlJ_+uH5+T6(*
z$<q58R6W7IYCJ4feyQ%HSH+Hvixw$WJ(=h(u#dsPMa#OR^HTghU;R5%9|~>z^I%(r
z=^2Ud1utI~$rj&}Jv9BpQS%o4#>QlhhKteh(Jc>-p8Yyc?pI9REUx&9cRVZ475vKm
zJ0<&c=tJ+`wQg&YLv7qoZZ}t7@!@HL^U?@*1_$n{A4WmoCM<G>6t%e{(XL-pzvDye
z|75woEqn|OQ$M!+)I9%of&ATxPlR@SNY=C8wlyjyU*q1l)=QS1Z6A)_`qB2`sQsOO
z`8~Dy3=YTk##xj+{LretbNVZnMe%R$FKn9LZnj=%diCP!y=!D6wDq=cI>~;0#e>v~
zAKyMIVqh@NKb~;OveWF*2i8OLp>34^&8<E^g5u)VwMjirJpRM5Y9E7x#beITJK4Fv
zr#zI?eRiO=dQZXm>8~zbEdTj^oBot@o+d5&*N(x1#PVuT=9zjW-F_FFBa`IvdfMkq
zdc~tYrFw6Hzqsw4i0b_95$p^Hcph^9m<qAdBBxOO?esMb|2IF~8D~+F_{;En+r(G=
ztm1wQ2Ub<&iOn;Z|2)gEUAO0XiDrj(VDTPMpB>WLRIJ%(wokE!lSjsxp~tW4sha4%
zH@P-?di^dJRoO#-e5(2=SGd!B;hon{cP@$%-M41-q34VYHILs)_*ki56xi3mjwthI
zeptF>$r0A?O*3sOjjmqr&w0TVW5LLv{m`J*Sv2#|pGe{BD-7Ib8PqSyxoWm)>+PQ7
z9Px+5;mLd5>ix6htmiE_H2wA7gL6afuG;ncchAl^H{+9a%hwvGRPUX$H!A;2)ys|h
z`GaH`7|J=~53ENik6k6oco>{S`NgLhm(D-l^0WW0P`1yE^N0I9U-x;u=H2vmb6|W^
zFPj)&z~N~JQ&H@GmvEU=bkCBIHR99V7v+kt4*Bq@w^#ef(|a--WCISK4!a#zxO3`R
zi%p)b`j)BfN1ie;2({=x2#2(mA8a@}cT4;xrFqP0OZLV|=*?j`5TRjRl6ivj^J8_<
zRh(a!esx>(o9(9G?G^UN1$S)t%lXs9^e1E2{J+}*`5$`!cqDyj`hf$6EC)`gPFo-N
z@btuI#*aREe0gzgt5(fPO>0d(dB4xSX`*{lZWygNdQIut##N`Tsfq5XsV-)Uee0&L
z!SF%$koX5}NJJFec;t6Wzg0NKg0Ugt;XQ4eoEg>p(^psrUv2p*bHd|wuh(1NO`^9}
z*dG(zb>-N+#ZQ)mzhsw`U-<snzqLlSkNtaop#;pctn;O!dp>MP-peeJ)b;Vnuc}40
za&<ndjAiCMZaq9*D7tgUol}qe--SK&{<Ko5(30UmfWW?n_?_`GtPC6?e9?D2e=jX)
z0@eQ+%e%8e^Tp)ceAxBucj(4_t@(Jp<tG=vcG(TLy`Ft?dUw06GQ*S9Z0Fa^7u>Ny
z-0Y8auHAHp>sI>Gulq`W7^R+?B3PWyoKoZbLrB7+jX`4OTweFkACqEZHT3#jjQie5
ze|7m?HR<owZC9`4zZQQTA^N@Kp`5<VzL=cdTGtpEB)G5#lEVYfkJsJg!}dm<*&9=F
zsb=0`*Qj)poLV!}H8XP0KG={HT6jD&@K5)bjAL)z9(?+{Ct7pkk4UZkO7p!V6l)|;
z@;>Z-{{vKq7w|9|80F9P34Q488a;Q1&7}hW>Hg1`_GB%p<%>3AHwyg`l)YN?b%f;i
zvWE{^pVv&B#>~LOj-H7Raq7)sXt0;P9ko{Nq&d5uO+UB)iu)0lwbmI+ZSywU^mapF
z{2b+i%<i3DvxS3RzMN8D{_^RkU)z3X{Qep8>V<5b%#Q$$_^j+~(S5wq`$CRfWcmI2
z;dPS?CRb6eQul>QdRZI(a^89O;@NY}o>>cSeYjr6+nUc2&(vIBA7sf;VRbcVWz+Q5
z^x~sWCu;oldAUOU*7dz{X1C0`POm+4=XLAz+KI0i7<i7~Hk?u7w@9&uksp!=HvH#r
z`@qVO&|(pCa(<z~hrKbka#OT72G-w7(4QXc-IY=G#{Wfi<aVjX)02O04}LXKeC4{W
zJ~{4>a^p4TPR=;U5nuOpb^OJ@-|zd&|Gsg2#fA+Rs(%<M_G$T~ina#Ir7jK>G;ch1
z^S0)if@z^5L05KgiY%A^-E+NrkyGK!`j612;(>Ba(bu6rHf{g7V~34UZE9^FSG-x+
z)b7*mi=S7m+!ES#dTq|rpNn67WnwT_UGebrw!78tXZF}dKuc~&F$#`>0~v)I4l#CH
z7CdBVYnOhUczj8z+u^cvZri>#H=e#agY8sTtcU-Sx=Tx^pPpmOe_`ILoL4X33*DCN
zyLx!KczjJDtNvo%_&>sBApv(<F0$w{6-qNVgMyj)xB$aGL;o%Juin?a)@gEEFKp$5
zr^P=rmFH#5Z<l=hK=t}G>7wGjUv9*|{=R+bDo};@g4LZNf!C2g-mS>_hfviW{XQlJ
z7Or;PcT4^TSsZK?<_LfI=O*8mY-#&Co7`I}|4r4t7d=!wQlVj3{Nj*v@5c$rm#h{W
z{!4zG`S?ulmFWf&yJWiVJLv1zoKh|L`Egc%K;BD}tgQVC+a7D@zh7D6x9F#Jji;w)
zpl$4z+^46ex_vOpy=4+w7Hk{;=JaB<7m0@#u^kqfUgXX%KE2>bbi$)+>jb}4*vPXx
zo;!Zd!Q<<Sz<%|BkUKBh45hiiW&8hgONtl@w(i^J(W<YVwXOTK^2N#4W~)A=zC5}8
z=+nbD^KP#@1T9Feh02B>JYDQ6QOC#Nb{>+(dUJnVit&;B{QUACrsC<x)3nZQn4T&U
zym{w3$@S)UiaaYB&(5Fkc=YTcqXNZP1<**<@BayMu&`-!m#cKCy|$(L{oe8u-M{|{
zWzB!Pv-o*XQqrOwXXgISF0QXPjlcbQ+49x>W$E+2?VNtvBd)vqopg%!){73)lyefN
zi-%vHbR+Z2i%tDITJ*TT)%EjTSu^RjBx~LNB}Kkg>KFe=?km`FNb$5YzwHtxiFuD*
z+4W<V<;NWs-+sP{%Q@u4qifYiIYM^Kc^+TLCLeS`X{uz*_3%WmkcFFLD(t3j^VwUp
z<IefR6TwNi;`cp<1H7+fUl^<1TA#UI{I!eJyqNjT)BRKQmo7WUxA9znRqkA^ijx~s
zSNsl=UG219oZ*8wv<33*p|@LG_Jl>ulJSSci#Z;vPSDgpv7o7m>6AvE*!05+km}*a
ze|F~|Oacrtd{d_I{3}fg44lZ7@Iz_u&UyB&x6)_dUwpAmUuysHe>HbYpU={Kx>NQ`
z^X*5y2cB+>PSUIkQk*KgOJ&_*w@*<s*4}d4)^re3YTteoDzf#5<Mabhck({Y{QI)C
zWbNloH_n~?@NFMx49NblxLnW~nQb-SHqQ_J{&V?G^D`Y)`$9xkF}V2VtUtuNJ)!M+
zj%G>x@wXoXQk``#_t#e0xfd)}d*83UW&Puv!~WnVT1<UEUyv<>M9G^hNwr($pQ}E~
ziSGN8^q;TrXF*r)QLUPX_w+9nX*(l@Nm}EFr8By?jo7M}Oc4)YYgbX`jc+@;H<N3{
zA2&<UA0hUeAdT3A|LkY5XYLpAD|pD#bkMdY@$K8UuRe#zzyG0q;nSz0#q8|-)kphx
z{sHx9=gg7m|N7&3z-xog>9bGol+{aqw~&8&p=@6B`{LPm_ElPbzjE<)aYag9&by<J
zb7!A9zj(Xz_3Jx&;y)MMUi^MGujsylnfKykyLhhJeJ^^rN%Q-Yl^dR}eq84%FY#S6
z^xMJP_D<^e>{7R!em&!Ucpcx%tsU>{L2Z|97Y_u@*1Bzw+5GEgZ~Ob@@0ab5zx({a
z)7#zu=l%P;uYbkyXWu8^eqdx)`6(rNwp;h@iKk3o&T;vi{^OAO-QP!hst?S_SLTY3
zNSv}*y^G7aWuB^YtNJsmcY7nwv_;*&{yaFfRYd&g;r@lsy;pqrwwJL%TC3!s%kSnL
z%elW-Jp9mFeZZr%?a=4lA8hnx_L<z0I%*Zx8mG_jz#ls9DD=?Vm|gFjoaP<pr8{am
z3XZClXf9xqo9FX=lB8b8!_=${W4^>UZ;LN1zP-VI{$k$R{RiqMW!{(53pQG@pk{*|
zyQ|*QbKlsvAKe#|-|oG$_O5{BC!bvoTnqvn-~J!8@qjcV9=!Xp^sPzl^XqQC)oMGo
zpA>&`_ptae@w24`2gR?Io;Xzc)A0Uv@h@Mk{zSy*$Y;r>{mYsCy70!~y?JMBjd$p^
zGs~#Ga5z}wIDh)<1ugUE-@bg>vP1IK&Lz2zzW%VRu>P@;Pxy!FtnHn*FB}xf@3%BC
zvY&sm;9+-V?X!FTw9oq*R_!dP<%|3A&;I{m_RNPp6`|+<etmJz``6bG)d!!xTYvQF
zU;EVh|Ms=|sy1^Hz7#!tzWev=|Bo)6ZauwizW>tx`bUrD-mC9V|8wWho?n)E)4uGR
z3CbY*4~y@c$t>}X_i>$c;g2Mqw@+o{e9o^<x>71@)^k|uyxE+?oi|U*I?uSd@@KTg
zgu>YSr8f*8{*2icxc%VwcFqd7PEgfy`1W@;g-ag|l4U^cHJd*(LewvvvDoC#s(=37
zEge1ibDFm`Mfc=v%@v&<Ey=D2ss#T%g0$n;2=03^e^+{!`%i(dXHVaKJ<CL#pG~zU
zp4B#H{__US^4;&Ztvg-3>cG*+c4n!sHGP&@Jw?gZK92p~J3nu1#mq!^-mW}Vwv)py
zsPpLgsx6iwKMsk%kko(N{qkAr9=`aL=iG1pSY8#sWvTI?`_JLEhrfSKk6v}W^p3Nj
ztc$@u`#VK14gY^hoqgtYVZlOYuI~p9^~CGzU+@3=dFSKj)29{AyvMe=Z(1H`q@};+
z$i>~R{JO`!R`yMIKI-09V|yvv^!mm1uGcrmZkm|8H#uYDrub<W=l=gASXCJ(Qmg(s
z>}ySY{1@{tDgWNS`W#&Mbc2@kyT4EF_y63xC-TzoG*JJxkt6;GXqAuau3noO!&WJo
z86OPdzg>T0_i&$~%Z$D2j<@{w6D<r}w(v-W#pZ(vsT*Q1@4EDPXYS&iXE|pS*r$AW
z@2$no(EjC=uU5^?n*|{;a+$k+|9)|~Qq%nI>(=%8o~3JgoW((tarGY|J?d*5@n1G1
zWSubl-MPy0g8{qPyQj~dF8lqI_1rx*w|w#E$+<I1zkU`e;Geee)$Hq%>+bxS<K^b@
zrEa3$1SSTSroZ(D{Fmx9=R7#Xx#RIqlXE8Tq_^(6X4G%?v&k~vqg>ydUGvqUHPU<g
z-YF*Yx$aqe_<N~salyiuPv3oCYV`f7v+cLi*(S1fA8U&f4wm%G^T}7(e$Fn)cfWl8
zecZdaV+&7b_n%+R|Nm_L1^d3=;#=puFFr3m@44%B`FCFpUe=~QyZ-6hvh6P?>f0rq
zc^<TF#guu`KW1dPvzOJ(ciunmpWb5Lzc#-t&+eFGZ~iCp!ShK?OD8@mTMDi{nMC*T
zluMh{d~D{j^_jUr=L&e#4^$7jZ1SG#t5s3?wt^}4z3j~;pB`%c^?B+0y^8bb&bSM&
zSsDJEg)|T%RcZ>qd7L!h|M*n(MnU|$xa)iRxDrcdZg%gIyXAA;@S5&&reb}Gmpf~8
zpDUgzspP;6G#2J~h3DMQo*C^+I5*$^-9@82tu@EQZ&|K*_;C4`50Uq-U7Vl0?)kmy
zrunyHj+LI>t8wRg;s1F&UvFH`z0}4P|K;;%|9g7h%kJL3xMSWHpXr4!CfPq$@cr}q
zddQ2S|2w9?c*?0c#U@$Q_REFCa$no4ZL6f0roP&`XQl^pBy-7^rHf16@4w@%{%rcC
z&7b9i6S=<^{e15Kw|>i>C!wO(c(<NCY`g#1e9mJ1r57h{JLm&ybBONinNlrnzB=IS
z_D4KB&)6n8$$;8gZQI}TI;cd)M=v=f{!df4-{j6~P~%do#`4+~g=>;rpc&*HpCCzY
z8fW~KTG=p(r!p-(=cX=t)@H~q_wM!U-)hGUlHa91^ZXlot+e{wqo#+|`z)%j@mR{t
ztb3=p?R*!c%boCFp64OA1H%W_l6@1-eYebc7$^JZ=HvOh&isiubb6<(?4*k5<9}{G
z>;8Ofj`Fv!mL>+@jUMi*dMBi&d;NQRwauj5q-P&m>t7{wZl8KsZQuFaPp{3|1>U{Q
zINYi)v+IM5p3RyY&yU;s#s7E_zH--=!W-ZA?v?xgz4ZJ#+Z5~E#VzVW8Rw2zu3CP2
z-;3HW{Qtki{d!bfzqV<5`{aL($L{}ijFOPf-C}&jth%~xS-ZmIX}v95c+)Ofc0TL>
zz{=DPFD`=${l(vXJoNDN({-zp=6Woy-dmIWn5*z-fz!_G-i}{6L3wolXGnlu<BV_9
zc)q}e|7Pd{X5r+@oy8s-nY6@j7kw)#lz-Lzx@h*UeYbbX?0BJgc!&A94-dOn+Fo5&
zd8l^YVYlhYhiVxZ%8@EE0iK$~`S#gT?A%s87fbR*V%^yVrTA^1-MgH$qx8qdo%1TC
z&m8PpcUt_%*P`#wpBcr=|K8<$MrPlKsw!F8jt{l61s)GOtiOMF8s%)gbpM{{sV{$m
zh7bLhcdxF^tKV@wxi0>AeTvQ7%G7?t<=yMnSMREM(*54>)ROP_|J;aCv)%u5@1wWR
zkH34}Kc#NpLXYPvQisH|AA<%WK$U+-2<!B3m-YSC0*bC$?YdEW)3v&O`HpiQCA?n(
zO56WGv)wJZ*7oj{l25r^b-x=rroUaccG-E0=T)7$wm!f0V?}OHGWlj2bj+cRE8g$x
z+(oC}Y3;gD?YPETXwTiC%I&^-^1a{g<$(qxe?5DqI;HR%gKYM$2a8?=Nt%}P{{Juh
zf8Ekg4lkr%h@U=uvh?Ba@};}yo{G}1p0e34TK?+S>zd+K`cm`zzcpTz4=>^WBK5uc
z*YB8Vch{`jXXhG~c5J=3mV9ShUzpX^7<IkLF|s?nuIFCA`geBa-G9I4#JzsE!1(&B
zsmi`rU#U;`ER9S)Z<_V?^WX1vTQ<&HSJGQke}4W(?b>_Kk3h3Yvh=<d#dmWb&&YO*
zljDLXew{am#cK~<@>#_$;Cbk6^#bSV$MZT%Ib(M$?+&>?_xyYJ@4TC2w_J4ClWZrx
zt?ZjxRn9;2o}V%EqDrmTT3ODvs)~MMrgP|a_B7P7zmAgUx3e?zBGvX*nq4*6Y%_P!
z>dhH-zTZBq+MJmes&@AC(sd{ItkT&!Z+Av@b<p35Mfua`_iqo?pC3FkbNijRcTxQ>
zugk8zU3hJ7@_zf<|AMz4JoEbOw(F0d=7;JpFweRdkaq3-63gCVjb~LoTyc|rPtx3`
z8zypD>apXh=XoZtJ?`9|c&jUO<Mi1&S+OqM>+}}5UKi`Fsl8kjx<B=!z?`q^^~*9(
zi|^^HtTwrB_3iq_O&*0e?aoG=-=Zsb+Q;Ygne&V1osM+Z3iip>-?8n$$9q=WPw(lo
z|L}R`^9`5R*WdVl&?W19&ufX?oqOYQ4gcqbROjtizWiC@%;~pFzRmdk>)EDOUGB`f
z&oxmMxeL#)zie>i^Q_l9^e>+&6-kelyYrg&Xx#OT+gtu^IKOh{tJyo(XzP5BxPE0>
zW$bseXYr95|K=w@gEVN|Km}yw--3|1gn4u9OR9G8xWe0#hwN$!+qu;l9)7mXSd$q#
z`Pppe=|4PMvxI6_?mJ@h<Yqw5TeD4Xw_QAtWS#zE(%&$~+Vqdpo%KpC3hZmq-{%_i
z<B;v?AD_?cNWT?ed-%-PlGv5^3My<@$|wZstc}WU(Yt(p>CP6tn)@;a88SXIoH+_+
z1aH2=5qEFTk7ZnO5kdKrxZ;JZgpYJjWoUd-^ze?~D&~EWcg#Ks7zF9=*yZuZ{`eV4
zz1C|{)A&Ih-lgS;XHW`QQO|Et@^FGHyPj3a!=jb@T=*l+-WbHKH9rw%A+ck_63y8!
z7rHhtJiYm~ozrW7eV&;YTt9!41TCuo4KQ!_;EJ#4aI-ZRW7lzRUAf2S{7sW6i?#-j
zu-i9RI8A4LUc9n=L4R6k$_jx*M_)c)+qo{;+22<zJa~rBeEzb%y~khP`O2O3!%(j6
zO;Gvd8+G3Kvrk*)#9!vLx?uC7#(k~foXFz!-~WHEoPBqD=KZNbw~Cj#Xzr`(x|e6^
zzBKpgD&M-krnbLZmVca4Rk!Z^>CM^SE(-q+da*E8^UL2wB~c~YCR~ftS;qM~>1E%R
znO`O^vN^Tpjx+1m{L62GXTN+H+xg|rLmw~e{gcgNESyr;iuYXKuz%+ivn%hH&ouoU
z@<AwguH(k((=3~&AJY-tv*gS23u`t_HTiwRtjluaY_U0-3%2{MUGy^jb4Fe&o9>Pc
zb0+DpT{6Xcb;ix3YqxBv(pH@=yP##$-7QOUtk>=+IB;~|>F00PCrazCkM`eLdRt>-
zk@3;6b&H>xT4%Vv)jPHFZr=X=bJOOYT)I--D6=lo&UO0QMdse-7IRK5y6(UH^6cft
z8Q*rwT;+Z-J$-WKr<WpCZ{GyXO8S0gqD58Gd!<>4>&3(8dKaHhzr4rmx4-Cx)Jt<#
znRM;j(X&W@$)%o8sU~m#1f{x_DxKXlJ8qiO<u}WwFTM2URLHDq#m;BfmuYO)3VM5f
ze%Ajvs+Yg4T59syiRbJd3FpmO$r)vDlfM2~yQ-pU@1ew3JC}axPCotOsPoJ(Xa1S|
zI(e|9>f*oO6<byM<Md-4H=lcU$>ewX&$UL?)lW6IJZbuC8oE3%DOT_7{tZ|5^>UVV
znyf#zZ%>k@uWtA9%uqv<twz(9-P-<BJpIl7`^lYOoHCX6mMGo(XQy&W`uX$=TRy3N
z33HY4UEjBL|Fxwi$M0S1nZ+`zG?DY{%8mPvb+P>Zp1QNjcB8m)=EGK=sG#Yt-C0H%
zpFS@A67@2u>T$}f+TB8#X<vnAMNjqme0p<c<=d)Ld$#mVdi_~rYo4j@rv3XjpHIJZ
z=JOrpY|FE2Jr`PT?fmw9%l`D~k23pe@3wxsE*w15dF8kJr9n^GW<~FtlQup5#{J(W
zvm!V4+|%-3rkOq|^VQ6d-_Nxs3$4C>!(Lch^VuJ-sE~&O@l*Y|-^@R3`E;hqYPFO9
zetT{Dzfbthd&fC{4fl)vwp&nT=^CCI@Tb+rYsZeJdsg}XZEwBy-Ms%==C}JtXFs2m
zS?9X!*0hY@*`0dl7n;A_|9tzYx9hLoJ10H$V_B!k(mVIwy)^liS;+QxPl}|k<;FS7
z=AK#fGX2uqx3}XL|J^&MT5(=l$dViP^M9xJ-(UXu^qcoji+-EksL#J@`R%**Y_agQ
zi+74fe!D)?G5<#W{>lDJZrF3L{Px|ndD)HYm7jg2d@WP|?+v?U?;d*6Pg5jzV)FmE
zrK`{1o^vZ@R`_xI$#3>s|9&29yZ!Ixh;;q!`;Tva+kbzR--ddz$uE3=KmX|a`}bd~
z`gw2XPu>1z{@b75wmqABdb!2#>Pba~wnsyr2FAoLJ2e0P%5UF4l>Yvno)#&kyW+&=
z`e&DA?sNYBec<iKZw2pnw~A(8$)1`0Klc3g{q@Cf?(dcScK)@_@Ase1cB%YMzZbXt
z*1ox`>ZH!D+M>Vt@%3-h_O6xu_MWA_EPi|aydQh3zE<e%&8m-&x7su(&SH|!`Kd=P
zonHRtcCp^of7YAVhs&<t=f3A(%<pQu|99AC6_@e)K7GIcZ|&@?|9Sgsj{LK=%K6VX
zb<Otr_BXfB+*ka)>$9H9>i2Jaem_szwYT^BxtsOtpZ0ANi}P5_QukLhJJao@+pM}Z
z4RN*Bv*WC1=KPn{-MW5%&!bc3-M<T<b9rqaSZ#Jd<}ZH#PvD4WP)PZqT=LQLG4H0e
z<-D=a{u)h}>)Cr-wd7`vUSrj--A~MP_&~Eb(JHO_X)>$$t}bqMmioSPyBpVW0fCd(
zu8Obx(CXa1w|A*l#lwaBm(BCw``!6%|2D<xcAJf+?%R}TaA0qJsakKeP4m0RnR#a{
zFJ8InIR8Z4s%KS}#icJSdCry13q9yvlDlQr`-H&DN}~JLyog<RX!@Lev$?yN-+F~h
z|IMfu^Sw3Q^Z0DN3wu{TU+wu#UN6QgynUuK6GOrl&~yx>hYp#m1@{siUOe0xeP$sy
zzwxody+3VBR_=A&X1Z?S=`|-Szc~C^en<^g+hzUOlzDlXV)ecPjl=V+kCnE)`Dn%?
z<Lt2H5$F8$X{jqdv{tXsSsAtTkodoo2fbq=_w9(Y*m38g-{a4dLVj%e^hYw(?CSwN
z(LHx2N;&hs&2_t*UY;9vko*6Y)OQt+-tX`()T*((SvToi$=tw$-c{baUVS|D(EF;x
z)E(<Wb!?-T9Gd=|f9;YRkF8(So9l!<JiTkx<D^=KhS^Uc)jVSUanJ8}yJ3UA4HphB
z<o2JF&lg*fR68#rewBUHHZJL#pV*wIUwff9>sOFm?QM}=Hro?z-mW|)cAei9X);)>
z^C5S;rcKWRxuzVAw{PE?l)sA!4Ual^zxScXYlfEHi?5$s^+5Iaqp+;kuT7p+b=tq@
zh_C3-%iLS=;q%_;&<{oBbyB6@zXyAlo8J|(cW>2q-D>J)8(+OwW=~xJt3Kyf&Q~8#
zO`ED!<N4Yv_@H;${;&O}i};?c`k=b@4a432WfQsL_C?R|vHja8=fJ@5589kT_!G2j
zQkyI4MbRy{Z9NAsJpJ@Wuj=7*&o2&_+z*?TT!gF};C<M=^`q{kL*nyJw^nZ}*r234
zz2{j4!*KzDm0w(bt4C{RdOvm)-E+onzT<jX|A2?5zux;GlhxHOyjxp8vQ>Y*?46~j
zCi(AK+cdrNb#d&ga~7M<FOSo=xgyc7Z}TPMx5I9(I589b1yA;+i0+%hH>K-!)X|sI
zszmo}n6t^*ZT<A7SHelP3=H)SKbY!N!R?op@_UuXo43SssqZLYN{C(Fot1g*h$$m)
zY)-A&rl~hF6iN#+HgaFy{o=U2_cHcrd~JH`^L{2pwCPEKmI{EHEjz@y{g>(MO7k5O
zXSi=tVlFNG_Vb72?(DD?A5sf{a*2ZHZm)B*Yo9)UVR`tHRUekViQ25S+gjbVRbTtG
zpX67!+pnJ{9CvhIKb_Bg{q!x3Ca-p0e7teq)H8Z_7d1`S%wNB_Q~Ghphfh!XN;c{-
zH0)QXVcb7S4>Y>MbV!_G0~25Dvb*#1TR>Bn^FF1^Ea{t;xA9*_;fo}1sW&f+o}Cq|
z`qP>*eV(4oE}4~gRo2P-9kZ&rxvlzr$Ik7HM*Cd7ejIY{y<7c$?;|6*x*q%IyUcm~
zcbxw&d0##&>EFW#5==@H=Vk2wP-GbUX5X5o={u*+6scA7Zq+}&Zd&K-)@J>6Uf~B%
zn{$P&I=<9*dgzCu+n0VGQ~8}^vNvvr&eN<euK1Gm)AyRa{vW@*daue{Eu*TvGC3<m
zUNbTL;64PO8~*e2eEm0tx6==S<`^T~H#mu#PTZGSvGLK#wO?MGU-WWe>ihm37hE?x
zfmi;D-~Xr^bZGjdNkU&E%Z@*N_prP2P_68d9owJtuGgN*+@k&NrO|es;8y*ub9<+R
z?~iTO&!6WPc<^+zdhp6}1)JwTZ^YEjJNDd4?N)s3Lk{may<5u;O@HpA*u8#lT;Ept
z;+J0+so#n(s_o>8o5riN*e`nd@v5*%@ueZG`sd%AU#7lIH~8S`$KU1$#WFC|LwjT3
z1=5Y6m7oIq7!E{O>{-|px6@ktgKg}s+>~g0iCt6nRnC<Axib5OQSI~VFMJ-`75*)F
z2AN}=#vMPaTT^-Qj9E)l7n{b+-jnmMX4ZKlqZK=1Rv5Sa5Yv3<z2Tt*lag<#`(CcN
zB(1Q3`D^EkU3s$NL+Z1YC8GOs_LVrq=*!%B{O0Q?%isR<GUiwBx-(H~@gebdjEi6Y
zy!5)Y@WrC)y#@L+c4!y&DIWhN<J_0~^1jl#bXEI8zb*Fbw@ldg>dn_po4?J85N9|r
zA5kzr^soOl88!it@Menat|vmf{=Dc@iIT3p@qZ!r`PW4k^sWi+DLJ=!W#<>cT|8YY
zKDg?eU;kVx+n@S0#K69}Yxdm!o%jAQX??#Y_jzfbJa`lhl&a1yYh%``xp}c<%DHXf
z)@rxbpI8vye{}W9Z-+}4)xP`s>Ef9R(S2uhEmxeX=o9CQt2iefyMFine!IETA8ozP
z&93(>PT$?Cf5ijUuT`tK;v#0RJ)3N+t(m1^-X>F>8o!S#PEOND2$amC0}hMZeU#Ar
z{41drG9~QrgNfnb0pVxI{k2LsW4RuAtyq5gzw^`^S~2RE<G+30{_^$a{Y@8+`nt60
z-|{%rzwlMxmOD8r=j(-Gv*{Dt)NfvQ?n{l=3VC??%jVM|>!-hY<y1Jg;KTgY#xLT-
zm+bJ_VqaLhjw}9AA8*{^+hrTQSoQDU*l|c>@{H=eTllwJ{<G|-R?W$Gr^61OezouF
zqpGk^pPhedRm?1|;jXPsUc(iCEo4eZDI>!l-4_o}d*pG%JN1{qQkTPmTwlG=!(D|h
zKHbpWm{qZ{p+)bk*`~FUGD&Z4TnYPi&3nBBq@#6QKUe&EuS=rjz7_LXLt-X8ln~0P
zV-x)_A>rJoCLY_68S7s2IGy5VYZokzbm#J#werS2KgYtIdwH)LEzZ^ZoDsYtxxVjU
z?xy_hoA*9Ty8KPtTJKjc3uru#txr*5(hh!0v;9`lt@`Wd`vg5az4V+~;ogAbd*j}D
z#II?Ze!M&<AXWG7-4gFVGD{cMZfHLRo?KP^>$5WO*}7f3qgeHAZra)3ySUAnRbTs7
zn8e<=EcPthYnvxs4%e658~1F_URPHA`#K%subCKFoL;o)*90Zb5wYO^@K&8IDK@{&
z#<6iCq#!&b|Nn<OY@FGl;DbPN@Vk@7RlCp3+R-MJ9d&e<`O@EPH^1?=>0M8|{o-bN
z_yuN9`Ot-@g|+jS-no={<??;i_3{f3nbjE1KVQJG==53pv#)2|Hk{7CHRh^jIcG&k
zhEY!KN?*R@J8k;Grz;{hGihB<{q>Ino|ro;Pi&kttyrsKX7I#?kW~t$hYoHxdnPIT
zRy=M+Nb1aVuh8|=e}|__ea*VPe)WS^_1pXJT~3~|`}F=v>kmzTt9v`PYHvZlIA2`R
zuc9Mg*F4#}I6L&ir_+mHznDFH+KLaV&m{gWU}b1h-*Q;|vnSJhkCZdZdjkKoUfrNB
zb7s$-=(`-y{&<T%!-IUAdhcJScgMC#Jm&nibeH+*vRiK3mK=PLdhv3oo^#LHo1Yo3
zZk^Wo+HKbc-uRevxpz*TJ$?6`c*+Zdqv!U%GnjRn`^m3+x1Rj2n%Q_*BAs9I_pWt&
zvuln+SAlh?+<ENEZ#k_daV}SU+8poTho@JHxoZgK>6LG}Y`;_|<lyQ0_2&bH-xmLT
z9DQxo2i2cPZoA!mF{xRr=I7CWvTieXZ~JF9t1fXDSNyuKvBuA;B1>yUb)_Sg9hzQT
zdu`r^eXHJfamA^<J-y`UQtOZphbF)M{^j?6abt!90f*FUBv+jgzjv+J{-(s)Viyj`
z)bXEx-}irq)gCP8{!M)za$`~R^f@bkGZ`Pbp$chW*{+XSzW#5@veN>|>{(%*ubY?;
ziPzq@Y~1%wSNW>!w9U)AyldreJ^6k3YnEZv^##wnr-Di;R(8E-ipLkN-W>A7Xs5}%
zdsZ)Z&F5F|?w2)EyEVUlO~lmFuU}U_JZ4dI(Yf!V%kTN8{evH#Uf5<e-Rulsoc)TX
z>1&SZe6@I+HH$0$+Shf#2feG}cOAD1GyZ-gOmyF#q6$;n0D<*~rk`YAWADz(&@%l4
zSNxq_ySyI@d_LI_xNi41Z*|dgW--Sj%B1rvS3JM?y7$u(<tdOxGvh;Uh9Ay%cc#rM
ztKTQM>qD*H<X882qIdV^ShG1#Z+mI}g8lyeEFQyS6&)M<?uixFO0;}%y<a}(H_y54
zMlSwh>ZbDE2NQPQ`FwP`nXGM0k7aDkHg|saXJ#fd`{4y$Q@hk<=RQ$0k<|}U^PU!6
zeVrQpR}oau&77n1>q}RN=)REHavATO6*|EOPups<Hw)Qb{1<+W-##Vc&QgoyMSpkQ
zvl88-vvX6+CALc;KZ+K-Z`d&}wB*az3;o~HSATfAcCmEGhfn*Z#V-Gxb{f=xcHrFn
z!BzgrZrL}|rFZ>B=BRT#J@@oo?yKX^il$pR+wlK4`F}Km1u{``=zZOHYgo2#_+PMI
z?A_f%!Y^Md9xF?qC-(ZDPIPB4+auY9+|{pthnD=m;@)%N>DS!vfoHc0Kic&`Q|a(@
z=|072SLMrQzACa<)Xs2v!~E$y;`=`A^xkQBzxy%&@l#hTdse<(c&lXf<4^3lzxXfQ
zxqh_ecXHQ$^><6E7#<$8c@p9CluLBqp49p?ptdSs;oN}j|1Y`zN^o1l6<4>fzQAs>
z=WVkUA5?#xs9T(~MELFZo4-~(sA}Yj|Frv4=!Z>f>St<AzqYk&X?l0ahfi-BWoEsY
z^IA2ld|QM#gTe&pBHz;LeIJaTJN{P)KJjQ%-`u>@dwo~l`TNgfo;nj`0WT;e^jzDx
z_Ntz9&qo`5oBQ1%-<Msvrx1NCA%A+){4ba4ZFe^8n_Rh?D;6}&`*!7_)`p3nAj>;X
zx9UG=0?j%w9~ThdJmcKA^~;u>JeJeqc6!FB%ecI~<x~E2>530e9ctHVJytxP7i|^a
zs{edlTwrSY8+*%&hn?SMTeb5oIwSnH{PwP^F29SVpG)nH<ErL9`gKi<vp8RTob?tr
zR{i+M{T<(~P3<maWMI<Vb4a{6V$pvs?<l*Foxh7vJ@e3f|Ic1nljFfNlWSWpt?A6}
zxsZCwwlbvb#iWSuX)6?uUE^=lyPAJzLA(47@B3Z<r(C?P`t?M&L{iR_H3HxnC<XA8
z!2v@SmnL6c_8!IKVb|UT96T-ad_wZ`sO%%9PO|Mf@{#*oSoQhE_|Bzo+Pce$Rlht{
zO)B;LyIX4xO_y~4CYk!~j`}VAmpXxa<BY6~LOx8oG~G#-`Pw_jEf1Yma>c3Ltj*q>
zyl(pO?RvtXDk|zni@wc9Wq!@DbNzSQ#V?oc<*|PD#xl8Nw!VJBl-=xl$3F9Yw0U=1
z%n>qJ-|&Hzq2{P_&)QzQ93I1O&9@(|R9NS-rrH?P_mJJvUCN>#d(o`w@xknp_xke+
zB$L+s>3VHw)N>}HU0=Wgy4tY8W5a`*bzJdMyJlzBg>K`DUp6nwYqMWuebJALr?jnI
zA>G~Gc?JnpdDqiK_nol_St9rO$uF5bA5wlF{#tdjsI;3au1LE0YO?Lcx~Tn&ub=)k
z@2lMFh_j!MYKQ#zH2LpNt%{xXZ=Fu*+iP88W#HIyfiM0}+wY*s&!ta4E$oOr_0?+G
z1{YtKmD5En4vSAW?5mY{FY%C@!QsH)U)T4;8}&Uh{D$8S#Y(#6gNC$A)*k#XUY;vz
zCgmYJ=X&bj%lnt=*O<@SH`~<W$c<;koR4-H7$L3pRJapber5Fo)qPo}Q@2f9{U9|i
zRBUgNPTw~(Iq~b9?D0`k*ELPoUw_i%)DH7AvMUX>YBY0;d<_2g_G<4r_ciI=*Gm^J
zC-`iUzb&-7Y5J3WDOpk(Lf5x;UB0e&aY^k=t(ut;4`#f6I$L^rfdBrj&sQ)ud=TCB
zkUL@$q{*eETJ!M#inB|s7#?&oPn&U^yXQhH^Nh=`eT5hPp1hW$e0$bG#bf9GehUAx
zb-mouPQ!BPy|F^w^Ot7N?dz5=_>9!^g)Rm>G*_-D=loP5uYDWN8D9LiGxf`@k1ZSK
zX4Dsdt^DP3E=Y7=${bmq&^zrix63x3dhzv=tq;e>?Wya%*H6E?c6I2FN%y9&J2d^z
zyPE#eHP60I;?=6COsn5^@wMu=AAB427#s{Ltl1`?^V|~Hs;`_HC%?uYGQ%)YXy1&7
zhd3MZD>@A2qI#Ug<+6I@y}#ypedpcuR+=X^>e-DKt?Rd65MMOU+-drBu77jSnef@J
zv+Cgk^>v}OKSz2k^IsEi?@HWCUt0L>{;C?z)bMCfdi!<jmyGD1n!He}((m7;pHAME
zTOe6<W_p}{?KZovb-iH^Pv1PZ`HJ0zb!xZbD{>cJe7$tj`k<KWT6)_|S3hXA-JsgI
zZ&#vWt!2HQX~~wZuZ#>$iYp#+x7b5!d5_-<3P7_XhaaE(9KX~0^ozGbe?R;>z+(t)
z-Kf5_-oN;~-Ig9X&-(pbvGXD-wb$D34TurXlS6U;^V`{>A0|a@p193KF67|pHJ@K*
zE&AKF&8l#2K=~&x<ysTtxK{mQZ$7D~HcxKkdOu(N;pv^L+HUK``4ZY*e=Yj(%TaWX
z$`$K2kGq%mxqq#Sytdt^RX;K|5|n4|2uFv0`1EDp(^c0`*RPFr*l)AJZ4EbrfDU3w
zxWe1!L$Z2v7*1G4?E)=hb9*6Fn!ak*Yu8oP#yQh-Qcg?r#PaR`lD+Hwy~$s$-i$Y_
z+O2crw=IvMSORj6n6q}Ze$d0y`?QyCSz#64svqp9yYz+eHGUqq*T18$UxJP_+<Nfm
z_&evmmvQ>it55Y>2^Fswf5$3ZuG*Kh?)Zuas!Q!#{l3f-xW2hdJ2uyC{d9J2;TNx^
zlS9`}-&=Zke;z}FgEM@jgk|>GXO6cZ>$g8_vHrZ}v_SGtW4<8&>HpP3x4xC*iIqyf
zw_y3Zw~NmIdl5Qa@mOE{ExmQz|5`t&)_|7~K9FEKG_~@?#>s0%&vkw?K3oOv<{kVJ
zA-czAZ}#NVMxQ4n?>qPPP-FMb>!(kyn{2YZP;cqC<*PoZZq*KDHG20t((}#CSgnf6
zqV<cfpWeIr`DJC+J_as^1RX`>5s`)yVX?P75bgh0=Xn1;U4DPdy@Ho-jo#1RzkK(<
za#Pz)Zx$aDNM7xCZrQwldX=>wtXuRScEhT{b*rpjg?<o<-#W`E%Y3KL7SNK|b;7Ua
z-Pkql*mKpDAC`VN^uR_360V2OemzvtRLd7%@xrifPMK-1SbTiWnXf@t-p;T-wyKK#
z*URJQ;`I4*eV4RvQ&|<K|MRK)(O(P<2U|X{{_%q3%fEmB!UhN#jteAP27i}ac)I_c
z$D+U5=Q~UDr@fs6ZpC;X{vuuS{(1Jx*TwAZ60<iyX9TT{UB}(nj2yyOA>HcIr@6D@
z=dL|8{p3>4EsNA_su#z-%UE9T6}zNq`tNJccld0{mn%ARJx)LOZ?0Cw%(_IYQr?ol
zRDad8*|XBWRCT`ZS^j$4>qoo7CPo)Vu<HB2jrEJaFXH}xr`D`@(VH7)?f&qelcC2J
zvDLug2a`ahMashsuI&0UhYBQ=ukMxHW+#2KjOSZf<o_@EFW<R8>b>yvW~ux_<*n-u
z*EoDosNsx<RbmBO!Ic=7ba&_nqvb_AXJz~^x*oc*Zbfv>8-ZFK>%dn1LiL#HvKj3%
zpSNB={chS_t(uejC#{!TpBL*}C|jsi;rYsYk#FoZyL&HRyk00Te<JMR>GEyrmlsC|
zhpeA|<FWM-P@uOV0=>4j78d9oAvW#af0j5YmppuUtm1~@%6DfsnchAz?IB0puIINk
zUj5VCmH$fF?Zc#KD_-ZGUPI)h()VRTvZ1K9#yU{5D1VOUvM;AT{AKPu|3q!|gVxwr
z8OEp1I`<Vmc;vlae3|;K_3zd#`_@?IzFvIZ^P_p5$AwnCk(Rl3rt8^Ujgl`_H>Z2v
z*XdYZ9d;J9RCI#~YT!y%$W0f2cPd~ZxBr^+!f(94{y7+yr5wFRxk7XHC+%OKY?r?{
zxO9Hd!qdU4^W5{ryB&TA?fU`RLG+{gk>4tIfm2^5B+u5D4>;&uQYyB$D2FrlRgTc>
z_FaFc>s@j;ejNJY)91hI4zE(3UlGl!FMR#^uRE_@{kO=MYx>#VyS}ksXqS-8-ngpC
z8he%RCd+EC+;Puk@|@zmHhW4n{}xnBRqbVHcnYa@Oj`6GI6_jkt*tFQkWFN^*pzv%
z;fezdix~*N+6-FCEFqJY^D}w>;@$gp_UxNpyP7L5?tYp4v$Bl>kp11xKaz6cY2=7)
zoJCD!*k;F8edAM?GalZI*_db1ack?$f2FQnT=9Ko*LLeb##Ot$x@zBjO`4an|NJ%g
zkJlfCe%O?gAbE7_@rxhtJ$_Ub_VLg`(PJ_C_jP^34swH>eida<wc*24yE74|imgLt
zhI~)EsdqC&;i}-SE$6mg{J#H=?|;*uoXImbK2=?xG{dml5faR*1_eBf50`D*9LTC~
z^W3{6bsATEU8T`PzpW9~GuB+pdi!#jwej>751t-8bXD&(-_`4@9z5;4BJu0vIVRD4
zN2c-B+pb%<{NRj?`RaG|-|H=J$#Y*neW|Ut=0e*>D>kbmrE3@L*f?q4z3D3tfePuD
z>bp0C7Mkg3u6THQie|7I(pCY1eGDz?-xH5jOsZWu&;AA<|F<&BPoLJmjQ_v3NiL$u
zyiIbm1#$|`<I)#j{XzBZAvMuGXI7tDRJq*v=&pwff4>y&(mro^-}@o!^@f)pb+bc%
ze7YPP9rEE*=bNQhu2|L-Y<l`iOmyD||GGJsrzM~HdT4K4-?trp&+<)oT|Yg!div$h
z)!_@Sb2CJQT{t{_;vIo~Epjix=PQ(ymsd~t=KYXG+)v>SYxWF7P?t5Et$BLo{Xe#~
zpR#WrGe};a_fx4&a<c)lL;ZfS{R-_DH@#U0?s8S?P5ovTRXt<P?2k)BUusqS{4~qw
z)w}oRr=)M)elhKbrB=<)yoXVs2Jfcxsj=mEE`zqV{kU}Hw#)S2daLj0$?Q=nQxB?3
z-?w94=#1*UHR;htwtH=v_r@_C*yLniquH}*e+Q>XxS~$7XrW1>lT7H3sCWK<WWJeK
z#Dpwdxu@xo+KtmpfA1u_K$1E~Ji{UPfOg5%+a~Is6x?HSc9)~@`*#aD_5a?GNYerJ
zP14Rh0<Y76Y-w|_u)H3&xM_OJs;gO(YDM?W@mYAU@qmnV&(^b7pT88g^bY+ospi*=
z)0=iAw}*cCWS_mc_TAS*#d``qOu9N_soe9jXI52vzbp?*s#c1X-SKK;-&C7=gYz%G
zeyaLtCAz03`?1jPkI@zX{xK?SX|4GYBw^aPXwjk!r<m3qcd~yx`d7T8)Xi(F@xiUS
zdXF8B$-j-B0BP%5SzFseS2PRsHCdgxvG3qLz1wx4q<>BKzp|6ZxNJe%)E#mW+{i5|
z^~$ZcLw{`AlGV!<mlm3GVcRd~q`L(L=U?%N?)l?hCl&j$$oBce$~cRf&RcsUTS1F{
z-)xEzij`h_X!_>4=`Wq`vnKD@_@l4lG{1S-r>wabUq78`8~NpGsi9WQ&FFBwYwQdX
z*@|-e3iKywXzdB~2y||hbY|6j_w*lE%$Yg4$6RMtJri-e$imnC=WmP{bgM;+KEp!&
zh{HX1Gxj=e+jH<i>Z$O54)S~RdruoAm#$6yv@5|9xj7ZH<!kAsGn;r){f-ui?%Pvn
zHFMqfsPdv8vo}8b6ZtD6Z4X!cvuQFVk=qt-@&We~FNI%wzT!jb?W0=Lt=?G_JbaKE
zcvtCbMdkY<(LE|()dI8HU;l3o`;zq0OLULT)^f|gN&mG(_x$;EKngU8Bid5-<Iux4
zsY~6?62S)&_4^+!-1|nquX>)uUY{jJq7z&spEjrN@l$l2*>3XqQ=!ba9otOL?b~PZ
zW@cVHvv~S>nRhFN!&lrczhbz3@$E9sbf13hM{jPQEq!}ibZPbPADeU?l_1kweSLi3
z0g?}Oaa!ygrn0(s4@#%Kfi@`Kev$t2wEu70zt2aX8=i^a_zWHr*fOz2|NWlNd`%4s
zkH62pU!Ey>M@GikVTVQ4+cmGgPI~d?>!HJaPp{^Pzi!*LZlB$+lrp8-D%Vw9@vGKt
z-=VX=zH}L==u4Q+6=yenzT4fnwtHSH`~x0x^V^2q3SZSUeab%()06)`#$}(Mx%qpL
zq;Ju;s>;6;l6M&TY~p7Kdk&dDJ!Vt$P(f)`!H0dy4+_q#6bgO8p=R-;phe$#%Ik!~
zG8Hr4iM#b0HwlZ@@Cbg}(V{xb{q{LIt7FOAb1%K!y*)kdzQ{j|CI$KJsvhvZwt`9M
zbThWp8HUmGK|^HKyVq2`+WzHW{Z1GDW7chw)k#m+ZdtAs9V5O*H{v1pWkwzr_NM6v
z4jQtwbYGd(t5p+OVdoC2d8S=ldQNR)-U@H4pJufgm2*G?J@-u$v~qW?X%e586W^}C
zUi^LG%Ex}+s~#pEkNO?0xz^sdRi87KbJcb4`Db?(FZ)oV=l{NxMc@9`bWi&`)0Q2Y
z{--FLe=-|`z@dlT$~zv2Clqj+&H4HzIsfUir;lobPaI5r_B_;wvEz5~iqgd^0(dRc
zxWdnzPhMvqva{~&x3?W#+FPy{>wQj*Zi1FZxw*OEiuYmdo)A6(t!--4*+3rO+?ylW
z_h4yyWAD6whvZ)#{+_e!ed@k=R(*ZZKh|xM+=-C+X0uBlS|5t-nf>Kob3Lfl?igI&
z%@w!k{PPQUR{4OctMwwU+nUcmQMnvjsi{?Cc`M@mrZ+odXNUgy^w~B#v|pSrzR%Qj
z$CbkAw`XL3sk&*qA(~bHWbH|j+hW;2<Ma*Hw@L4f6O+BatZBOX_NB>_Y8f`zK=%r5
zd&u3+w@SQAueeS5nd07(y$_jqGk1E&%I?~sd)+pmEjh~l>zRiaTB={YD4PEK<H9=?
zF)N-;lsta>!0DQs(-a`%f=q|R8!j9?+;h|>PGC;rF~8d<#9nj6X)XWT_3qb!{FkTg
zYp+VA>1_NF$zv?`s7ZDoLkqKvvx9}ko|rX<rtdgaBf96z${4@pQCdfLEzJABw7bOp
zSCZ(Sn)2IG(^Glh=B_%<-L5a0-nSaGSJLo$;q?ob6TVcf+<tZSgI3*_5lKJe^sioD
ze}Vsd+KLBHpSOSN_58+vwf*^fIfg^tC))Iz5BKbxF1qTTqMmc*$_IxoW*=D`!)0^l
z5TEQE9=rLQbuZ7+)_d6T?B24<69%DrIg`1j$Q<r|c~3j*oJ8n-8}?ql)|xvopE_k{
zuZE44IZga+klb$m;<2Xv>wS*fnhqu&3zGjUyZ8Oy@Rzr{_xB_oJH&HHykX*DLzWgR
z+lzMX`kS?lLVg&%&e;`VF0*G-zI5+-@1TdL-|do`o?3XEZ}o%JN55jK65of3?$fdT
zxy@U)eAo5U>!!tSsY=*tcqc;ROV!Vp`(mCKBuAQj&R|r#$=)zQXwk!NKDj%mN<S=U
z;yW%7BfQAM!jN4d@2OkFLyp{@dn(TuoYy}8RwHUt-(5UIKVSGAe|X;h_eMAOZ|>tt
zy0Xo0!3qu8eFZ!J?yKW<d+_M+HkM9Ey`-dAQ~1aJf!8YbgwSV+$Cllh?A~kI_;cPZ
zSmVs~_KSt*|8&}Y_!+BrxaVq&wc<MNg~xC23jl8hcUV$jUk6%0ykXa))yqRZOnNXU
zqdqQF>}{rv+BM!%n~VSEo(8qf?v>uWa`*oGO@G#%aS`3OM#tJ0wBF^=^kwH3KHq<3
zZ``u@8Gi1+`Xvtc9^HC0a^LmS-*y>)d2;WmR?W<B-`qe2m?CroYH#Lw@eOK_7LSY5
zk4JpsehMKw_>HIi`}FPruW?yL!HkW`lk0uU|J=WSar(U4!xCva-wsT^a`Cp(I_`xh
z5e`3O$kMWDUt?WVf!%e>``+7%7yT`HCChRCr=3~&{N??r>Z?AezRg*AdK2%|RIQ4g
z*6OWAr~FnuczRM+I`-9$%X{Ov)`<rEa@fTc|LE3Zr$yIKPu_i5c5mDpxf>$u4^7{4
z`S;OX3=Cl+Up}NRIrF;tD`akA;nJl?VXGn*DnuOaxe1!ZHnuo(;}Cnj|NFnov%e&q
z`FqSD`S$t8NmUk3KbQ=PEZQ0Z$~J8cV%7f`x>|FWm3OQDPx11u(!&QQ-@bl%dH9k`
z^-q>xoVw0$daBH>f**$z&zPI73wU_?*s7xGR%zFNt&`jvSH|}y<e+y_`68~kHTPbt
zuHJLw#n(@d4o=mos4PntgCwylhyx7-_Ax~GZ)lU;9a!C*<JtFMY2`WIUH>1~FZ=%g
zw_}wF_u(E_rWSn$<@RI_j#Tf}pvkChNlTO0P5=C|WN-P4#BENI+5YB#y~Fmr`uge7
zzPEa(OV&@fzWnILnXB4b6+cVjxJvnj-EwyBa&6ULy>4yLLGI`~+irZ?8)uPFnUlVX
zE3U3aVfMRN*|lxbt9RbhxyH`0K?~7q+fns(6)bbE(OPl1=dJXcx0_z4?_YR3uX4KG
z|1UYoGd8kT?{7~$c1Q-6GgrOps{(IpFV->+{jurCAFhpg7ALIAC%rr69r)1urs?$s
zj^C>ua(+*jxw-t1_`l3`$=9ykUskoJ;K!zA^}Sqi(@OLL4|+RQGnMj0m>-x`p3{Bt
z_0#IxlS0=||MO{aQY-_5HqxL@i$24(fU*}tu6JijeMmf(S8`t=dY*E{%h-&?)BnBZ
z|1vLncTAh)?HWDDIPpVVa7UJYb=9glxo3wftA4T8^vmE1(c;H>)0tWoKj-)E)O)(?
z`f1sfhO5fYJpXh#<i{b~gfm~yeB1UW^q}{(tvc@Or~eD-+9|jA(DX&`lnQ0Fcig(`
zc>ngqH!r?!vY-7f(zL&B+wre|85lT35#weT?%a6;8-H4@*`dvO&LDZMd4|H(i?^RX
zIs<CR{F(XhXMF#~@0;xwHBEnR*0cPRw#B<15sr8UH@@Qn0$ml0{?1I5)T)^oKk>0d
z(VLjI2y>Y^doFp*KM}d=(DdG`&ts>}tlpcmJ7Q^*_`J_YV~+PcFX~*^yDZc6vc_`I
ziYb|0El%sFXSSP5?2X&Evy!t-nz>;D(wG8AJi{?Qeq*t}AAT+1F)mwNSWq{;S>Liv
z^7k8IZo@MXEY7feQnF`h46DATsWf<g^L<W5^crctx-G?vx#B7o{oQs?Zt<QK+m5~X
zdg<zQcgTFi7vrURuV>5dsSEg8<(cIhbkIA<U+CzTsQuTyzkb#JazpQUjQ-?rsVn33
zHy$qstsHP-L@ci{F*U7(cDW94HJP26F?a1PH|CFha{9C9|JD6_*uLx%Gq;-5LGCum
zZXbAf8R?0v_@KJ4YAaX#viZ?!*SOiITi@#K{1$J!<8tcF!_uEae|##sXtpYM|GFl~
zMqt04-@D#@{dDc<c8y#G-&TF@Xz@0e_0!{JqI~_|pEjx53+k7c{@fQL&Y&QI+U0)0
zeaj%ZY};kCO-HS&_AIgcWxea|;?OU9d-_*_M~w~|Bn$F9<UVlXp#+nYuXp}zuDG<R
z=@<UI$`ReCqZ_Y%jh7uVD{Fr%B7chcmhBgHKNgGZTd#hrKJk6n^si^jw!aD68#ih9
zq|gtOZp`)QjuHcv-xrT%{W>_WM0Af$<dzuUP5U1ok_3&QuW*2md$t@q=Ee`*!q&5N
z%JVXo^cjZM(VfaAiI?|(t9Jh|>1Unj9-sR^O?iyP9t!MZ=#lADRJihHXV9WU)0c3+
z4*6iT*=DX@Y0Go@=#K$O^G}tX`C9b<m(283V?NQ9ar$?DFWtTW{EUqM5y8#gwrkh!
z?*&ydo2AlLK1jWJz^E&)YuS!%7T({h9^TXSe}8g;=eaoj|C{aDCcj~Jh(I`#rA43N
z;K9$keRkW&x9Z(_o%;V&aguHQ#<y}jap1<@-~V+J<j(F4W!2xvK5xtXIpv+N9iKSB
z5~OhO#-@8V)4jM?$qK*qx1S4&?b)l(U*4Yrs$X7~bOxSV$aDVlp@*jz{)&o<Exh*H
z<@e9<Poe9lf4NtZJn3)1i?5SP_sy86b1LBA>16BRm#beVtyrjBV7rEqp@|iAl*I3&
zfeZ}}jz5?LDs0x=;4V*H@!+X@xys6)aZ9THCA{7|aqR)YJzsP-Tr{`)`v1k@?>S~o
z(~mq#Kl)YSlR^!ngr$tLgT~5tE5i52iG@5{^&r*nY0<4%`F+A~|381l1DT4B0gbnX
zPn#Qb@bq0v%~v0tXNRx+pt}6clEC9@7A|*W)i0I?_nP$=@9Ztk^t>%3x-Vku_0wD5
z@m1AaH+rt}duGY|SwFWdjjHQqU|6Vzc?^S;l#~suckCmx<wMIanLT@o%EKI<m+!n-
z+nkdvbI|*Djdbl#<@#m*f8I-p?%HtYQs$>Bfr+qmG3CkCyz-Y{Kb;otymIrp?ls<0
zYyGf`?3a^6KYW_J$O%$4PO4ls<y_3(B%dwk5BJY}oyqx?Q>!NO;_gE)z6zD=`u)9g
zWvge*ajvv!Im?!R*9(7mx;c9KC4K%ig0C4FS{5y6*H_-DSi@-xU+g4cvD?^RTKM((
zSv%Z#ZPl)RW{=+8%eG18aF6MkZ5NmS`zgEY_r(h3ipbKUZ9C*#^jh>8?tT1zWNrxm
zfvl4C(_e78e>k)^ZrVKV``sV?b7H;cpC~-@J*Z;gC5`(5k;|II<;r^6<*WBr^mD~m
z9IE~N>(;$#d$lSeWA@Lu#4h99cQ$*LyZ7zst3E7Ux8E-&f6dhGF=oD-<~!cMC*prL
z_H9xuLqot5Xt&R-CR1-&&(F`9`xKkGPR)M0OKSb(K%cXhjPk#H7M(Kt)a;cOhe3ya
z9EOfpyn6i_K3>6N8T@_ah1QQMzk_7=XYO@mUfK6xY0fR)U7u^~7oNAbz0DE#EhP2y
zYeyGIN7tl9Utnbc597k5%&Sq;<Mb!{s{}kP&1PdafBj^tTUGoiCirC5>LR}Acc*+p
z9(v!|czD(F)5|{xforupw=-Ym22Flb{A1JF^4VN*Lc4@CR;qfk>hF}jHfP5?^_%}+
z{<BG*^mqN5c$ezeXRhC2U~r1hXxD#sVe!rwRz=C9hgl4&=3Z<u$ePQu{dozS5l1`^
zJlY<_+s4+4e6DB|+>_(0C;IxQ?_*x6j&c@#^<{s$@^(Ls-SuDGJ{Y`;$@K%1GrSR2
zux{<K#n(^2R`pD}TkzrWT<KoL<8j*ea;FqONP72m)7zx08-(93kM#>ac>0!hwr0A5
zd#nEE_2-s<`BoyjFXS8Vw-p;FeJN&tq4~TZ`Tos`PMiBhI=(V6I7R+w(RZFY^X1cf
zuRQ0x77<SgDBQex&7KA$t~YMC!Xzex*Q7ed#?EEW+@GTQ^y$-(pdhEWZx_Gz>gQG$
z(6}yj>*^jonOz@j^p9@~>;C%ZXjrat^qS<p>AC0sw*5Q*|EF#3<FoRT2Ty<WzJ2s7
zg9Nm-5fOXe0W>XrGbAcx^79$5pQb3i-BS{>M*bCO!O`2(VwF6W#}c{Q^<?&axR4z4
z?a;loS~ZbXpK_<0-I(~=J>-K>thBHCFaH$}QZvs7oyt2KcF;S_ecExWu!3h%wzm6j
z8=m|Z{M+cIHEZXUYtr*R|6*`R;a>mYshg$f8T|!C&p$EA3Hf~W(JWw5%bLp+ytBBL
zFYwBR)Sh<z%Wr4Cc>Z|TxqGHhWsXdlz3uQ@%dcfSa+bd=EG|6up!k)XbzZK7qkqQ)
zNXV>c)$h0Y#L+8nANTjk|C;Wut|L{454H+(w7k0H*0swxlvV%FWO?tee~y;j65O?6
zk9xg({a5q)1^MSHBUttQ*Y28oYW)s5d!`-I-5*j<P0>_V+sbSZR=~p;7GCpp3Rm2;
zkhMXO(fxu9!L8p~p8t^#=6hq(CiDBxE1u*NhIe1Ls^5No{CaiO6_wvI`%Fs9c5JzL
z?fx<+alUx3TizL84`hk%)5(fV`BG(huli1^toDv;cO55pO_M&{Tbmp#Bb&WT0ki_3
zG*N8d2N|6^#;*TMR=@stIW>0sF6XJzr%$gu@N{GHKXKdlJzQZGbrRpdoq0XGX%SCp
zeaG8tuiF?PouMn&u2oI=<{h>%^Rn7wJJ4j&jOo9b#QhH3=gVIaU>j>uF|){j-YZ7s
ztDnVhKRoy#^&a#7<@+nw_sV(x*d(~;3aDqto?sRRSr&O93*`S}d*j6BoDY5I{ixMc
ztLEYFm8sH4em#6(Z0Y%B;nPB`nwe!4ANn`F^XV>r`E^ooSrDtf|GOI*S1y{x-CfZ%
z{m!u%t*uj6Jy2a~-Z?EcKJ81@%gtXa*O*_sXSHkMyb?b1Z!gY;cv<g_U&GEIz;{Te
zrjSia>rBBnIl~!MD|f8Vu8*0puzKMQ&&KwReC#UUo(DMh%{(N!@NDAg-jly}UiaR)
zcV6+#(`LrO62eSNz^B3qWIgPb<hO16z<MmoF{aaSKlk~shl=-keEoBDTe3k#W!WM5
zm$uu>E-pUV_W*Qq$;y?o^&dI*ZOr<y=)eA>+Z!HwyUO@(*xyzd*t+Z!SJ&(W{n-NU
zqR)2yTQdK;LE|DW?I*hyHpKfY++T0doXoMo=<aLPg;`s<;%;!ei(NeW?R3&#$TFq&
zB^kfIPdyD<mvygDZZmidjLpq`>wdogo$wVITX;<)nb($A3$)B9yE<F5Rw1@kf8*MX
zS`~?3|6ZFdT<l*fX}iz*tVG_MnR90**)lW~JcJbFYsB}>VCKKOI%UPvW4mN-6c`v=
zyqWI4JMZb8)vfCH!w*mH%>5ORXQLPX`NSD>C*{Q5&K;1BzFy3Z2adP&BLkC?l+Mnz
z4v#UDiLqd;_pHoo7u@s2=dSv-kK&*Kyw|b^z27@Y+kTm8f3f=iODWrFA3a6)><Rhs
zo+B>71+wNsH{jvv^>tshcm4nO`(?8HZwXN`u`2#}HJ<`rz7hkW7axv3wd4+cV63R;
z!Yt9EuiTW(u|cbN{q!l_uR}i=Ew-5}bbg)S>-OgGZ@I>8GP~1rml-~}0dDsma_;~5
z_|~`ATA*_ecm0n2@1eHP&aqYhcJ#K(Ww*;j_o$Sp2Gm6F;fmw?B2%z3pVj8h4gNCS
z#h$-6hy3{TV46$Tiwx1%ObjjEA2{PJjszU8`<VP(AYoZ$NKBK!^itWqsU`0xJ$uBY
zZyP`7d#Oj#6n?`uGpc{zNp`zh-e$KdhTq)mNZIGV0+7M(mH^)PqN1XZ_0t!s-F^Ol
zM!WJ_<_D|$rsaWF26Dy9?N*)kZM}{9wa>HPNvE7HXVIU_o%`i!|DTz8zcWN_=e^{J
zi@W>D_0@~qiPu>rnzn<_dS=zCsr-0UJoLk(>K{Vxg@M8v>kdVRNw^9xdebDik0(@2
zOJH9QN1x(>RVC}i=jGV%x^?mO)2EYeY<T<iknpQ|3)^eSCpNZyGux$F*LUdq&K~s}
z&p))P-!b1__~Dmh@}$3~8oeM}At$}|4*4+Y_QdRy50A~eKR=zv{M(InA-AHBUqAC?
z7X!n?L(n~^wLc!2*-YoNlR16bR(+lMAs+XD#Z|L!ye@jM==LKy-MGWk7oK|i-B#`M
z_Trfl`yJZ#^Ui0p=p8DvzRk%BsZu$%erP>BEqz|4+t;dz(?Dg;gEW(CTP|^f+M=z`
zD>bc4Jl8xo2X!h>8=i@H93y@AZ)17Lhfja!+z$N!TDC8+=fH}d(jSv1O<Gj7>&5L;
zQ?+{^q~6++x$4aH4@;Yv#nabMcH&Fo@=a{o>nFnRoL0Z~v6{=yHj|iTA2uZJvaOb_
zmezav>!*p~8jU3@3Iju4#N^fa#hN+ln_s{By}j~XmqqZ*cd^E2zPVf8o*@waBBy`)
z?dz9MFU|e@t*({RuB>2B`r|9|1yiQ?|7lVMZFT5TEnYuev=rpL<wcbd->V{v|E-ZK
zdh;$cQ_s2YtVPI@s=YS*Qr8B4*M40b@*^lDJbB7Y^_%t=>zaykclJFzHpjVFb&Gny
zWrxXu_qCSE-b>&y|Mq2{PgF(D><E4ag*%Wb-7On=;}1-NlwASA!H+>1lr>)G;m)`v
zsrwGK3QOqnMS<o(TA3yIl9p8cOUmAM(P5Qx#maf5&F^aN$G=dHxA5qHdT&+JbjxbF
z1@r#t6~;2;Lo(^xw8P?j;T9>CFBZ0kFdQ!4^Ws74=`UxOe>lp)&!u_lsp0wmNv}kc
z`6S!2+}TBA&M_V@_!@iaYq90CITv$RJa9SdnS4vEdiS4u{A_B6ZMSEyFqU{QW5?Eo
z^A7*rx3R_`z;K(d{`aqcxe_O<`!D-db^H#m%#jcB#XGKlWIHa9;Of49Zs<X7c7xBW
zi$i{V%F3*{@vSNm)X07B-yL}Hboq8(Hr?_i$5wn;y5d@)U-62CeysZQPx}{37U}%=
z+Q$|5<2mDPM$tVwZzH`nUYC7%df_~0x7gJhYb;&w-<_DlWB%>TJe}W#3%*u8V_;x$
z0!@V)t>?e?ysmJHeZs8Yn<g4<f7<_zLE!__&%L}$KeReeuYSKb{I7|0){Bh`KU8Z>
zW#&mQf0k)_M1o!4=7|J9sP!&>TUDC(h;`HS&H4Yp?Z970YOQ-8sBZibS!l};UwK>H
z>$%FUvu*k|OJ-Q+{Ctyczxv&0|Kfa!`G>?srCe^_?peh7%)qggcisQ`B7Ff_4;SAH
zwq9>aYKw0?_*ZeGsF+`DXYU~m_Xmrro*%h+x-z<Q_D=KD6JA>$np5~Kx3^1sOa9{r
zpAxsn@5<MkeCp@&yz>cM$%lC4?LWwuc${uK{JuMc&tO_mw%Mu=s=K!Zu<CzawMpY+
z%#UwXnQvb%yFFcZ(Z@<pt(wTEb6o13Q)eD``3)Z85SOd+nLhWe&FZG<H^tL~eL;g+
zHx6BE)vEA(@3H+(LGpC#^_R=HsY)L1EqpJ2<}(w+#3RtM0kj`IeV@T8dA-lc^#<D|
zJH9a<_|Nop@7h(XJ}JDNzNVp_{UNvNJ2$Hwj{WsPmIneVjB5E}CHyz(U;U^a-P)V8
zvClG&Z_lUfs`K&ty~}Hpw{pcsyqYL}^ecngA;?f{o>|Sq+3)@)9kWe1V}AdTxVt{z
zyqf!)%{wmyyRfPXYxsRPGQ3{C?fJcZr+)?Q)X!b9pw(Hp?#+v(!s++(rsqz(7To_;
zAn8w4&5gR3``l;eJ^d40v;WZLWxlg7d}cl5<Xpa)EAH8})JuQ7j>qV)pX;$?)3pmT
zju)C-@|QQS+IwfeV)+TsQd{eu&Tl{MrkC=TNFMH;x@G!>tlL@1J2vjvX7P3NruCO9
z^j25=JfJoG;jwM87P{A%892frr(p3u<gPG?m-zbJHLUmcl_gIZ5^SHnUv0mC_d=0<
zKe$1G(y)8pI=imQJqI}AxE|%@ov@C-;b-k6zU|lX{afqfI^R`l-Q$Q0n>$B$9lJpg
zWS;0*|A*H6XAEuChj-ks;RPMqA|vtioZ^8mHkxib7f(O+;6rqv`Ss7O&4Q*^B<#!V
zmLK1cyr*aW^yf*1I*;WH9_`WA>!0h`eJjjXX9oXm{kzZS>%F$zes+cRmETWS$ou{1
zH#^oZpU>m?tZ%>c;fdE6CA3?ocZdF%6tg#uRsZ7E$S==6F8qG@;REa0;gWmfica~j
z+%)Z7UVrB|@Yv||_&4%@4$s^ger?qQ)we}bQ*U^Me3*1+wvXZUq>vArR%TBQyspKy
z%KyUQ+tXHEXgxeVQg!yxDh7s!XCSFcv4)ZV8TaBp4=>r9e*SdPVEaRGM*Xvs-(dBc
zHDAE{4feMOSvK6<+E!Ne^qr3At`7yt?dBHx9&8idK1lem>BGePx9wNE#Q(g>q+D^c
zM$a*ZKfwf2>Ya^$$bDZ-FJ^^@`Fv2#Wx3)(;_e^+{V%h8l8JbsxNXPrGaZM;pYP&Z
zwliwZY4-V-_w3_Ko6}$UPF(g3&*wXOw_owN?aA9;690Eg{_5Y`c3zo!e|^EE$&(jX
zo$V_*5!8EMdZkQ)&J6Wi^P?xOJT%?>oJw$F?#2jv(12&gm#UxdzgoJjpMJ)8>8k6e
zk9^(v%JBUAL!b8){3v?%N;EX=ZtU8o=`Y2Xt-h!G?r`r%mEYHMulGLR)O6~aT2+C!
z(%%IdcMZ><{+F{+j=^CE^bi`3_yf&94{6lzSn}IkSs<6mA%XSP?ye842ZBp8m0j=7
zT-7B0?_~eds(&fkGCXl{Kj+)uuwU+Sf6g~!<%*lZt&?*<=tI{0d`|yibSL-52UfRZ
zGMijh>@0r1s`J~@)P08xSt3rJyyhjk=Sax90MJ^X%7-76vffI@?vUV$ySe@B;_KXx
zFSH&O=S%!i=y%gobdSxJ%BfXnig)O&0`1pbG)*bWaDx-8{zuu5S`~?@dsE%2!#=)h
zy!z$EIT6u48;W&#!7W(m3^ZtG>-X-(e;AYBJzc!;_NVmO3<432f6mRbufGSL8u?@A
z^^~EfdI#tXY0*6e=f&TF`z)ZwDQFz%zLIq9`_1)>_Wzn~f5Ex9Zi&-$^{{(h?pbTi
zS=d_aAb#CF^F!*AC1nXBN;WkQ7an`J_j_F7O^*i>OlwpgJ2i5}FVdDdcsly|w7^F<
zKg`YeU*v9GTLGGJerNo(>S5wBi;9Pe$18pqzWZvF__auMU(W6q5<!*Kr$G(SE9w^*
zK{Zp#msE+*dt_c%KKK+9w&Cw1$*O|vMTU1GF74b4@~+1NNP`{ZT}THFG}?Bgc-M=c
zv7l4GpVdr!_0#t|@201gP1D!8GMD^6{vWitYnsiHrs>PW?)n|q&z*jqb%$rN;68?)
zsh~ZXGs}-Z4f?zAgH@2F!-lP2wyk`ST6VK2dENBqe7XxBpM9G2w?N?UH^z<Ey+3ts
z5B>4!$F!}l3V+QE0#AF$95MM~vf@Fj?0egFO`zSdi;tiFweG81blAmvw|)CX^p?t7
zwM$p8t6(kt_O+vwkwK{uazB9c4<@-Y>MY8tu`yS7%>J9PblI{#FE_ViD^%3g`=d%q
zOdLEW?puFr?e=?CT0zl5|GWdrL8H1K_62FRpP2UP4xe(x$(g(S*E|kiYkmT>_HUi;
zmWy}gE871(+iUjg;ims)P19#(t3N4a{Br<OB?#<uSOQ9vt@ASK9ZR<rubVFN>*z$)
zE7k%J>}4dCV{2=_temq{t72z)?6!-qTkr0-3T_2$`Q3Tu>!H1I6+1S9Hknu4ys>Sf
zQI4cmP2{DGF0A^Rxtgo`e(qe8aO)xOmm~8`j@#(7Z+e;ZI~0`HmVAI5_{0%^fVqBw
zoA<6At2`HWtzVLGR6J<IBW}%*k5fwur4O81@_w}%tM<MLw<hgi*Y~-l-zt1B-0P{p
zOwdIKxBmXrDtXxXwH&lX=4a?*UMY{#Gw&}Myl1=nbKm|8)p>6hsZX!>WYs^PE7AXe
zmFXO+XC0&C)z9jjULg7OrB=nv@TsnW?29vV&&qAAvpAt`t@^FXkl*qcWAD4Kpku19
zeCiE1w`!Bl_FM3!u|#x_O1Zl9-ndu4UiWNUla%|^u&U(v0~`J0>th67GcvdcLDm3(
zPC59%p0#C3_v%?9tA2QKh1Dp6JmVx9?<u~WagEQfeOVyiuGHl`B;JwIYr6K!p;$?+
ziieePe&ScVzW!NNYEZFq##ilKZ>N>N-21<zcix%5O9c0rY<;XE&2%2sJB_ct7U@lW
zb{({r_SH(2s<i)iHq6bqf9Lhnd0S`d?TuU1|0ew5=_R?+HdTA4l!LY&IDTdl-L+xP
zJejV{-oS&Wuf1EVcRrbQ>RR6~C$24Z`><)*uNe{i3<{T^EoZ@Y{np#x9A+5&k30K=
z*);0X(NJHm&>B^ckD5f|JwVadeVZXntv23NGvZ_Gm1$4z>@0>Z-dGyKH*x8k3#!RU
zwR{P$&Y5<9{c}oWkIY<XLuGsa`bD1FvIkGEj^5{S-Ae!TYo;GFA>B8{nnsV-WDbe!
zUDr?hq_3ZDIra3*o6MgNRW15^^f+JV-nbigEj6`jZbro>eyOU=_^g%nI)CwtTNzO`
zwHpQZ<h;G>#l221;GlO=wNdHqU9q>MvTgs?etGdNWY!a&xMx%HKts(gN{|RMTlL}S
zABkhfZs+f>4HnqP;}7y*>>aiZMlXM_@;un3zhs*VN4$d2$0NUNklV4^iWNUUq-(wG
z+;hA4!P3RsUE{<R7$DUn??Y}EEg9zn5ysD|R-O!fcv|0IZB?AU`0c}=`^`baesgDf
zhkP(f%$?@FemZ30TiMj@Yv0TbRXe#ri%$Z=g2j%OA6<Q+^?9Y`)%q?mQ+=a0Y3<j#
z#g%f}6_x+Js;c6bamCeDDTJ?KW)N5j*=i+qXu7~r##09l99W@H^U)8BKhn?5`3SE0
z-)k=|;;6qabqjP}fL2N3$H`nPmur8Ryyn)e+-B!q-~9`lruX|>INU$KJCs$w_?wM`
z?cdl(yBHdw=Sp+LD+E21U^;f`L5zN;R>jQdsVkpVaXydJpZxv(g595jLVj%8z3v!v
zn(dHtziifu;De{buRRY4tQORL8@Z}!`kls@tbG@KB6TCT?8w~+8EVKp&J`9u<pO{A
zrO*$DY~yF`x^<+Afnnk)NR#Gn&5uhrJbHVV9alEIvdjE(T8o5U&BF_hTGs{kRj9XW
z?VF^R#~d;9OYPMqpduw&s6}5uvoCk8S(_yHts}qI{AS}#x%vNpIh(%C@ovkCPTLPE
zP2v*5kk+8ttq-ZU-|dBuN+xHXpDJ|qb!y>R&<?pmHP^3ImiJ`l6`Rf5bIC*gY)|;X
z(~ECk+o_|SyDP_M%lXOg{wMwC6Wym{x4m~#)g+mN-tl^0@4Q&Js81<*-t?_=vtw^<
zY&t#ndN1d<ozJ}slc$1m*`!LyPM6tS@qW*bUU+iSP{g>>=;#yPV!?%l;!l0gExx>L
z)52?Ccb?@i5BZVAC%5D&&%^HWpcRj_Up_KxnCf{y+!s{7Y*n?a{j~)=Szi&aIn}x1
z(LCKdhh4vy%`EbdD|l9(xa`5wv~B4hCPn;Nvq#QBpaR;*Y5`BRFs?pm$kHQv_w`d<
z(~EUxuHoqyK?7}P9=}ZbTkzqo?&_D9wSz)`1eKlrdgJZWMY&ovm3i0GOfUXh{yFG{
z{k3DkUin>IahIwuhkgjU9_`*8C40Dc>DJ|udGo}-9r>1Oa?<k7jWx-$k6JM>OgsmP
z$zHpf%sZ}zhPzh;PBp)@Zid9Irx&AJPpe;ka`KGGKAx@X{BxNm9}<_f*m1wS{ligf
zELDR-O=H2a$*(@A{IaSzIlnMqjkti*hffQ4_bXN~p8fz{3TWcpqJQ@2x8!3<S>M9a
zp5^YG{i*4vN&UVB57hphs%g2+kbJsk-crx63{1Y~zfP)1?B$AIHeGjdI)6+4`stst
z&wknX#zl0GkNu|esglq$?+f^6^oBkyxqhm`>qQZZ{>i&1uOz+>aP<DOwL)~yk?lt>
zUu|5i_cr3-p6%Q(SMEI>`r*^#zc;fk)vlCda99FuYA)uF_j`UZWxAa9WX<<q5^Tel
z$5!v-+4`>IR!@#|tNvlzYuJ<Xq{-_3pt0wNE#IdGS+-2yda=Ft0(8uxG2!)Q$%Ch-
zW=QY-|Ng(z_Wt!oYS-OAFoivUp3%S`-^OL(CR;Nl_~0KVE%E8S0*9tgXiMgh;P$<%
zyXyMs+0TPACl)XI8}9Y!g}D2)fcFj+ack-;@0(qH{nY-`<riMz%b|;TjSBZB2mPA=
z?ygJmtjUns=+L`;iMzvM<CivxhuzCwJK@!vt5v^l-m|I7xo&jaM*sJ;%Qv&Xg4S+G
zEdH>xFr4S;n=F%4cjb2%@y54Bm8ayonQ7L16g?#Veo4U5@JmN?8MG(wTR%%=%@3z6
z@1_r|hkTQLS4&0tfW{Ca+`T`v&3N$O=(c0anx?Pj&MA5R`=8VQ@6OkI9<WY+-5d%@
z+fhrt)JV2`V3jH0VN6zBU$*<z*G+rMHiK9AuIT($9%rR>jlXoQ?OnI^)1QCO`x;SK
z?$^Z?Zx%A`;?r|xvo6k9)g(SIr9SnuSLpIBtFO#>o|-*9T{^k7dfORo%_Zw|drVpN
zXHKa)x3!#~fdO{%Cr7-K$RY9j9-uk{(<4QXj&Oo!;=ygU3&$tFc<uT1r#h$?&n==^
zQTg6JcCmV~edhyK9q7U@r&Z6p|JFISCGRk>J2ppu%K7se3m&BfTg%-!B!0DyQB>r{
z?Z=-!F`Y7KQ$4+)sVMgRzIk<gq4#Wd^zMrNZML!~eYf20C;wuqq!WA|NH8_6UAQ}v
zRsYUs)qjt)MECvq<f3F-UcgxTEi(U~P`!smVabV|aiC$ar{}g<m@fCOx}9}AAi4IW
z$?Hoxpz8J7zp$t)k7hhijr?u&YIEfB@(YU|-wyro>Bl=0<%-I;C!#j$F);AJ&l7PH
zVb?Hcet7x=mN*sSeAvBZ{ghXZFCNX)HS8Cc*c+SX8{>Rk>ekU+jY)iQ>-5W5^y@#z
z%@*9VXV=s2D;Ghl$_&IHmFrofhuwk|Gj_D-pADH~c%Q?3)@klJ54oqO^RVgZHFmgg
z#6P=z{rbYrnZJw0UnjKQeo#8+bf`h{t+s{RGIu<0T5#Bqh2{1#(CHnKk^6(e!#gi_
zI&){FUgg_ZXR$-Od?i<$-0gkml5hT9Z5;Yz(~~2PQN|nGSoJ;kYMMO0#B-~tw0qU$
zyR+6dO$V)PIQw~0&8id16*J>D34Y$g!0_V$w1~5+IXFi@*_$iGjR|)sSc6-(v;4%b
zUh0465w!4h^7bQ_u009b7`Nc&HET$h<vaVmz=NlkywL}hY><7=TPD2x`6%vq%sat5
zGnxg;oqjyJrpK;X#PDQI@~YE`UTohB4E^ueUom^R;n*Fv9!VKz2ZOJ?UoPL-<RiLo
z$(6O?4^K;{PrB;z`|RDdkE|BvIj36QojQHdq3L%^P4!M!6xH&@+0ESE@rgZb<%871
zYNN9&W;=e?JGXdUqn_2x(n}I~IqBd1rhh$t>C|U;S?AujKBX-BWybgP1YR>SG^j(H
zRlhzgEfha`<jo?t(0d-claeyRA1By`=RG_f@Vals*%QYYyG)kM(8p2KJwDzK-emzP
z>ZZ+GXLoAmuJbS69{RlJ%3SH$`+2sDS~X37C*l6#Q+WMn_A0Bl(pnXjd50^&o66V?
zra{t*uFaw86L}t*#{5`Rz2$|Rp0myNW%GAOem_;HJ^hvU+XFKW7_zWj-nmUmbdSol
zHKtX2_f(j)2|=b!^w)ZYA3Xh6y2xgl?Y>mq+M=&EUhh1E51yX$Or&aSlG@(D&up@L
z>L%P`zoq$YRdUncrEk4g-cIXHytysjW^vQ>_j&$d2f1h0AF}#<fq|ig2_7s*-<8Di
z_|B7YHk_;;+;cJcsBVVjBA>YO7exiCt@<)%Jl`X@kA6^9RZR_<rXO#og{8W8{K2%a
z!x?nDl~I#8U!q0N#8+&y_~NR>_%BR<SHhw{d75p@16CbqZ^22ercl0YO~m=1kL*-t
zY;QOm{lmy2-KThCWya>jHrdN>CEoIF^o`rj+r)WXK)`3U{@bvJr#Ic|bz47uMf2yE
zNxxG!)~&FvTsjfFeNc7#ov%?L&ehU?zkhSxvc)`<Rlo3T<(I>Yi$gw43Yfp3X_~06
zc1GdtB+-3yUTk&C|7Tm8`sHiy#xHZ<o?Nw^n_ZtXy=|Wu3j>1@Bt*)BA9jP9b&KE5
zHG;P4v}!IkcK_UG-J*XF+(AU^Vcgi5jObx3T&S?lg;)RBLhmbcSAuug>r0e;xU~Jm
zvL^B0?%j0)=AMw*=d5+D{|)K{c0p4{$szIdnZ*+8dzjuOt_4lXY&YNjG-=0&qk=mm
z&QDC7cjf*Una@i(zn^WK5M4W+^E><N!#55Yvaonxcj%v$QvYkGiRd1e@brt?$CD24
zx_)}Q`PzVkr=N$X2b9<EKfUSa)$Gt0hs?^~F8jUxT=>D$vrY2@o%5O3=0tjOuhR`U
z=w0T%?ewnXry0q-wYv=(_wBaroOf^f%BJZrj~iT(U^tKg?SIYYk5BONc*uQu;pu>b
z+|EB9%?0%-ckSR!NWJtubTX(5mg_07&tb&_&M%cpwRdl;)&(t`l@Y&6nrGYHz6VR~
zkGb9d`)B@}`Pz$)i`y-40uADHAyS48Xw-vI#@S(mOulCL-Z&w>HDM1yBQ`5LzbP-g
ze)>{y{%#%Z_p42sroX+;AAIHB)-O><XKU41mi(Q-75B{ST<AgW)$iArN7n1CS#9OV
zpXa;e{ac@f%fIP`A3VL<{aQe_|MCcV28K5Hs8q{}g@?q=Vm=(gp7Dk5$?u%}%^T9h
ziMYOT?N!h{8KEx@*?$L30dkAoc0B*!X=mg`w+}^sE|nG9GH8DUxAnHn=8Sjq%3@Dg
z1FGs&e?JNn-FGA|V&$Rf*Zov3%n_amT1;~I?Q7Y+abZh$f2qpcxb?!Oy5lnX=cni_
zp7kyJ%bk}mwJJ`oIKSxX+SQqUk7c6nJd()E3BIm2y{vid^8NkKrixtJzU|2B2TwiI
zKYY4%acjxa-LWz33=Dgq%{ox+AHQHqlW6>hT^#Xk*!m;IFBXBWZ94SZYo*MZ_UDBU
z<@9!K=l)*vP|hIeZo<T->Fo8E@vQnh2f16gAR}zjxj&c$UKQ{#9=vev`pTy1H=j>m
z{Q$Ihtm@$jC98Wede`_%#c!{>y!_Lo&>xcuw_VYAzw@HAHe1+=2T#?XPrGnxQ}CCx
z$~9bZ%R<jCWPZyY@?q18Ou?hukFNf(G;_hprEe3m-oITo|INu|ovhw5P18ZA0{%J|
zRw&EB@SztrG9|R=2h&oKH9wlJyZ$h;!O|9&v8}oS9#q`3e#)tXQ5Aa7t)AyUZZf;|
zwIW03#e-JczxVqdJiUE>y;0M2+t6zg$ubATAw93Q53EX}eToVaUF+usJv@D>J3I7;
z(c2uZy_GsO-`Cm~e5?9-=vB$|PfXE`Tye|dyq54U*Ijn!^y-ighn)Lz^R1jd&wjT0
zLu&N%ZHDi^7Hxd11=`VBTN^B6yUDh*E@Da^i@x~n^<A$S7#iLqI?Ad4v_D>S!0sP~
z8b<9$3tYdKRX%;Ev&#Cq&NXhe=$8o}KD|0(f5YD0CI0_MH|2_(b#mf!6t_ScoKlCx
zJ3=hl8WK9zNBQQ@^Iqy|S_wKaXrpS$l9xRZ=2y0U`*H!itzGrwlLwV2B5y~6c3LgD
zXus_2vXx6uhkh_x`ii$|Uxliwnfj^+snxg3yxw}qUXIgOpY{61#6v2-ZXJuLs(2k#
zmE=E*D{c|rv!JJ6H@@y<W)Sd$c<R=QL*g9#&+f05ByMa?#;)cDJkk>1m|oj*X-f5{
zo(HPwuk1{}%m^``k+Yn=dHPCztETCk{O^|^55KdjX?m!7diQI_h?$6Snnb(vmzGp9
z{0q8$b4Ar&ofRiS51xM0#Z&d~g3~KmiQkVGI{bJ!Pgkqr=bKd~i+i3|p4^bS;=@z>
z?Rty)pZP4`o^>zi;pypSdP}C9w$!Td+&Rmsy_&B-bNzHsXZgX?#<iSZe&o4-IOG?*
zR`@z2L&HR9c2nkySNOA2{y~3BNnz+@(CFI1E`4ynAG3Gt_=72IqxI)4hAaPpHl|;8
z-I}yF!I)h?zwY_>Z|C_hwy@tfYnp!NQs$#7hOp<59ur5r!UZocFB`e**RSuM(#tC2
z?64p=%VNQy=`GyZAwNDHd#U23Eq~kd3G>o*d(Xc3I;rH+$GPvff4%H>+s%Fb^k=4D
z%DvMs=xfUezp>wvuV))OXH}E9T+y4H^O0T|{s9lYYwJIJ=eODU&-?f1-Um<F&n-N5
zqw$y*DCbGYLTW~mh7u*RWp(d@mfKyt-Ku`$xzWnJ4Ktgkv)=zd_wQ!sFYCXDT$ucG
z`)cSkYeN9EU&s-!FyYkc(=YE;zc-chpJy}kSik&w5mC{dS9r~2oE;QEt;RE(cub$%
z054SrHIMvadCznIw7;?O;KkRf^)my@u9Sta>RU2fe);(3=$FIWm#=*A^m+QTOZ(UM
zE!{c&`N|JZBc2<2z4r(|c>3GBw|Z~cypP>v)~cv1$X2p_Iw$78I(yNJg5a&7Y65mO
z6=?42PotFX%SWr!K*KtqY7P{;SUOcNE+`^8RSzED)4g-L)mi-A*F$^bmsBMm1h3Ik
zt~vShX8zmx(u@3S7xvZdxFom-bS6ciZ9~9Acw|r5G5xf1zs;g|#R7*HdRJHP)~bIt
zGu`GDyKtYP!i2u<Q&z|6Zw%EA{b00OskyE&r8C0(O6<#5AqTmm%YMz!(%wFG^@G;E
zfx9Nk-T1wXD=zKYnZ+L~H<~OxW_Ymf;8!NoGZO6jKj;3ulFt2aLXOROZgzc}o&QwQ
z^QVXRuUYu(T-ZiC28KH_<U~BTO=I1!)4O}`>Din=y|t}u<GY9nP1F7F-Ss)YPI5ic
zngtZ^T4Y?@8?%F*XMT6VPRl8h+7@jM4qj_-t~fNk^=jYGxVlQEiPx_2*oNn5{65&%
zt5xx{e`;Tx{?=_#$@8Y$zIA^&d$+7}@7~DIVt4mzcB)OaY7(FK{At2>(CH#u)i0Lb
zF74mO{PxaOwpkGyTK4VFo@sdI#+*w|uVi0{zh-7Qa2A@2k){H6vTG@WN6JC5jb&W<
z!NF!ktFQ6s<d@<{re5GKPnCdlnr>xe%RZmcJpJl=i>B%IzYZ|`t(dA@QCWMtmLpC<
z2VO{XsPM&GR6Jae`tHt7x1Y;=XNwfyO#pQXy}-K!)21z5{Xlh>Rd-!ghFxsVew}Ol
zsj1gzg?>2ndG8S!YnQX;vownpf?4%#-rVr#JHGnC)32{GMdG(j-50qffK|VHmYC#0
z?$xjP|FwKmpZ)qpG^_WEc2`Na50i@W=0}J#F#Lh;uOdE$H+^7b>a5;e@Zi&_yOC}W
z4ppq!IOTcS3OR#IRmn$X_T<d}CH*`7sNf!({olN`Dk@7)i?3X~-8DvB;R?dfOBPn`
zSn!;?WwUL#SICb|D~$YG^)H{Ec5%xjk&SsPstZnQiSD~Ib+*s*U+1L{p3b|S=e7FY
ziYD=G`+rK**4_>h-LoNf?(UukPkru7ZhY%-E3|srzQc!m-kuIw$jxuQZP_lDHQWpg
z|Db0FyUFbHnQ6p&sO9JPtTJPBXdaH*mUDAON=iyk7jgsk_w)LH&jTmknj~XUZ~$Bx
z?I;k6klB&rtG6`s`}5r_dN#|s{g>5OIJfS&HzRwGBlE|;lV88@f5;Jc?>B$_g6Y@p
zE^h*DmT~{Ul*sy!JEC{qbyanBcV6>j-_Pz4=XqAsSa3x6t-i(l)rY3<44tV}Q@Jw5
zFn8CE_%-sU%C28s@!;wGH<M;xlLqZf54sua8N5p7p!b)TSGT;mqgb^%Zyi^h+1C6;
zE#Fu~_sG1r)!QcPk*($Z;`i>L-Um<9k7}>@R5ckKt@<w>a_23qcgESCsDF|1GHmkG
z^RspwQ!RLycr-~)e?#7bMKhbGKmJ^o%c8HpzU~4?+^k#rCtfqPXtd}jxZc^^+ARLB
z=2NXPU)-zLuaAChVm&S(!14T6R>%jV^)}wD`kz-Bz1-Q%y<+QetIrRAI+xem*VbO<
z+EpE@<-5{1a3S~k&qdeDrrr8=J0tC^jOZSlJ>_OsOHD6Ii!ToSAas9D)|U#)E5d(%
zY9{x+UvS*#^X?<_?>7I8WngIHdB~loyic)4avpMIL3<nD-rjx=-O2Q^f8IK~Tl+uB
zxZ3Zzr&^FGv*Ux&I_0(5nob{zPV2g?Ut8w|?t94|<Ze-I(N73{`t+$@@!kl|K1BtA
zFD}0~pHE-$A@$YErBedq6t3~6dM@{VQJfu|nKpUxc5Z(2YZ=)~?4sqD#)8&*FG;?v
z@#{v}tYG=YP17TJ4U?tz>?up&wcTcX^u@<bQippO8G1Nd^mp|BX#0>_fxXYHypUO(
zFMLOMw`IXYfwt=X1^N=1vD%wVZ}&V{T6J-K$cIC5XS<D>Kog}$N*SGG4~cKE;!C{K
zF83Hzoj#CY`m=W7?wf*prs#)*7R`UUvye5>{Nlf<>-=`*7h9dpk6qR@ea=6dtlbxv
zO<TKnQy8nh{?xsW^H0q7-RS#Kt72x@_5`2i>6Y%?FRGV4P+czc*^rr`M--o<Z|*K%
z4<6UQ^Z)WXz1)&Yo_Q5F4CbwGeV%K`u4l8J`}}($ug30$U)gw<ST#+*wL|*Ly#00I
ztop%4%T_;l+BtRmrJ2V*@GC18Db_G*gO+7&d?(oaf%Q*r@L|T|6Oy0H9z5OiIa8!u
zEq;yksif801Dv)mKJM+5Z3ddWSl6g`D(g(p!PBopFTa?lYn*XyDd?2fqC`90eRAe$
z2fcs2to?FiUP<zbjZ2Cj*7vb6B&2f07f65P#W~`|{a^j9pZ@NB>E@-q)$&LCe>3ha
z2yf#O{**2g(s=0QqFWbuME|tJ?-$vzr>J~dReeQLZRXy91yy@E4xWB}^K;LGr$3gy
zeYA_=;h{bHQy|ytaHza{c<5pGmYg+}zwa^hxLkMOKdW;(;34R2EUlW#rx7x;`Es}A
z;{F{`^iI6bC%S7xc-FL_n_JbgUga(VPh!t3F4VIvD%i#qd*k%;#hGD~7a#WyET4EI
zPhsi2o9_PB)w?%r`SfAFurR}cSCGcG)S>AddvRnC&}QHCfBw5a+uOgBNI&)N!Vdil
z{KwB0aY{+F^UIuCVzb?u)mX;s%G~U-Oyigj*^jUPTx>ll<{mTOGOkBnD>g0*jnQ8+
z<5@+f*FMMXfA=a^RAz#XVc5WIy81~e;~|bi;v2N)h0L$s>2bQL7jl;4;=(VtB1QM;
ztczI*+RyUlZ1Tg+{BI$RQPEv0Uv(F^^Q&AGj=sLiGEV4~aL9*4_RGWd4V$K4_gjAP
z^V2>#P}5}9x+`T0bMklk*x#L;%wzoR%Dtyso(IhYm2XX_A%mKVHJulv4^4lFWmG2R
z{5;5MIfnn2+g`Zpy8rJhS(p0990~vK-{0HbHNSK5m+9rJ4;<Zlz1{j9t5NnNDbZ6O
zVl-spX3W<X`LH4TPu1dgKBbkpGCMxJ=32erWc!0u|IlrF^c-e<EMrohJ2?Z?RK0ck
z_SFwblhyr?&CwUQ2HxS9nishYGH)|?dS6$m^F{Eokt17{UaFlP{BnMf*rk)&y(Qpd
z&s?s%2EUqWBD%|G^<3wh;@>W9|7O)R{l~V7U3t6pwn=kvdu_WEe*L(_L2iD_ZC@W{
zf(Eo%<PVA8xuUD3b*l3GSGB3i*vmvE+nS5J;(xI1{mtplaQNSsQ~fX9!ly?an|!8t
z#i7Y3izN<%c6dBna7gyM%)<|D&b?bNwqO0#D7S3&n+vL6ZPJc!3tOFAc6@Ji#qT@J
z;&o?k%k<e+=Ui=`{Calt^!fWjm$XSfevtb2Wh*G<b3EjZ5M|eSSG+f4#%I<;u8`e?
zbG(<|6qVGfseGJb^p%fg{c?AOxtrz|@t9tl@Af_4cPVJAbsXrtER)}la;iQbnk`a)
z)Ntd!C$B8O{C#`#%b$a#TUJ~(ymMpbKI5d_jRwCx7;AG6Cw%zSZ2NM{x>q|uso)qJ
zC@gxN#lA0a)dC$*i8UQuUl%I}x+PZfmHpe-A2+}FUz=*sQ2G6_{DS*argfE?zB66K
z%r{TImAMyuvy;i4!nre!b6>HtnYed*!;!haCnT76-+ui3(RHQ0#|jP{?AE=utZDjb
z&HYAA)79fGlUVdu`<)ZJ&dL!E-s$;}`v>S0xjx0?T455-eYv}zRJ~OXnR@$_=HY1z
z4^2Pbb@_#{w(Q=xZ}Gc4ve&N(J$U+$+wmj6?Dgk6*UvYZye-zkKR>RBMSr#x*U>U&
zN3OU>{_n+JGcg>v;rOFz_O6!;T-$Zu^`A9w(LW6CWQwCN;Ru(N4x9Y8y59ARVmim?
z?f>QfCcZnNf9kW#-#edMo_m58<+Q53+w1ek^ZS;<<<4^DHi>rm^53UT+-5&#M)UN~
zANMbd&MW+rHNR?O&%CJ8-CS`&{d+XlaSNOi*w=9N;~miT0RNvHzoXpNu2Q^S+=nCn
zK>VZCVXHr+rp-)VRQpfz)ZRF&+__mZHpWD+TYS9x?wxMX$jIKfcd>IFd2M-3-X1cU
zReaNM|LHT`8%w+&@l;hL+v)C@vASuxYxpE4yYt@B`R2JR?L6&oPxg+#E%91t&!0aJ
zyk5QJ@4x5H!0?deA$Q)fvobq)ME88aQLJw&e(neEgjM|Z-~IZ6;;wI7zhBz@-I`fq
zclo|Ak-tiPdn4+XSbTUOryr!T=w-o?*d2af<TbWk?z`a1u756mxi@%bdWW9F4(A_C
zVaE=4e@rj=^W)>In3mIKxwlg8$ZK=NACQ;YHG6LH-js`TPCeDCi42wAb^L+N#(!D2
zvy@97{;sy_TlL`S{V=`7-<RqvXnJMTwYq=t&FSf@AE+)goSEl6&w2Zi^9yam`z@QM
zUyHD+c)R9@%)bj4F0QKDRbyGd?vdOcn~m1Jr=s<`!IAIuqiOc(v(X<8;g0yo^E-<}
z{x9XfG=HAF`xm?8qQA@Y|62d5O<NWrzvj5io;fjE?9i1HeLHIzlz2hq_Pd8g@ArOR
z6{q(rrsecbah~(PAH~_v5ZKpX|0cs~)uHLFPj?=hkQ`p!WWICHe$ks<Jr|Z<d?s=1
z8@sjnst-?<Pe0DA31dn%eJXMA^rqFPU+j(!yRhS^aYj)g<I!!$R)0u6^)jY^+uwyd
zSI<rAf4|`OH64Sd>7LfpFJJz>x#z*tm9~-`aSRMPp!S{I_J>X4xY~Hz^6$R`xAEfj
z=X^0W3Aj7`e{bF%qrMl#=d{0<)z7n-Sa<c3(mLjA3EGl8ab3x5&C@kMpHr-0EO7e4
z6t?uR_`db$U4PUC1`67KK4Yv`+RXcq`^Vv?&pWkhX4d7+<cfc^>hsa6g$>WkA3kV(
zUXi)cvai1!+#LVB+W5t_CqGTr)~w`;E&1@N?6Rxqo<DytSx&h&X>HT==cgZoPB)Ny
zdw-4d*QuaGmfT)!T9BeW)g*3#`ghQ%^JG`aFF}QNrlnguUo$f_m@3p9%ssOG;UV#V
z4!zS#3PYnerJP)`bjgw)ud=dlD?D<4dcj81cfZ|s+s!r0+eqvocYMgh{c)zCW}gCR
zx^_CxmMs^Sb_%cj+F0mfzrA|>W37L?-|wGWx4U%p4!$1U+XoLmNPT|i{)hG6jvtsF
zoPw{?T2RO@&KGs#$;ru@>gw*bX_gHWU$LL|=u>1+c@8?rbJL5Od4^AJJY2o}=&rEi
ziQV%e<~vKo{=1u-9r~jv+)v21GH0FOo+mM913&Ndi2?OuZiYr`Rm{97;k{n$_xByM
zp65Qir)O1CSsA}is%*`st(|iBclxmE`@cWEBwl3wqP~4{8+jQRHt@j)7F3<`L4(s<
zpQ5iIxw$QOHhbp&C;ES;ui3NyQMXo_&9>bk_5weCF8#ESr=;}aQ_smO9vVM>@&D-m
z{~dNO_sq)wu3r6<_jggIEr(t4hfQ_o^1rU<JjmT}0PcH(4>J0{EE<_5{w4FSj5Fx{
zBEfXvE_mv7X8gRzGDSMGf93p5ie0U}L3G7~)^^#;sjcE#p&vedZ4&)*HMaSnw^wpk
z)IRW;Rq=Nvlh1rEdjE_i>(v{BTM}zO>rU6)|3~BRrvp-;&VuQ*(rppC`|niFop)nq
z+L_Ovj$P!{T2h$L&cLt+-skRwG+vP69NMR8lHB*fRoJ%YM8u=or6KRmPF)$i^4GS)
zFSl7Acg)-JTl)Cazx(f$ivN9fL9tMlL+&=8Wku(CtL_J^4v2BIpx$p1Ob(Zw`}RJS
zDbiV)HaFt@t1kt~JGaHW+WGK><)yp#{lDj*2z_{(|9jfSH>dj^q#j(#dga=CiEFRl
z%@O&v|8!d5o3E-`HI*+MWR}j?EX}M|sfaKvzLE6p$+i-!t1-)874}bmpXa@7`JBVO
zd&^4Jx<@lGB*elN$a}Zk11(<5I*q=7Lg>tgrTg~9{r$RXi_Q$t@}S?3s?yX#|D3gW
zRhK3cxae;CobVrS|5+rr?cigXxAubIo*PNITb?}Me|f(Ui$23)=vtZ<{RjCrJNLc2
zx#+!jlcmqfCux0(46CeP$qK)n-6mswJxux9)=hrV>ZyDwt3E8f6qY&V`IPxV2Ty-5
ze|q_gci)55vcED}uX2M<hfkZkXn&khbKL4q(`S`Gj~x@eV%v33-KNv7UGLe3rxTl8
zX4Nif*cLKB#JT6?F0G22^K#C7{#5gk>(!BfWCjMsqi9W0%-H1O=C<|tty=$}ThnIG
z3^}WmHG0cHx%hkN?`vyA3Qy}^`4j*6#oej(u3zpRWOz2W`rX3#`PS;|*#D)!-n^4V
z-%$Tw+<&c%#A|X@Nl({mRYY$1Ud<85a0fo1^1=KcGq3GTJ9*(350BkpEuIl{@~oV*
zL&|06KG*4~D<7oRb?{kkyS*)%RX;kn`SX^7_0#{~oZ@_6yKm{u>9U7=&#sy8diCyA
zH(Ag+(?uDgd(PzcE$)AQ?BcfW&|^jM1urI*RqdDZ>tPmu_i@v@M>D){B!yKyRjMs_
z?-Jazry@f&_GKRET8V~I$O1Bx7Jb1g(AdLDUGzn!o(6}-SI77~tX&#nCv<n|(vZaZ
z`TBJkGJg%<+Pc_?+<aUAbjPdu)4E@NKS?RH{U9&8XH6lWshE8Ni~hIm^QW!5naiTz
zdGvhOYi1WK$dr`x52nUr^)nvE9b@GEuh?E)Y4G#01d~$meFyuqx~D@PcF+2}RBiU9
zZ1c|2wQH`0t!@&Zm$CnCTe61Xwikb*7H{XCebu0Qk``OM{Q0j%*H>9;RU`_V=?CtO
z`%;u|5%Vxk*QzA(&y2$oEA39NxAE$K=VjU^RsHgV7K=UugCcY#V6Rq9<A;QQduM$_
zUTJGt10FxT^$C3$an8+6p!0w}sedj|s!?=ZCG4?h{ghMgtJW`TTlId=WUc$weRZa<
z{|h|rRsJP!UH$vbd13WS>!qZQls<g6gI$09p9lS~a-Tm)y?HE_BaWd4k>@A;=r>(r
z`KYU_E98CT_fHN+^V2hfY#H*;ZNF|+wRcLov~yqVn|(Uhwrb70-TJ)Z;fJSF-cHfV
zewV#$XX!?_7e>E7<y5^pQ}XN4t%z5zih0zprN3T0f4-Mt)AafCxBZXbSi_mS=F`2;
zd)IECoz(x{fJyw@vxU4_MYSvKUcY#~@H*exh4ODa!xwT_e?1`u4j<^|5AYS2&+8iO
zg<uPITV?i501d2S=9VVeeL1(bc)nfzV-eT)iif{#&UhI7pEPxTu$Fvp<212d1>f(6
z<mGREdojAhtVg-(^t<=}>_rdk$^IYvC+{#@VXVYb(6&r@p167c@6G?Q*6jn6ffIaU
zkE!#&MRUJkjV`}hJt&7nhQ~j7@wMpI#n-8hrF{|SPj%(EYE|6KxV=Iyf9E#WR$Z%-
znZdgo{$*cQ`qjQ9<5o;<+|^8}j73*ozjXUB>E;ek@b<OOR@b@t)zjvA)H|NMrr0i?
zT>a|Qi=(yLFRQI5z06F{7oQeg$x^(BfuY3$R`0Repe)KpB$eN-xeTi(!<U-{1q3{B
zyrut>nO$ek{G$2yTmN^N8X8_)sxzTNdda7YQZHUD@)lNK#jxz*{eRQ`ZTeX2a{9Pn
z?Zf=uID`6GCY|7B5x??<-1_~S*+M>Cns4NAzV5i-o;zi?cW+6$?#H6fpp2Y+DiZ(v
z_$XID<6!*(!8h~t-~FlOVVro-=E;rd=hIexNd5XWHYs+s5x@Dhr0j)rb*+0JJYBoX
za?7_%ab0!48@NDi{Ds#ZK2Tk0>)EOooSpn)y8HFZkaI)+`m8)&oMAKN#_8XmdM~uT
zzaEn>_B!jc7uU;!hg_;^z8lTjq95F<XS31Hf1CbQc7}!v&^zlvTPdJzC2)^Nu^lq4
z7LC5Vc2Yu{e%Md%Fwhbv^Vgr2cxpAQ+?Ol!xA5zJi>iZv?!3$Yf9oRmvp(w==Iwl)
zubcRN4tmFbVzB-D;s4|9SN~PkPrvrg{R0zhJ678VR;J$PO^@R?$?W)0$YlS_;n-h(
zizeNG;{pQr&TYNA;z8=InIf~opU%j!pVB5{%x@l+q`mO2?(WQEOC_}`D*qm`xfLGo
zJl*=%%WtWDar)}(O?I*9`%lwbv_Iae{oA$JfP<ieFWTOG-Sqg7<l^nz|L+9V2CZ4H
zYrVZ+?)FTt`{rlg9{RNUzSwJK28RszIyTg;3KI^C2W@!7y%M9G<&3{lp#FCHpT_6q
zl36_Ea#d&c+5~`W+ry#1-%n<iC{upg-N&F-D6{wX%<qzW6aHD0YTbSDO6YpK;+A_0
z8bi`QY&zq1|Ihh@4^&rcgtzMDRGwV@=@-L;gNWADi^t6Wn0eoQG!pxKWGD9%Lzb5H
zdv3kes;T@NGKniLN&ko4#XX`Tx4S|=Y${98mEBi219CTRTI@XMi_5pIwh9KFPj*^3
z`uZ*w{bD_x<%Qkp7nbBozqkoH{Ltv}3A_I12{Kz|RPXkf{BryEvS7!roK;^BWc_-#
zEoJ&Vy*E1_iI%*|ntQy8fkEIFY#dDd!vXmf7boeh%L0wLYCieU3SPH~tr^n#@u={=
zjzi}c|DQDVd+fnK-eGliEAPawOHT_rs=Iv!!?NOA^_O=?=LIXC*?s<>@x^Kx=ZSxx
zuG{_M`tx(0ubZWVKU|t^=<xsRQTxx)9_9Cbil4HckPIFVn0B3=Vd8f1&Tr7x0nr!9
z931w!wR8L8^vjG-T@LxNY4OjFi0fUTj`9jazIZ*}m_;vcWhC#|nBK1rTCwB3etPrS
zRJRYCHr8_A1>Y|yncgQ|yw{|(tir~>?q{HKg{Sr2`^i6VuKt>pu`xg2Z^8QfuEp<j
z{FkZw+Qu&1v$yZtmFbCj;tUN2(0LHz^AWGKS;d5J-v5pq?Ea}A*=*aw0a~74pLKuN
zRINAr+4&OI+5C(Da_-y5zBq&Kf7AYb>g2boI(Vmu`|rVjzo%bgXOG^Td*VUrS3wqi
z2J^$P^wO~Z;~%|0?vl@b3-MU9ElfK<RS2}|^GQ(Gx-uTiYqy@?Tzq&{#SBBfxNWz$
zU7of&I^)?=&;e+vXKPIFNL;_TH*TKw6mQPAoLUt(@9mu%r*~^v)AV+;bxZPdVieDP
zEn2m+@1D(;#2XJ<t9KQ=Fxr^VnH)Dg_H@Q1<C4mc0d`fp7#Lg_AjPlQBk+kI0%~XW
zhZ_@Dwm7uwhwYf5^j3Rm)xT@8QC^|tcNQ|u*roqp`fuU?!+i{CUoEPhAD?g9ruh5w
zkxq#=pGy_ZY>#9Qdb@Ay-S<WQ{fg~82e}*Sk(z9BK|cJBr;fAyVV4qmXZU(wo&oD?
z38q8L{#*7x09`?{Y+f0U<+PaHervK_|8A>GUN`;n-sLaX-r6p6xYzW|!j;F(R$1-3
ze)_qY&H~lkk8U3>UAUL_rD|qn#eA+fi-w1#x2krB`cHo^zD!~vck%rDOF?UfWQ%>4
z{6Fzc<=4G)BEK91cU`=#`s<3>ml8&X4gpBRJvRhCafDf@crG|Bt{n$%yvcFYnwo|b
zUylV9vY-8aojtg+kKx!m{qA>*%JcuSvls|%fADm&d^wAL`Tf6qYwf4?K43k-4_WI5
z>U>N8Z*2SL%=5W+HRm%!mKHnPi~n9PpPU)y+Qt>%w`Gpt>!^#Gw>6X3O<y{F`l<)5
zwq*tr*Oip6vbyuSwR!r{wS{gU4uMX^+kSn0fbg5*A3+hpZkzi>F4Sg=Ni?3`Zl<?z
z$>i!43m0Flh?{p^b@v+q+il8I_w81WeE8t$%)d9?K5Tk?DRQwL14D~FBuTV=P<6^r
z7To6{UVAt&E$!KgC(oYsMa9I#1bD8OcAEaB_UgUw`@Z*fU742h_t#gsC4VRFFVGkN
ze*EA(hw0y^230Nq^~jG_zYApK;di~Zy;yVa?zeo$Z45Z;{omjFRqn>{S-U!RWv@rl
z>TSGi`jzG}P1DaV7udt_qZ?LCFxEeQ%=j;v=lkz{4>q{@pPN5phQ_tTJ!fj}nM`_q
zp{bUKaiK1F8QGqwT`sKp+;4WVR~_9Imi+GPrq#vM4bPwM?>ipg_fs>py?XDT_va);
zcWrRCZ4cSG^ZMz<t=XX;KJ7hw6EZ;YX|wIlEsvYB1xhz8oFpcD-;Y1Xb4mMl-Oz=n
z7h4Bsy!+@>_49+4=&n6ADW;)u`9+cZ3=D_n!&hG|G;h&wR0i$10<Cgk^i+U_5O}pq
zG^iT>6TasAz3TNJXFRW#w6EK9NZj{KDRU<%oD_IVN-r*LmoH;6*nY=JR(tZxxwYRd
zst)S>j8<-_eEx2?vQ@*w2S>%SgAaNazvDGMGUIP<;K9>f+r=dho}RZVr?8gc!(5~w
zvp@3Syox;klP6DBJZ9xfyyIz`w8Kt5xIjND{~`DFsndRD>GUZom~8#BO;Y&n{z9wB
zR{hB7+d4|udVH^nG~UqmEN<TFrs*fAetz+fcW2h5+I7aOYg*ku94c8qUH|Fw%llJ~
zUKIZ%F1hgZ>EEALKC9XsIK6u3lPR)`-sgKSU7y<%`gvE)@te1wrvLu@BI$PMthzl8
ztonb1L9>4@YDg)IBc90}v@-^@dW30`!=yO#7CppvuI%gUzAAt!YyDX-_8UFzR+riJ
zqB7yjg_MH_etWI7k(d=%7s+byX4c=`f0idtmSObyRQ=*E|9<;L%o5)pJl%Ht-qY>P
zYJPu=Q)Op@ve|ifHe+F8*RPp#;2?Ls-OqiyAGT>eElAz*p_N(6?%4!T(DW-RY!Qjm
z3wwC_$t9Lq!8>DG^`Beob(OANb}h_XavP86z9rk2SwF9w={rAmveWL@Sub842tAc~
z-nePH@AYpIucw*CF8i)_W<h`YxuyDR=A^gi-Fbbq)=zw%M|`{9o0YMWSw^*danEG#
zJDrWq1r7B%e1vVeRQl5PLA3y!WL7-V)>PnBd0FcGViLUJD|F^VYqQwh)2;ezUT~{C
z3s92)wO-h{+x4s(9vU3uNC-N9aku<Gb<qQVz*VxT>7!o{Pk@hlI-MQUJbh0xM;rqa
zqP3#%Q9h$5P0u3#A-8<?i(<8Zi~BF0(KfzzTtGl-7h6?ET4Wnnd|lxg!zVWu&5C)o
z*|Mr9Vy0!q&1;*_&UpTG;oib8cXP8>uG!urbW1qoh0*FvL)&tV>k;2;PN(ituP?|j
zbbqmFhS-#xrO(!tExvd0m{ip|jq4n-aku9Ce_UQJ{dxD1dAB^>XRUg<6?7HhA$!3+
z3=A%2kR<YK`G-{by+4w)Os(WupI!4<slP9yUhU4GLgybs7NGVWXpN)D1eQr(b}5!1
zHd#e(P6J)-^+f;I`lz$wp=P<amdtp~D)e4^>8FHU`-+xI)%7sXI6YbIqP+iJ8;Juo
zf0+9io}IH+R%$S0*Z-O=xQBrSUivg9JXm<*2fxXeI|VY+KFAmCxPEz0tG?9sgU1#B
zuq$3F`n+?hOBDNoH9uZyF1~&m(i`wSoVRh^)EUjwlcEjJ=FeH*H2u2P^NZ<Ww@%N}
z?s@R^<RsNEjXfD&8h4i;nhL6i8c$CTyY>3H-V*uuQSR!qUSHXebn8iTcF%>@>b*zi
z-SIsC_PlA+^gCULP5wVI6W#SfPCtBC`@eh43=S#Kow{|L@lwI>7mGJf?0Qo@@trqs
z&JkfvLd8+Lei%3#-MF<q&y-2x9pmTYn<V=f)beHiK8?4R5M=CGn_^I*dHSE|&HH?H
zH%&4x%@^3iz%m0?$}~DWE@-h|EHmMSL+k&5<Q)Y^uYYirKKrpDv`_KCu3v1wG7tWY
z5#6_^B1Q1*>Qg}vPq(gP+qf^(<d#(LgQX|JTq?sqhkP*l{YmE6qhBgjFAtcVdUYb`
z;OQ-^mVWsu+PpV<i~hPf-t$lOEvrA<z4-SvwM+cvOQapSe;-?BQMJIJQSR<Mr}OXQ
z9sS>)G;k8<OZawW`o+)fVXH2*s$YA4{L(fi28SomTvf*r|LtP;<EE?1OD6`scYZ&8
zVP0Th;JNB`<|}0$dOv_|1PXEx^0@T6?Sd+NlWfY>RiWTIZAbssyno%-5*~uaXT&TT
zE;>($-C8g9FF-Dr#o+zD(snPUdwbuS{qoIRxuZ^Fv#bnHoZp%P-ng*2B`o^>Yxam9
z<aTI)H*&x;;fE&|@rxEOc$7OMs6{`4b=URNTP|yU=_)?4amFm0jq5^9?q@7J)99mI
zVR^gsnnd24kB7Tod|qvw;e0#nctA35cjloi_Zvn3XSHTA{av&9=$C|pM!)X!Ham8I
zd(u~O%A&d<ZU5exa<^w@wdhKne|yNeXYQ`3bQb;Y^KT9rBr`BPfS!1Cx8}#D_pVaM
z@5V_5zufmbZpjt#_rcO8d2?2p_tf6({%)y%;q`|Umak{$#(vvtp=Yr-$LYf{#qR10
zN>f4iE#V0~l^GAa_sv)&|M|%FKjLev<RpskI)H|4?ZVdG{%&v8@bLHk{WC0YFhBDy
zZ;NNRT-%((>ddZx+B?p)Y5L>o+fL^)9PmXPq3H4KF>CRHL&v#n!n=2_n_2ziQ2m{Q
z^NVdmB<0IL)E@ipZhU{v)jq`oRY_W5D?U7JRqoDki=4$3Z)W;V{B=~LWyQ_B+kWA{
z)?L_htWv9DW?5pNR9?=xo4i^ToteE$^F_`r_D?&%_<zjYMc3ZHe81S!y0~Y<(|=z#
zniK|4l1c)N*jxRo%5Z#Xrkvwxv$y?R%<AxUrR6IWk8Zm%Z_$e8omv&1`t}ZcnXk>4
zW@li4O~l_V`Ekg(Z{@-R7CwQDUB;RZ6%W08T$!+AI~TiJU(ve!7E|YI`|}M70u|3b
zs4+VB`Qfv|%Z}ZA(Rbu$gRY6fQ#bzn`4f~f?PvT?nySC1;Kd<vhaELLx;6hD`|Y)|
zMyGJSxa@&~d<o@-NZY=DMh6X9B-su2^Co$Sa%}T9+oWpQJpBjbt*O7Hl`ATLKD7@@
z`nZ`Rj-lxYtXJZ&;NwRod1(VaucD%&E9qrB?5}+g+gC9A&y1G}Zr8s~-Tt1}W!vPn
zQ?)8))|OfReX`)M#r|7{Pa?ACmp<2twB8WLsvn&jz4G!kPw#a)fd@~Y`8D&){@Kqj
z{?D1^&b{vFst-?(yNh2G4_CWjui|?7*{jbbJ3m|5ZoF%_<wm&KkxToZ9lO{*U4Nl*
z`1%#wX79T7;;QrN&=-fCdkXo*|L$Rz(!N@wSCuC&yXVDI1_p*SNE`j^{)gR~Du#0X
z%kESLSY;UWIQ}sD1D_Jbm(NvZJmijla$#@G5B;dGtU~#RJAO!ms*8<fsjosr_kFN2
zh;8S)_+H9PK=jTdp#ud$A8)UXKKga-nQQ6Fw?TK8Y_pMmn*PUcuZmeRTa$Q00wUe=
z*c|7Ump9<^{!!t$O57*Rwv8eC#EyUkho*zJaBdam)T)RqQrzJD>t2skbT3z2QSQzw
z`LCBB-FkY?=RIeZt+=%P%7vuMS;-+EglaWgFP)bOJa~HVYU7NjKNo*_Fm0(;#n1Dm
zh9&Q3m5A;+W3lP{#j@zlh7;}B4P=fK{M`7g`-|I2Hsykca{8L7zprdaewNG3z+eDf
z)G5Xr-!|_$>%;Evv$sEJC6L|@^2AH9>(>ajs$aUAT2t6M6H=oVY45PfoTS{a^R3w{
zJ>8}E<L<rUNHFVJyx-=7zM|xT8?$n?axNb9e$OcFJNK9S*C2@|@rI3v)x1HGKP8xs
zg@KnwESvAWIGz98;>hQhLw{^qUirq^nX_FNv`6B@(=~5(ROiK~v*=%5b$`kKdlJt2
zb9}-MdS|(7mGYEa*lzah;@i{DE`Q(7>$l|p%sZ@K-uCX)s`wdGCuLe%cA>G2E7|Dp
z%l!OBZRf&Fx7^5fzb>_q`~LHL`VvVz3=B07A!n21S=AKI{p`vS{N{g~Oj*v3U(K<x
zF|QKOiQmsV%&HH%9|~WaV`EY)cmWuwH5Pr)AzVCmmPH_|!PWA6p0%c?Uv6u=et9?T
zwhZH$?RF_F2K%?a-@e3b%@OF`S{qYutXTFVyyW}c_b;X=+@JrK@gVmB3CNBa(D|`E
zRT)9YoI5;Ao6ep(E?}_jTa0Gl!_#YwCBGbc*xILfeAVWad-n&gvA^bb({JSiS6=I~
zGV{HWuV*CGSG_H%dUZnS*DXHx4~Jg;di~<bRJUI*KCyn;Fa0Rw#U}?xrN7CaWOf<r
z_Sx+&n#mPEZJt#MkMS|Fc{kj4PId438FS;bd)Vp=uD8?}7#bELb~|Or?>%7~+Qt(s
z^POG&g1nCOu6=ub3lG)urM0~iZJzy}K;vU0NBj?_uAfd`m6bnN9E+9L1r1Jy9b`On
zwECSP(~QPxV!wQ|UnVeoKH)CI*fTekSz=yDeQ)NBjfei*S3hTzJ6ifK|I*G*;W^do
z*Z<G-%bW8<t!zi(oEbl?`n-D{H(BQ;G#jqF!6qHd>fuy+wK|{U%0!pR44j-T0vw8*
zrdO)p^WJ&<>Gylf#gcEc>^C+q5xw;4gU$QJcV`->mxo!mTRy+~z1Dfd)_*Q5-xpo>
zjaO8D?CiQ{&xY4lB4#h{zvp0Kx}21wv*ekIk<&g7AKvbc)cb+cmW8aldg#@DpXYy>
zj{59x7l^Ta;`Doc>aE1ub=Lh=A6NFM-j2(S$Q9pTxAMfl*^zb?U)QL_Ma-SvyEMA+
zwOo0`^_|5ZCtaL<u)k8t(4n*J<{ZiP`ttjom3+}#Z<jybu6V8@tG+=gHcXDQDfgY-
zo@s*XJjC1gG@rP#@~4sj2Mg1|sR?i7dS*>N;nP@?c&|nDoP1@{sroe<`#Gv-PO5j>
zH-pb>&5FiJT3HKOIyD!bi#DiUxGr20B=)LX$;9LeLsrZzt%zNQW?>s|=@*?9-*#Y8
zw8Hf(8?G<kc3x#h!Ojf@isA-u)P;?9F+F>o`rlM5!N%r<-8wPnrN3Oi&i3O;{P;-!
z$@bISueX<$z3!a8>DlF!c&5e!B@)uyD=j;FCg?K-A7bJDUAM{p+=aX6UhM-1RL+ie
z0U!INbFT(n@!n@2wlq39dZPSveaqZm_aCnI^-b5lbACy}W7cxLIJ1yl4>meai`nyL
z#odK>_X+aLZ8|>rxZCC4soSnUzIu3JR|V&Z^9MdXk<g2K*ZuAI-NJV<J8ViXaGwtP
z`|;aH57!+AG3S;p*t58^#-6FsK|x@}(HpiBrs@-C3jI8^IR4&+&LqQ=`>iVWDlUp!
z_UiuAC#O^X1$M7Cy0U1!rBkP<DGM8$pG124^UPMO{DW^=cg3Zy+x>1Ar{h;w!?~YY
zbr)^epOD(EmGjX><j#H@ou?ArHHl1ZOO=wDo&|+V?z>gq+}Q9l=XKbo{+BJ{SM8Sw
zzui>LaZyn|@nF;XpSSn?`?Y@JlrMtfy7@=%D;?=}D3~hv$<-iO^8LPqUGK#;T51FX
zTtb(7eT^%s{SY=ySzO;N_23K1H_Er;d6~}3K4$$gU1rnMM@NefNdH^<J25<c{SxWu
z=!91LH=kOZyH~5kdp-V`(8T@k;L&d3c{jzIPAt0oqxHX^&4dSC>A|%dW8Al#IN|!G
zwL}n<j6w{VpLG8)zx|K%XOfl1#zkyit5&oMsVr-Oc^o9RO26yDg9PRkg_&9to^n^O
z(T<N0tefWLvVE=QOkZyGy+42M`C%t)VCp^nQ|>ONXREi@n;EnC+%;JL^3c-m#{#>8
z+BeqxeEuo=@kD)HU2(zMYtycmB|d89lu(RgX<FSrLBDAxm$$dpef<|kcfWIsIPK#&
zcFEp?p_|EGFFwvPt+}^SE@t1GRkj}6_Dj9dwLN_FbiDG~2N&NSe|q}l!pR<4{*NA|
zMxQmy(Yf97TUhpE(YBE1M;F}^<X5qH_)sNWb*{u?PTlh|pDs?;(_hY-{CsB053yw*
zuOuz%*yo@iz_Cn9G5*MY?SJY|x?e6h(j}#_a*>$Vgc(Aw_ONaie^oVg;gK$>75g-n
z1@AMs5x-_rt1z3l{Gqu1zv`#NLRNfBp13$I!v4*V&L?&AbGg-REiE->+0W;m@yj&(
zhnZT!KA%Vp*8_Wg%P(e1OZi`7G-YLci2d^ms&Y>c-4p7r;^g?j@>N#+gX5>UF}n<$
zip&<3FPQztUqQf!-)SF5n$es872oP@y?sw9#XZ@wG&y*puleS;a!v_VeWp)~J|;y=
zG+mW6yAk;2{L|Ar`(_5-S6RGEqWs;qho4k;ScYkCioX}Gw^aJ_p-a7cOOn{-?!M>w
z9p<@D=X^nM%jw0xZ9aG`62E65)|l$<w)FI*+I<C9wLLYlp!~wIOepz#!j(BLZYQQ&
z{_;N|e%Y~8)HEQ-Rd<Slq3V@N?lk=?U$vY;Vzq$*_tUdtW_jIk;))KK$jTKRvhY^m
ziAQa+hx?ZPGrA_Rx$xl<Wlc{(gI;ApJ@MaB9*m!VetODwe0I~qK1Ckao@J7+@7uTZ
zzP6uwpDl0yU+*W+<DI-b1^o4oEjfK^#eC)U?>KJGcTkuS*HR;RrsT$7rr-AQ`{H-p
zdeo};q}xGU>}yi7UCZg|y_WMTzE2Sr*WZ?^_(FcyuZ!}}qq7S0r`U??zk5D;M@3=?
zf9T<r_d>nQ!?v=%7tK8+%x=43-<6r&(|ezuv)PsE9VYXx`|H8niFy^5wk{L({N;HL
z9O5wlV|a+&mih0KY;G2&MhAnbKD%oKXRz<uA0Dgrxj!mq>O!&Fvx1;<HL4|yTh+sd
zv-DomP43dYrvj2(c|+nwu6&;<HGLVA*ixgJt-{mVT0~!6zk6(6eCGVkr4N_r+RQmE
z?l!yZOw#Qg4VFc_RT8%SW%zNge7WlZ9#P(pNxy7v9j)bf>aLgc@R0sZ@8UIU?Om6D
z+xym|(xbC1k@ee6vsLzu4hDZYekut_w8yF^FTBP1Ozr2})R;XPT}mHLZJc{!&ik`Z
zPv2`R+4Q&MP}l4K#@9N#r@uY^xN-}(u)cbm-+`x2FFbZ_e0w8c8{_GI%Lh|)R-Wq)
z-YtA;*Xj!S>MsAsoy_~XN-AGDFs%E2v-5V@&xh&%{63sLZCC+GCq9iEs|~XJl!}jj
za!vT>^Gts04lQThU$c~_L<BN#<yea?C#}eCl$yS*>7>}HrA2!;KYyd2T)JH{*-U?q
zNw!H>L*hk)n%@rQ`%XmUC?xEQ*duZHZt$F2tJ5W%JAb);m0fFp^v};pB|Bd(TR&mq
zM@7HTw~qt$e0FVEF@4LX*phmt#s{ks5w-Qvr;8GA%18UC+<g(E1WGO2C)vjAs}Mc<
z!RdFHYGcg4kgr!Jzg^z_ZJ(Tt)caFMPp{TgJRxgw`l$B>2aat|-85bvGn<-!Xx5&$
z6>k=>zlol+qh_Nn_u*;5AG-<?*VsrkE$J$r@qB}*ebN1sN4q<!z-4NQ(v$84ubF4;
zvkF7oqQ02AuQ-~5Gw)nyGS%~C%{iE&lzAwnr29*Jo^<oy<29DYw$1n5uqD}N-v*PE
z*rK&Ev5d)onQQ(omiO7&Ah}BFlJ=_mMHMr|FJ0fq(=K`=KPUU4pkC1y?(Uk*cdJx?
z3v;l9izg>5nyOC}{N$RzH6v!{om!^f$=qSTTW^`ip7~bIaqvpu#n9tNxxKBw-Qx`3
zGynCmquu4HJU8ohB|LiiGbLUx?#B7M;(doVU!Q(@`t>544NEW0>K5<T;;sqUCD<?f
z<;XQHalN>0p}SvsPJhnF{$Sf(J&nxlwyz%-|CsjJ?2pz3SC_pUEKH3Fw+zep-OhTZ
zwA2Vn7|g7{@@N&$))(1*0o^CF7A)zC(r`@jTJc%zOx%jsp>0CiSqpwGTCk2aD`png
zjG9E&Z>8KXPOb2iHJewHSo<o^xXn^$&K13QJ<b`d+!c;>`)U_R&rjEX&cx<-|EjQo
zc(mH|ci9@T>bF!&I5-{UKPLUU^{%q;?~*0`0XHUeZhw^j@Y2(nALJDk@3+43RuDMD
z11eKYdR$LP-L{wI_^C93bM1}ePrZGfChn=b!zb3dFJ@oLmM4?mF6W+>)YCcLUHHxi
zw<?QmA(cfRH|;&g{a)lo;m1vTk4;)GUHy3r^F7<=Cy#c=b_>^Z1x<{M@A}lIwr74B
zgMG>NW5%A<75llHZ9mG#eT&&+Gf6>d7AvSc*~{@$X+{uIQIcgC$vY(^5L;1FS69at
z@c2^50T%9PlZ64Fr?0uPh`ajSw%2*aZ98@5gy=>dV)Z%s_4?xdYkY3{an8^e*PAn=
zAyf9*CRZ{2yT98vd^&nQV!Djj&!D)g%@gMIdTlw)ezJVgrmYR{{1pVwNI=74AIM3X
zSH5p4_?Q&<fPMZVi#^3&%&&tqpGF*a5U)H_GU0Z_$)l(5Ui?<^Zi0T8%=9DO-y8Vv
zohzMw#QQB@&Ylf|ue;43U)71X*pj)UM)S4&-sw6yc}Gg`mBs9n(a?9S3ufN;KI`bw
z)Aj7T7A;!r`@R$u5();UPWw2PO*#2zu~>NO3fK7J7Eofqo<L^!{WOY<c*66WKfG^J
zqP_YijwC&9_2#>GS48cZ!9C-Wbl!mleK9}(e97`Wu;bj}>-N9<IXP!6-2BV+>*q=O
zA2q~t!}lEXb~vCOv&rD5Qn$ET;);DNOb5>#z7gR8NvSiq)Z&k=etPWHp4iMSuS1gc
zz6&&+oOr2V(&_1UuWD8(U*?nkWV559bB(pfHgnUnwsSl5Y+ILqtA4o9Mck(H0Jp#F
zmzM15^3(NiYt?RCdu!6^>AFvES6n&0DLL;(l=9U|&$p7E$<~MLlbv@~RK9f3ssDFr
zU&Nmy=Q~eNf6!!Hl?x7%8GJo=n*)o5W@=8*?~K^$)vQ!BKf%lhDGw#N;z}(0_U!{F
zmP0CBzh8&Cm6n>`OtZPs#Pi#K`roIod(H2i=>8D=R<GSscaFisqQr+zdI>lDV;Mh3
z9t`t5P|?dZV}0H(&gmtKQamC8|Ngj`G5y~R#fX{@n$~lQf4_}avi~o%==`luUy^)w
zZ750Bc`E^~q9=lqPUi;uH3|RsuaDgw<{5aZg?qA5xb-VgUEPovFLLaucgfMjJyV}H
z$R7O{vv1C84b|JTXUQ)A_UPkZxjE{56^~{*+uaCzc>ZX2@@{jp*a??<EwAn@`1r|j
zqG{aO>96*<l(;Ua&)8B>m*J=<u5bUj)Fn5aJxKdh$)#S`S9{li8gwj|kA_awcb(-L
zG1cR}v8Ah&PL#@#^C8AWh1+G__>!lb&9-UvC%v}H@~h?CNaC4naiuPK#_dC%^}f9Q
zpr*d-p$#1pmGkE{{JdH^so~-_+3*7)JfAhh_2TBR2X3vG;1;ht$@e(t?k`FG1^b^K
zsP)>yt-ayCwSlSYUsk4rUyv%O&KTvsyYd^VS-%zU$(Zr-*qwKZEaB&V_HpUOMJ)WA
zYgrdD`|+Wp-OJ{9_Aq{u2%Z!adtmA5<li<0;Zt<Y(vChjJBL9p&W3%@$GDBk;`)4F
zrE+xdpL%-Q+wxq@o*kt-7UpJ{v_@p-^XKuDJp6R@uaDTS-@iZ3W(e1d%i}0;tPNj&
z58AMamDtU`_?e2)<SF`{5vKW~LMLl4H%!o1OEmhw;%KO3*2U{GpoXx<O013JJ$v`^
zE;zPyWrL^iH~D3A%z6%rMcjzr-lw}H-|I}_!<MTxiCg1mG^~u=6_S5n{VC(52F|(d
z>+^Tb6ESf9>nvflC_d!=>0in7;(Tt)nBV;(YGh>R;^iq2ccWBxx}Nspy1wAh-(TWD
zCAM5kjUdN58O3;(iJ|Q~YC2cjb(#C!&~E+4Z<adotuLpy`L<6U0@L;D`!^oWc6ZvS
z<ZEsI_RGUhsqRb7#r3Cc4J%k%`1H}!xrgS)>@n#2-Tn2~=5t<;A3g2!(B%F2YfH1&
zmD9`49EN2;zjgC!JY!lbR|_pY?%El#HAAl6`17-k88eiYo>kiCd+*#Wqs&cQvqHPC
z<UaqK?e_cnHcuJ7CbvpEw*x(2HfJrF?>g(qM7z>^*Ju1QaaBGZ^gev|T-P5Ow@q%@
z{m6c?XWgHe_glBVFWz%~=0UW!lWOP{Cr+VYr@7k`zB$FNHv+Y%u8JQmIW_$w*VDf3
zcSAP(=U?%5^W1rJWJGi$4zT*%eRZ|!w+7?qt=rzV-%4(iYX8#pYP%}`N2g!6)aE=r
zZSP*{tSxhSUihNevL!Yx=k{@md=TegF=m6e5)8r(uUB(4TJ2)5uAEq_7XRm5zu2wq
z+T1&}TEEE_^A~(P^lHx?`NtjRM#>vjo|)b~{lGngoRwQ1eLA}N+~n@*ys6d+mvuyM
z_`mkO{JPt9=ZQf5bg5Sp0%IHB?~W+?_-VC6xZqdN*hSOoE=ZfvSo7zhmLp1PN^UM3
z(}i;6g$+N8Np`WtIxf_9n(<-BowA+(C%%noF?gx`UH!Yh@g@HHzZ?6f{wuk6|LL!X
zw@a03D(WYNT(owpJNG^N_p|q*PhX!gK&nZ-a1MYRI(_;yXaIyOVhXovWW-%DvA;%T
zyH^@KZ=J07Ha*g$-cH=0H+uW4x4(B7$#l#=&w3_&dmqP)Y_SKIe${yHso+hjf6yMW
z!9i=)`8qY#+9QjjBc^aq*3)^_>bn2MH*EoqG<Bza94z5|6ZDH`3!i<i^07!!E<ZhU
zgXR6%?t9<ldZ^!?J!#7<rKRUi-(UPDVdCTii{tkETB7t@O#IG{ikp$i$B&*~w(0DR
zFzwFicjsPY`@7L3*=LW<v<=*;xw<<Jx;L}RZHw8rr_SQ!g1wLe@8s3#`qL+K-Kt$J
zb}~>|=U1{zK##hf+UD9f|31oXp8x50poFLi+xzgoO&9axmwH~2vtM?9W9{pG^X_FE
zl%$;DasB@{P7>ZSD{|r0+5W|)u%u*;fK+dqQ^0fG8G8e+uRgQ&dYrW)s4pD7Z^M@)
zopNhoJ)K9bPYb4VKi~YE=kAu2WevSkuap%pR90&{E$$ZW&~R^Gf7aw34L^;ZnE!b6
zY`>T0iFJD!|1z3Zd|i6}MlfG@jpq7SjsH)UPtBOlKk2<o?Qzz<|5%s~zBvl7iO+<d
z`77sCP`T$+($1RBRUSug-we9KJz2kW@7a{<;DeWXb-lknfAn--MD>pOz6ZPAnKv%A
zXz8Bba8Dp-_05A%Pfz=#8?$duyprgR^6YO{wKtr<&|B51b?W$`1pk`Th0=BJ-k*8u
z9T(gjR}Ja-N<%un$BukTRbg^dzdF5f!HiRHlfU<WpYc~E`)beUx~G3#i+&j?Pt@PF
z-hJP>Gi|jC)wqzmJ)o=$>KP}w@}l*WD=I1^9D2Qj87FJ$d^##IZQ8UaAA?z?wO77<
z#xhxNQ$wPW+daE0hArKksX?*gtMzI=cHiEStNG`g><s(&!Euj|9)2t=VwUn~?T_dk
zC6#J_+&k>DS6|=npfKYv(!kr{cdRAPEsWmSHD=kzZustyD?Y>C)V4Qf&xS3@@+s8`
z&hyyC^#e;+rc?(fRa-@RnNNGd^n4C;M6u1J)6*B+Gti4KySBUF<EF*8y1J*oRgH_;
zW3eSOz&;}X!hD&phqCtSboL&wX<z$e=X_yteQka<i7n#bhW;*SLtjlb-Yt7y&-5}M
zuj{JrRU(;N-X7=Tx;IZieAjLDNo79w{Osg@*FAmvcu#!fsSj_9v^E;%mIg0Bx!(NU
zYmeh+LQ2#n!ifw}pXD`)6ZNl@9eeuC*EDC%l5Ml4b>2!$*4N>UyuR?T#3`9W3pR&b
zZ!J7|n|qeK>;;qe;V}p1{P`$+;{5)W*z|4M7xjD&Ofj9W_FJ5TWwSKAC`|CWA)mcr
zMMSXsp;eCM^HiO;3eT`#DEfOx4X4KKZc}yH#84&Iy>s5LQn)>v>uyQ1{=4t(<p%@x
z;^v$Z+EMZG<@e^)xvJZ)zdLY@TRJg(;lVa8b-nmS(UW<9UGA-_)YF{RJ-uGu)BM|O
z1#^+DCXzDB;33_TWayBt-@X@Doi5s?t=##;_hH6eF*SoHSE9Qktn`^35BAAc+THs$
z@%ZA_*H6tlWAZ+Di2RgzWbtd`x!+ecTm1Lk-+0?Sg=@M)&FpVA;enr0|6HGW(&M>_
zAx0&>W<?{YfsHyeoMiQs+gr2jxNP}{`MYjE4N2K`+lLdJK0W@<GF=h(!=%dG>!O~`
zBh@Mn_OjZR|9gK<`nH*&&U`)B{F?7ldI_84|8^?H!3MIz6$H-I_4Kx00SzRHH)>8)
zPF-0WK6_^gzoLYz{;Q``*snd`Rrv8yyzSmVMV-4hK5N#+?D?a7O>pJa)|fqOy!aje
zI4DimpT)=9Uv)EPqhz#R&Oz(+ze`q37YqNw^Ht)Dru$wUp07_HJ(cc@{`fq$E@q#_
zvzR<xF9iV(mZq17p@Y6lY9_KAS>;kSKcVc<z5wA?<Y61o5IJbjZ{s31v;n_6ckhDR
zG)-445|c%})epVeV<35W#<~4@sq6i7&CT0ZwHb(5E%BKCt~7mu?2WG5`Tv7{OL}aL
z&HMFLb<WnE%+Gc9HP+X<-;(%ws()g5dCQw%1%Vm-$BsE=`6($s={|5}gZ-I=?>txb
ztv(sEvt8hYaqyxzn@@b+FOLbuZrJ@o#cWQ`!%wRFv}SjUw}tK%HH+Ku;N`KH(@F_9
zHAHWoe|gsQ#rmz=-8-v(c1~xWuK&M>`}w1%?=K#FA$egvFC;}>Jp>JbEj14>>aQ+Q
zvSryU_pNo72E3?;bs)m7IDm>QXhF}T8g|9utE=JNPpt=odNz89o26|$D!QsyGjY9l
zuDNtndu2(tPAuD9wu!q<B<kl)Uus+Kxhtmr@AZVOv9oq(CGPyrZ*%GB*RT2B)>~_)
zOmX?HE5LCE(b7opifCD5vt6=##ZRRfc_oYEw$1w(mbKTX{L1N{)2vkQ-pFou7L2Zo
z(%Mw7B_~)b7_-Ns+%nns9-m&^oYk=({3f!C>%Tk8m@@zUa@hxuo<83vr5BgS`}J5o
z`>vv&lVanY<)Gd_0QEl8PbC9$5{earpN3*tF|)K*tzNyc>}`}?+(uTBX_x1^9tg<w
zWv$N5`n_g(x`eZPo52%}PVsM>?aCzl?Y`~&Hb-^+r^A)b761QNE?m7$^!K^yhpn;K
z3qNks`O|K(_4n)po9|DSIyb+czqshdbwLi6w8o7aC(N3B0@Q0-J>#uyOG3<(71KX-
zF;`ee7hhcz7w5zI;L+2KD&-;N+dhOO`|Oi>;}N_lF3vXj@X^!HHl5v|-}~gz(S1|(
zHpSn#)A4%G+Z{DOmn~)$+wu0u=97joduj@FjzU`q;lhw=&FzGEBjX!x(?Al+1KnfC
zG}YptF#Yz~W59U$(uCgJe61VR3$~q4Yx8?0emTEe>R7b4{pES99|diA`ZsaT&$ICY
zWzWq&Zo1w-<(8gxklZ6dgZb(D>`7aC-~TqRyAW=Er@=cl^~q~amZqEi$U*Sfwk4tF
z$qMn=pA|P)Nq6}EjEmXha!pp{_H3?h;X<z~-igJ!Hr#y$A0LHIVS3M)uNU{`_1%wk
zAKmogAI+Y$vn278>cW|Rs=s|M$?3(_y-7U0IPT46;~f=0lUw>d%+)}Jf@C|q@i}RS
zmzIV7Ueoxl1(Bx94(v<tO5A_B(@A&TsetZ%&p1GNz*BT9XdD;R_(mJo)zi}h_pc8u
ziaREDJKaFgcy8t0n-QM5^Lu$jc@t0Mn;ebszaWrvz3xzI_cs&E>}xe`vu1UBXmTEU
zb$5TrlGEM$u77wTzI1<MUd_af5*>5e*Z==eTlZl)xA>zZ@9ulqTr5nV55QYNGo*ID
z*k^M*v3|wDcRN>ZyWDAc*O{Y7*?q6dwbTc5gKt?ztQ7egbp5u^Hgi_?)1N;UOwtzD
zm#bW<@jfG{>rMODw&IVEF1~Io|Fpg6<D+n4hS_Z9dU0hTzb4hnY3NUUwdaS$18F|7
z2#GhHQ`v=g)O<WEJn=ke=4?hKtX~Kkf-uc@X%flYkTH?X^^<?|wqTz}S~{0RF8*ej
z>^3jOhXpp?sj+cU+eW9SUf|i3jTi;%GUt5;I~PR<+`myAeE67G*6%fo@+6!E7hhU>
zznUZQw*95adZ(w?_iftbJ*lK~XW&FVZKZYY2PVlYytn=wu)Ahw+<x)jQ6ExIIVptP
zKB&Ls+`foC5$k&$6b$$gRn5x8`~GG9y6&eo{cf>-x8w0D@rUN!)f|mlCE0sfrmG}B
z_E<Z8#jcm$({JCn+sH0FXJ<v^v`78-8tdX7x1OASVAo!+T^n;#`(pOwXl^_*Z(538
z+_vl13s;@~z@HH@@$Y@s-4FT&&+SmX!nyy$_Qs<hMgC}AklfGxRU}X!9BLDxp(Z?C
ze@ezoy`P5`#fx>m5Dz|xR{bQd3D_xg%nOpr&>D*tmX?wZz21`>U3b3_J7N0m&Xo-t
zC*87>mUty`C}5GFVeNCrH}`)kis}4_s*amf61nL~{E3r4FF5Sla9roo(((n;cO@k?
zX4%WP-eQ)zw*Go`Z}(l(A6fT#8>VmDTt7YQK+V3Yt#Q(g4hFvs&BTI=g(MZ@S(c^j
z`Q7w#U;Kg@XTJ+HX*+jTt+Z_`y{QMDV)FJ37rl8;WQ~o;iY3z4R|ERa{0h>GyJxwR
z+m6{TX3vf-6^XSc+4SQ5wmjE(dCa7h{e)e-r9}UJ*WC~QRq#(T&AhnboL$3&Lpo2)
zuTQE4H5nZY?!p3UioVV(*SLcCRu{e4u*9p2CgysHoYBf~3z~d(_Vx@D+nHteGoo{6
zD`i$sbX)$`BZYfw@5CKhyDrZ?E4$=;by5J*>@8@TDtT#Flm=$fYpIzj%kz~#Q6qN$
zjDMW%-ap>*#%G>)pQKlQ_bY3k+-v2f+_%lPo$s3=p%*Wa%g9*uTW{<0$FKKtGf3wi
zzyDXPzR`c{{m{E+OpOU;$%tu&&N)RnZ^{(oSuVHcUGc8^E4jw{?V+RH#g!^I?}_|)
zx-`*qleD;g-1EK#TeowsPi-mwDRgC{x2O5HsCQ<1aeG!bJ-GGe^wHDNKXsZ?4=nJX
zmiOja0Pl32a<|<NuQQfaSf-l)(?2EqUqU%CWk!#nkQ=CZ6AtaDq-80^KRKSfz3`!t
zl261cgMIz3YjuKJPG{IBUz+jp_!YC47HX+3uM96AKudd|)d7;)p;w$RD}Kk?{VNJH
zjW~W9S+*6m89cF*dRuhxrP8|{Ke^vldR$6<cu0TZge(0mHR5Y}e(el=xyXC-x9?}4
zaCbMo^t$T5_+y~6mVmu{g#9O{D7(JSi`mKo9BKN<4Ih!@b?esciIb_Gx9YEbS8lTX
z@ojVc(r(+fBqY6X+naO#>)~lk`Finu+vh0V_BVYVo%QiSoVZ@xJs<w(FOQYY^L+N`
z=vSwvNqKMnGNtRqnN7<(v~XRBUYw4!>5b}#dmQKM+`aMMIkqr<j!LN5j_5~?F9PBc
z=3lMaXgBj*{BxgV(?#Cr52$|Un0HftiHF4RIU*%rzDs@4hP1+=3oB-X?K{zbeA~r+
ziQksFCmOYRO1^#Us&hy+@OGc9d+ua`Pe)5${Lfp*gH-52CeO9A7KrV;>VP_M!}|5_
zqGL->9(ckPZIEa$`DT{n?>kpMrM`*q+&Z%`geTdcKxOZYX>QXK*X;RU{Py?sm=nC_
zHLbRW%)*oOc3oK;rwf|!xDX$<Z2SAivm_MUekW<W#g+W4shKfx;dM6!fir%{W#87g
zYwKc9?_bUnSP*XzJLmn1fJJd#x3oW$ZDR{ly&Rq~pL4?UNj5txDnB)-Ygy0QxLqmS
z%e+iU=-=Oepw8Zo2UjnP#q4u2bw9ZAZ0G6eQ)eos?AN(_L-yjjm?eMoZZO_2*>Ygf
z-G4EAZ1&qvTl4i$N_Tyonw;O4hsXC;sR;Sn&SiTGnk`i@Fg^C};0nvmGoh387YpjJ
ziKzB1(Yl=JoFn|{XvvAmJ)I8F%GM(w;KCMX-GtSRLPl8&Q2QiJzv6-(UrL#9xjlx@
z+uQq7MDPJnrQwsfqL9ORx4Uv|m5K9>FHe@=`mX!4|MmL+Qu`R2Cr{Hef=>r*nw@tj
zp}Vf|nt!+Hq=rfdFZ*v_n^<?4xLx94X}Z~t)Brj2rJCd5lS1vAho7FF*d|jkum9NZ
zop~{PeuN#J{C4@Qvokk(&7J;tYNf{ej6<uw2<XK>+BIosMdAD6<J~i@cUElF+3pfh
zEg`PIKI=U%_bh4NUpAKm+U?uCUmg=XUi~QQyX=qH4s~(;@5kFDbvMP|yVo;4Soz1t
z(|m0UwymC<qYg@cjVlcx(`C!z_w~4J*7zDd<*m-q8FTu4{CwSVC;t*QjrU>y?c^&0
zPj{9{Su0p`OhN4w^cuv%0yNz4j0Mz#3zv@b`F~+W$;)Dymg`;pANS^aAN>*;Sj)a#
z(PjR{>3Z+q?d3hL{pf_<)k7`Z!umc3Lhh7`iyQ6wZEw9jPRL+;-E8g|>$l|U_f0eb
zm41jZ>}OoNKI=%XntQ<JM85vxM@OI4aGbKv-uP^ZvJO}6&YF{1r`7vsdM)bOED9Py
z+>l$eYvX}W${*yuJo}VdyOHg6ms#P@Nmr&VSt5OVPANmL?c9#lC!AfMzo@-sdvRXO
z9D{c^9=F_9mi_oVw(r*bcOLr!7Hruqqr=s9wLY=dX5O3jD_S4hcYteMJLq)D%BlK?
zJA-B!h%Gc)SCiN;d|Y$KelJf?HTSInw^am-XTFd;xa7h9>6}i`)W@Of?!yae?qQaM
zicd~oZ5O|MUgpq(&lz1Cmz3KdsgG3_y_|k2b@NdJrOnIZcD*<h)VJ|Uvd@7HAt}k<
z|7&M!3aqSk{{J^tFJa$~*Lpnt-~juU0Ix2VNr|ay*Q-Z*1y$x5i;3x4f4lLrWiNZy
z?gKUx7RU9OK0UPDLM%Vs`rxh*FUxC&7hL6xL+9kKI+Xld<>My3Wb^ye?nazDdV1F5
zi5xHaA3i#|GPv5lqKI8wU+=13LH@_bpPu%{=&w23b@<emST5;J!XJ~eEX3439z9*i
z68}f*09$=m!lTqFzmx7v3{19JHFK%U?TVW9N=4UXyB`M{hD1Y#!Jz9u(jI+!nsun_
z`+Bb>-#OKK-?2RQS_qq;o20x9JYfr3AA>r9yJ7ow^9HHuXE@Emf^M&8bN=Zp@kMH?
z-g5K!BZvNfU;54V(<iNp&v)~FEXzO6y?dSU%#w>st(BX_@24{@^Zb4H%5D1#z518l
zG40us|8Bnu<LC8Dy}#b(U}=hMM$W8DK$F-`u6uJ%eSh>wb<;bA$}=Gcx!%~@DaFmR
zeX%&_ev#I^)6-*pfA6UHDD&Y+mvC7`{HDg!)17lOWA@xI$^P(ZBZup*JtZbGtBuS0
z?fknB7`HcVJaCum|I^1OF8Ai1u;94AheyYkFNEdH&uLYAznjXfQLyi>OJr)h+T5vO
z<+@E6)QCw~1<kQ(3u`o+(x!G)8LFc6u%2|R1W%n{Hb<5P@7u6HW$C}9sk!kRlUfg!
z1s?<zZCip2dlp`}D3WX%cDUllwEE2ZvqP?@-aI8&A$!v>_mb=MZ66Bs;w5Aa4Xb|Z
zecSrr^pw_y#ov76l$R>}&UqcS*9??F5$Vn6q|eUy1v3P{3ouPyApP}Gv`qU}OFgeE
z-l6?gnudaW;bM>GfAovlvw`V%X>4vyby}^axPJEL@`LFI+E0t0j!NG%Ph0e6e`Z%>
z>}KQX`meeE>`1v;qs93?<=BIh&5U!7fB*UT=M@3hLps(%ee9E%KAhisaK{Bhsg0W~
zjVE%?aR2^m(X!yG+T;x;DSA8Ir)Wq_o8;qi5EOI=LZHpR&BmWx6M}AtXDxy^<W33(
zgVz^fjG|t=Aq5&kJy_ALSy)(jd6rvC@6;>bG8dLgIA2_RY3bQCj{G}6XMVHwyp(!!
z^4<dLCHk@3WOn37E$P<ljox1L^U$J%s-mJV-(p{B9S;AU$6(gKCsTJm3(JeGf*dTy
z@<@Y#X{Y`&9Sz;vE-<4fShtleQ2%!KpB)vJH>SAC8H*Zx{B+$Td+E8;ox6he?aeP}
zSS}SkQLJOZJcE@d>ejQzS>>HP+Fd%;^v>+1CCOo5*Tt-<5R-X)JkNe3|Mw%i9-d_W
z7jwX7+j)L{*;}pd-&nPT+>Ytm_g-&He0e0!`|ep*p6(XQ+YxKO+v&U46|W8InSW4j
zb4iL~Yvdw>{h&erWjx8y6<W|9{fncaoZx9XoarsYM?c=m%Id^NFY#>`oMf}PZoCe>
z?dx@ZL!DvmZN&)NFPvg$rgy#k|5v`Y;8wau?59^tl2(T|&syrevq5v`zp6NXxAOb#
zYb}3Ny=O}J+R-r6US59bvRB}6lSYIaSK6(=Osv)XSIQ2)Sz2Gv;4Q8eAMwgi?CYn4
zk>z@EGN%q*us3m#Gmp%&iUuvvlJhBf_UNeL_l8aS+y0pJR~2S)g<a~c4ajQk6*o&t
zf9$6hE3xgYUxlqTi}krT&kv>LN6Lw7)OVL1T<p28BFW`|ft=5mqqZOSMO0dFz5DsH
z!q_;<|B%c&-Gj!zn|^%U)2|_}mvX20az&NVwe9bIUr)^u>Cs!`c4p(PAiZRp&zBPC
z);fs12904K6on4Ka{N>>a42soTza8<wGen!D9+><c;m(mP%RsLu%iB?)m=~@EstwO
z(yxW*>(_`}PQP?i?Oc(Yr})f+@A9VHud^065Es*Xa=!F?{gnw^N6xd$O=mfCom=cd
zP~ARoXRH{xGq&uGet^$&+m?hXk2SW(j&_Gf>a5!J>f<@h-bs0HV%k3{eGzVSowdeB
zprbnJ&CZI*N)?W?wsULd$E4g}bSYd=-)F~$4)NP+)_Sorw@fPj?=fHh`B2xJ?cd54
zuUXs4Ev)7jCg1nu(c^O&=6`IDar3MH))I0{d;8HM;}hTGt39?=7Y){@yCfdH$YE1!
zFhRaNeadE~lrXt2hve;F?2a#9vUcKWF}36f+Zh{;+@8(+>lkxl!}+FH?x5ZYQ!Jz|
z26ZagHt1h*A)?j*t?CF)NLJliyDIVMm-ajEi(|j-|Fkg3=l?r)=?%7JcYCJj?c&+C
zWx<y==^YI#qxRfL_<wxrLx-MkE&r4_!Htt^S0uIwbFdim!&9R{_{??H90$JyT)xZ!
zUQl`?S?ud3%lost#m{s9Dt*69=y>q&!smI?s&^xzdPO*c?q2P>ZggnlN*^Kpd(HPY
z<Ze6q?9|yCQJK1~d$w35U;UV%=M`tW&D~R4P5fMZrN*4oz15HJe7w)-2VMvEz4*}o
zmR*m2Ht*hGY8~l+XwS5L(sDnKKPY+E(b$o=#;gCyahoX*j{dGUxaVCOWh5qexa;#~
zlN6t01~(5ylo_~8G|=&t^p16t)dx2Qdmw{VpwVImlyTdlBUeBTJ}d)MSAH)%wzM-S
zBI3kxwQn>2s=mHq=zVNW);@42!{pM_FSojP{NEmA`1kj;w}-8(<-%G{`%Kl__2t>~
z#Tf^g^b%fOUt@UVf966{ft9v9@7GP{o}r(=U-oeFq7<|hGE&~&-v4A}!GpM7O{XUB
zsOVhn>0FmRGwzCaXgPD`nUs?&^q!T*$=!W^?r3+tqt*R;SMPW8swZz)944f1*KD^T
zd*k76x8B~UNSr+>>&;qY#+a@>MLFi;`fnnpN%eG2Y!}+I^TLnzY4d;He0Y0S?vH)l
zs&%3-y5|41R$QO?WlqrQ?j02qRnM$iy!%1$gvP_SzK7q4>VM4p^n>1+0F^V<ksm9b
z7Az5BJ)F`%XWARDtRp8<u1@`|^mkKBqUOn#7VuKN3TXY~w2y=5!rX)yP4K|@iLAZ=
z`~j?5+ytt0P8waYSQ75DKkc-^w7)NnYu>xxt$3iTzDMGtv@0m<t$&=qVAIxyH`&WB
zPMmV2`@J#a<wp+_H~imx$JF74%>ygX1Anql3N?f3>oi1z+K2P^;a=(dJ%U%SUM(ms
zb<N7sTF-vCh3B@rta0p&S8`4Ue>eQn^Vt(2#<F1Ves1f}O&bi)H+PHwKgIHC#>qoU
zA#%OtN1Z+OV)o>m><a$2bB=f5k?!{k^lhSTH!jX&-xikJn%iB*cjH|L@6xy~o|SL2
z9v`2Zzw-IZWB>B|h2`#OpPVkuEc+q3f3D4|rw4Zw=*5M}nb@eusW1C8XI{(1n=`gf
zKRsjb)5;qqTAr16uN!@Pu+T-`_0S2MUDu~yZ!?@W%}4xO<&Wp-T1;+k>x7+UL4)y4
zJ3Fx@Wuim6)98v}%fi2lFD<S7EwJs*cg}yWx2J!+FzsrE)fe}!l1lA=VsEV9FP)on
zZr=$3wb&Uoi3XC!W&igZO=np5xk288>6x*T7pR{sgXkw~UR?WANJi!iYtNqAqg|p^
zxyxs-+y~mqamVXh!Ox;Kn&LZkk5*VmGp`oXeD$;@oVD^yO5)37avkN59;JT0C{Xh4
z`=__NPj8wUlI*i*$@eA29}g{x+ZN_s8n@=vor>P+;{9!l7cGu_Ir(bh&)0dpa(7-I
zJ$m%*?z<nI%d_RaetK>o|HtaYS%p`vi`MMrWPINL_-&5_v*6(;J+*mS_fGT#shqJ^
zPZA87!RqU$C9c1zWvBkJx!yChS3W(Y6c-V-DZJlfYgS4`)uz9qn;-K}1$DR+qOg>@
z$MiPL(w-t?XbLJUK)oEak%`~hmo|cy6(0Px{MfpiGs=3zFZ;_MYPg)yWq4hkz2J21
z#ELUt^}7lZEtc8#A8Nb#|G%ZT^|2@1-p<D^O?>%Q{>3rz%yLHSXN>p%vWXka7YH<B
zYD`E&YJQ%TEx&VcacsmA?%nPQ{WCj2yB$PN1nQeTcfMe6!SK5{@rHrW*GI9E-g3r~
z=eA#Y^gggGW{-~Ti{4G&-1gaS-Y1ej_v7<Nt$~IijO|tIdht4|baz&4%r8BpJu|Vw
zOqFG->{_q+g3tX6A8xd`H~DmzyD;;Pil64;-e=11HEzvhKh|5dQ$|NgZT(I$!_;)1
zMM*cRD>QYTCK|k-*Wv7O(({d;ZS(eiH*KM|t8IyzCr{m)b>g}Nf0(-3LLLA0dOfaT
zM)&=;g4+jopfgIKMYRm*y>QTYBWTeH+SZAUk6Imjy@L<>cq$(PweF`Hl$3G&)mwZ~
z@8J57NAK6~Yd-$>zqcTGnbV8S+cMr6t^ad1)O%V1<6*9ko16aZ0=My3H=8rQKlk`k
z>qkLBRb>H=b$0Oav;?cf@0@RbpL(b0a?9{w$O7rFL03cjL9^76LJ6Bzh>Nj)irKV2
z=(*~`KRoy2?u7MU_is6{_W11X>64w+CtjF!;OObTTe2JSubvaXE&K7DWxY+M0SBw@
z;)~j*b8~I(-ngF}vvWbkI|l=?iiqN$pM2f9YbKNhbXIw;)d}vK&G9_?wP56|#Scop
zZD?$nSZS}#s4vTR{1J=3&aJf%m7cZ!RTN&ueRxrvod37S&yO!k%G!PFWErTxZcvXU
zwXSfDKlr9a$SA9`uWz1!nVFgJvFq2%odTZc^#xpC?Gqh+yJf+zMK?A&Tkn)+1}&X7
zR_)~gjc;U0XvsSD_xF|D6zgo?u6MD!=;p*v0+&veO*}rEx%uwh9UDMfnx1aIe{TNk
zcl)n|wymGMo_(I2puy+oj}On00d<-lyh4oWKGT}Kvt8f}&oQ^XJH*@`{f#l#i<@&=
z`DC(Mw`<y*pyx+_P5I)k`Bc`oLRVR)OxG@E&k_Ekix=*11hsZ`Ud!&N*tp`;fy?bp
z6C$m(T4z)wuH5&or|4PIqlJ6#{Yc)yF0OyS^j723lZH08Qk4SrvsS);=MYh77H1>V
z_ng1|(a|jh%z~S>Y=!*a@IUyPF+-zE&(8N-spYBLv!`lEs3j?N`7K=OUK=Y3>aEXU
zhYYiUwp}qJb)pi=oM01JpcTboR~+W<wnSM>CVtGv1ClCJS4z93Pf&@S^(Om4c7FHq
zyS&^lu9wFw=r=e1b^27r6K?SS9;r9~H<+@=zmH|OqYG(qW%f(n_EQjuv4xkb2V<a1
zCeFx}ERMVN%c^qibns%&l&!s<E3H`H=-M{_ek22Gu3AjEUODmTY4)RaF?(8gS{Kx=
zknP*rr~Js(`hIqKOYUajX|lY>R<>PSG4b0zIUQfVC#7*uN|L=NAKqE<vvIcQKS9x+
z9hW}G?AdT)`Y*l9U0u`e>BVV3+c_m+-n5>R62~{M++bVnG0{MOx%;DwwRJm^`-RQY
z)jkz3_$9?ZttjX2MvJ<2MsF;=-Nk}tv}U|buTeEk0~MnNzo9*P@RId!Z2!F$K*l@{
zzG;E27XYn;T(hFls=T;mS6nJ6>#x%9%Due}G-b2wLDo{i#Py!J%M*@%xxJ`uXNl&8
zlmiEom&Q%u)?P3_NXSH+`-T5B(9*rL^Ut&TJifM;?YI3ZU#3ORnD<X-IRlyld^E!(
zm8tQ;D@3zknUol-_W5+Z<0aQR7AL%Kyrnlowx_r1qgL0&m0yLL|1D(vw4-y<?TF6b
zNisVsDql5-b46zb-o1M2=;^L44i(=$KP}B!!Bai!o5!Q22Zi`@gLZ8+_y7IrmUdCS
zPt*pHIGwrI?S)l7eo84a5ZAxHBl2-Q)183pS3B+9el(mEzrW{Yw@unagKg=2np+bi
z*3LM5^=QFq|6r9f-tNjD3O4Eg6MTKJ`&0bNmXoWr+e^PO+Hxv?KJ!muNto!w<D4u^
zjS1h-`&Q7dKA}_^@$t}tV~_?{S!{&i-)DOdzu55hnB#SZJ%R<B=am%NZo8zz^}>Hz
z-x3wjtj)uRY`^U_`5By^GulsOIV0XJF1<yNgC(3DsZS7C%&$0O=L`3}B_CFZub$*E
z`ASt{(!+}^;d*frnWmE@ZWL+B>C5r9q}Pgw>sPaVSJ<m7dpmo3@z12&Yvyj=YMoTP
z)A~}j_t#%{>XPKe_4(B*3Lk#>vhSjm{{Lr5otw6a<=ED-`0TkOFWok8wE*v-SBG=X
z+&az5)7^G6KC+?foS4O2>qQ<jp1L3Ycpy%HURTfD%Tup4CuSZ$m(icNsnvDg8qe-9
z6`pDxl^zELff@2-<?q(k)>g(VJ4{4hT&vkR3)C^yyIfEp;cAxp;Y*dQK1=L!j_Ff(
z=bM~n`ZalJ<l={)T3rv8q^C%{`M>3Y)Q4H@F~52B671sl-dfP>pfCfmj^|*^(Ho`8
zGj`5-zv4o7)y+yXkIt%(tNYaZXY#zh{9S|fTkYq|Keqi1eZHe2a`s;Cea-t~_Q|}r
zUDq*vF7LLOJvU6O6SL37?fv&{+S9_1Me%!ncT{j5+B(mAXT{HLY0@@+>54n|v~-J4
z%hTDt&+Yw>mu*XxCNEB?;WE0g{O=j==+}u;s{<w)obGG+Sd#Mn*shOKX3wKvo!ocj
zK-cHZFT^7cZ8*Nhj^}C^s2jSOA8n8Xvg{$;z<MEQX8~-#qjuJUb9ZGHu7mB6O|p9G
zU0Q0o{-@L1*?u=7JahH!Dg+Drc5k@*-f^$Sbl&Q-yBq$61Z=O`s*)h4^Wba3v3LLf
ze91bHCig*Blj&LT+wXNbpqbUZJ?3Jk0-#f?Pl_~8fGTJg&b2p|yX-v@Ds$9&xyc5-
zsf*%PRjKWJ;lJ^}lia_Z*JpN5*HwS~V_VxizBOL_pcO#X_Tu`5&4nA*Y<==5)qU>P
zNqKLsPTsfTm5lGzQ(SNEi$CtGPP)4zqT-!{K;5Hkd!@K-kJp`G5ZP){`0~&b?}CGp
zPj9STP`*OEx7qi}zSD(^_Q{;)Z!cur{H|h7k>B!Jt2Vq0nWU8XBx6SV_g_9<2R7U?
zSo5ztX2JZ2^YyP?W?^bPAY%ZTlK_>63#<?B2qH2VPfybYFOm=x7iSL-57$dt@%J*L
zw07zI^rcr0AHK-9nmIqD<@9C7>j(cmm|eUK6sB7%48;vn&F{82F4}flT(Z0I>!g~*
z8X4i_3vLPmKJv-O4{d}7=w)AbclPz7=5tLSY4pUJH_i0)^;Z_x7h|nEk*j$uI+E{3
z)QiJMy+dNPD$lrNzIk`RnR8M_<+BCmv#OUgednuW-&yf-rC-ba2_dhKZadi5J^iie
zv<;a>j~+d3HZpga_Ud|8rKxI=puUfejexY$QmNv*jX%#>7i`-oSKrU~?9tQaWX-DM
zer#&7e&3!RSg$P+H}{{%o15<2cFUx`_1*KYJ5S0eY{~q$r*qdiy1dlPIpg<)>Dkf6
z4{es-N@3~Y@wQdtmYCS)qaeVMW{)d-8-=Zi;hYi?$P8Oj3TXwwmS@fVl)CJXs&=*Q
z&yCG<b;I=PgkI0v$DDodCvV*6U!pOO|95roEc@|g4V(OS*N2PyZ!D>KCwgUn(2N4`
z=3BKSw%x~T(wOi5_;lj!fi6MD&wJlLbXM}6>7Xz}uH?8A7i5{*gDeB#OVg!x=dl<I
zTfS0V_e|s7*`h<$=`lMiHtrYb>SOd|@e!5obJc!%>|5xQkGbuuK5yK3h5y}NyO>=D
zHdcF=bWD$G;}+MyogMSRFU`dNqU!hO^Jcg0PLA2>QlWE`|J(9QJ1P=u4j*J<th9`%
zG-&Le?k-doTR3-m<3h#|{%Z=`?fbi@)!MQP-k;oZ;+;fAIa|?7rH9As*~(oO?AYG&
zCfmm9clE=PZyP4H`G6YmMs0J~r#tRaO86)FzV+`@)|B{grtYT1`L_>u+MUQ3%WP(4
zYD_RohOV{r*th5Zzu!%f=Wg6zsfQT0CtN~2Iej%wXW=og%K5i%`#%Wp&-k+AyZq}9
zXC9?S{QY5j<I}nI8;ug|wk<!?TzbvsNbHREHOnej*!*U$3H@GNbG3l^dG{yw59@C|
zopU}q?%;9x_To(C;*XE6p51L5yJ1SQ>h<{ZtUTVXW@Z16?h~uo`kw7Uo#%m?y$OtZ
zPoGPJ#<-Tbm+LH906CRlnY5VU?y|R0Ya%u_-La3A07Vx6-4){3n{4--*q6G|e!6~S
zZg4gKrtM#N-gOs0d6XLaEN|c1h1*4EnXTN%tz4ub6xX|OQpw8Ojk#C(luP4GDqcn?
zd@50K3~-z#^zqY!xcoUQRv)OZx>@w`lkD=h5tC=}9G~N(7gs0#s`Hng<|#Mcx6ZN_
z7t4hhV;5RA9ToUq7SFP+Eit@$%D0^=2{BPNf_KIG`y9WnKkl(s@6fe3Da@RQgC@I9
znbYJPrzLK%M(j-Ot>U;!jYnPGAN!BmskheGtzqjCFaT|ltA;Hr)`^#rl498`q8QII
zxfO3a?CflFY0%P{#APD4rY9yZ{qrMY4qNv0JMGi|e-shBzx$ZOkrQ`*ozJcK)ENHZ
z*yHYx_g2mRxaeztVf$a3KL;!2-t|sNC~7O*;wNMhD}RpJ;q%uAOf^?OW!K#NonBLP
zG5Y=Avt<z%Yr|^hb4rBu)W7-X9h>^Y>Sxu1{#)mrZa-Tsop(TWkzbF4f`RU?WJOl!
zE=;du-BVH@J<VFCo4Vn<W3Koa&}zR4pH$}M<vh0Z*NgkJTKIy!iBzHycwpSWhgn?T
zU-pYrWVNzXyY$1WkM&)5efgoZ>!HN8`J9KRb?L_JiMaOm$)nb*Rgsm4+T9yZ{AGPA
zf874jk}Z+_D!(2Sgc|1Ee`-A`?~IUllJXR$-#tCEy!L)y?0r2zyzi@=eE1fZGI!-|
z)%xE`jvQKjRNn9GyMo+rhbFEou{~I{OyA$~*wUlidA;{eyq-DZK+;76pJN-qwd*c}
zYgVj3=X>)$6l!z*|0OQbrJu(-&o|2Czl@~bPWOsjiB)Ss6BKEB@M5i4fN-4c)Qd0C
zTyNLn{9E4n!Yt{BKTgDdcz3t|(Zb#L59snoOm=#<o&R>?$>#Ns{~nh=bZq+no~^g@
z4`k1(+;O~4E^pJ-zLVCfbx%$Bzqg$|Jo)3KITxRJ6t#VPDXwhv>=OTn$op(Ddom)l
zQ_U`k$(?)3y;xcEWWxXUwU!Q7YHS&cC$11y7U1wXUXi?z2NGKb(TQ7=o0^*|d9AYc
z8-nW>*Scy+as6qmj1jZn_H4C0cOz2D<f-eon@s<^fB&6YlC0=DbA{Zxw(|=_J|<m%
z)EqkVVU*LxeJR}AuIIIfHjB#K+VXjGnfsp7Yr>4r`?ddC&hraRIk@?EbK%0ZlKrZ0
z9zA->o)@;m@&xnEuUl<ywRVc%w^`}N<E?n?Qv1d&i9BLACtWM&sf%^2+T^OX(3<n%
z+T9N?JXqzPl5H}JZPwK9i@Cjh)#Tq_e8}y)_;$;pgp!MExewR9)3k2h?ter}=-I=R
zcpYE9IRzp-zyEGH^jqhz#BU#05m5W`40PgLQZfFSg3~?@HEvja1KRK(5y)Kn?imMS
zOS`mro=Jn$bhX13^#@m-zPjqCkzDE8R*Omhv>qL<Yb)u`SNPg1ul(cV><usH9j$n@
z?rKEI{j{JH2LFCOkXlpqMlxdkAL*R!|D~kPZWq=3%x?YmXP<}U%xdN(8Ip3MpV#L<
zjIZr~bZP0$`CY3$f8R|5HA}xdc_H4HS`#vxrKO(#$5NGqeK9)%I3yYqq!803n+4Tk
z^Y(mnlXHCh>Xp|v!?sz~90$)FT${o_U4PrpgAv7-1jSnS#q0s^zwsB@{U$}nv+VJs
z)aWv%TkW?64I~a6J^d}qevJqB(bLD3KK!fH&RKnv?KGSBjqRQ<k6jbreOT)`!~1sE
zls!ci)0N`lJntUeZa;ZTd-wG0J8L%_$W`YHj|(`$t-V3td-{ZR9JV_baj$-Q$>PS5
z->cuPe%RzXO;@L>m2*nZI^ml6HF1vv4Fgv0nYLYQd0M}x>p_DEl^z+^!<Qz^&CzgA
zO5HcFVW;gS!P&;b7jM`aUJSl>Z;i`A1HsMvlRlNEg1e};@R92?jx9BUK4Rd5JG8UF
zTdF}b&3DcUA{}1gz3)uhiRm9-yvaT|(b#%p`uVdro`2PSvh)1oIThJ<Ww!saBkug?
z-&1tm=uY)@tDs{B|Gqt7ulTl6^u~`%NiGrpq#l<2=aX-L|G4yc{$q9JK%?ze=MS%Y
zB*?kAb*akl7s`iMOuXrG_R`Xr15@r5F#pQtQU4rspe5`4cPq~WH5)4o+m4I966au1
zJBV1(DwJHm<sge!R+d)fo2LnZ`ft>Q^_}+3urIoD`ddx?nv+FGZpnyG7P;B4eeNK5
zG<(l9v95QGac?B=96s8eJ6S)@i~HEG$<-gz*0s6s*-(;PraP(PX3$Hf+s5q`2Y=pL
zS7tNs&GDlL>>u6T`142HmT%uQ9<_e-b?262GFuVB!0`Wzr;B6A(@h$e=66qj{{8r(
zjr&<Q+H!WKB=(<XJN32e#pz3}g?nDB%#pSgeR-(G`}~Ee%OAeFDp$PX`-hGO&Ic<`
z^|+puXs+ek)%x>r*XD_F;mbH?xPSjy%=me`hB(X9`7;W%jHXrjh{epS4Ay=GinRnO
z_=p!#8O<!m0G!csrt9ze!dLRxCDTIb`@P<~b@!dOwW}nF-21PV6!*)1huue|d%M4j
z>g;@68g$GcukwbJzuhmc8v7lR*Ji!f5PJ4Mv%c!tnZp;BPip!3>r(xL$^JH5*4zJH
zawMVLT<hJgZtfY}+~S{}C*Q6Kl4Gdo6*Ne`#lh0F@;D;0q{QYf(vFB&D_*7_7!h&e
zz4p5ukNN%uf0x+G@iXaP3IDf4&uTcP>%UL4{<uPX^&$tocs{e#iN}3f?i};7nWE7-
zT|DT|g*%r!j~q<Vi<@IryTM3VK<;_)$6u1wu6u55yHWfxDfE5M1R>3<5uVY?a^9b0
z1@+}P^iDV>7AyY~d3Sc{>3+8(r(ZQ!i+=p%aY#?(i<o9<OoR&8WWD73i+ye<NFR#+
zu5nQ>bFQTDB)w$Mx3;I^?}OUpXa80G{m$>})AvT#zWsSn(@xO&5)Y5w>}sgAR&L{#
zIM)0zuEU}}%Si|{*z4(>HgT#=;zaO_#mj?`-2;N3Qu&mgbSG>IfR=`^!V#r0uK48i
zlM}10n>IN6D{1_QlKJ+hZC}U5BC{{kSA3}yi@a^7T=X+4XLq`)xY71|`+fb7-1+^%
ziY0$1XPMvqaFsJ#t@k&sJ-N8`_x^t+c6PgenCZot%;QT^GrV!>kTPiJY}BTfoX?&s
z*&nR3J^$XD>DlL}r$M8r39k$gi+5W%#a``eyz9RvcK0;<v?RB>nxztx^>-C(bG?!{
zct&Z3O=tIX{-V1(D}FxM@_UN=-VIE@y(h&iW%|v&C*bDAp2K@3WA^O$UiEP68ipI<
z><|A=I%{)PLRjDI?4*i~c_mI>wQ}#<JEyC9PGZ@ievQ|Cbywk{ZCv)BOP@S?s&2;@
zv*(ZM8S(!eoKGJ;{ddDa=H0b@*F3gxdutw33Ou&YT~aNwW>(V<k&juQ+%w;^Zn|3`
zQX=}$BikzL_p1|sJ#Byg{LS(9Rx9(|m0b;%HwtIutZ)0g_N$ky#4+uc6C+A>CUHv~
z-xIQD+OFt{2H%hFIBc%Z{I=FMCI0cm(C~FG(i=-s772hBMx2R3%45fnPIgKtTXZo)
z%_k(}O3Q}Ln`aw<`c=@1t)#r%pJSDN*VotALG8w8Ei1+RPR;UH{`BMZ2cF-Y-zw@a
zI~;lP>o3Ps_Ia+ae-@O;+BPJq^JmCi6gTX*`DLZF?XOf{-v7@38)hC?J@aSo{R68m
zRIYe6S%v3u{PbxL4)YtI_--it^6Yu{i%qOC2TXcC%l=-Ip#5Nl?SA>1X6_m1udPwM
z&B@ZV61jM^+|+tY&wJ*{KZ)%fHHP^@+5LqdHyNH6exH)P#X@n3Ri)SBxEo6UFaO@N
zqoVRrgZiggt2ZwH$RV!ZQ(p8papL=K-c;#?z+^?&Jv|-byTy1f%|7?B?#844$tQ!3
za0~1Idu~*?Y@wf^etqAEgEsAJcU-Ggm-D}K_~>cd!hapFZdnWE3bhp1PG28=Xpw(Y
zw9*cq!u$`8b=NCnUaVAD9JH%#cVXEN8Lu|)K=YXXP5+8muDT{v@6<iM?b1vUgEh~5
zYT}jt{Av=sBHlJ*A<K`iY|fXTG(Nf~)ch?gu{0q<q(>)l+A<bxK?A+#eM*bJH8r*r
zp7(W*yqz)2G}++i);9?p|7G`7sRVIIG&(5ESjh2HX@?Twdak9$koT*s_y+Uyg6{(N
zJ1=-|UU~7~Mx$jG-||f6UR<E2D0cf@d~dYehA@>gll%8Ky_jp?KKuK65zfOEmvZGc
z&2m#cGktm<s5>_Ked4l<Q)iumOxhm2v_f**&o_6f5;lLkvv0BM?RT5kMuYR;C(!1d
zlLkKzv2c2uADg#1L!wf;TJK4B!y}*UjVf^wyEX;~?b_HfYl~)YRidE2zgl(C$3q?~
z^%{~3g>2aO?5y~?;0FJHpKnKht4eR&n)TY_NcUW&xFBi&&vkuQr)g!bf7e%dwEAVC
zMt=U$_sw^AR_x3!XApC{`Cu(mx$2=-!5fmt>f1_P7v(Lu_u#Lg)Gw)>36>Y<@xDLC
zpcnV%Rcu0J;_?oWSeey#1h4Ip7Hr-vXe6_+y}sUHk2RCP4H>rGR_PX3s~`P1;5kRq
zuKV!e%p2BoateBS{yT8onZ4(tp~Xg~pSzxzgdNg)&M-}m>rmGg4!wjp+imUQPtWJC
zdpdu<PNIbOWaiN8t%+aDW>g3=f!3y{={fD=SjR!I&Cg{f*V`&J{Y>Y}IE&18tB&sb
z9QVOzpSaZie8rpI@kgHg(sih=6v|2x{&4T^>JPWu--vL2eiN)cA>+F+3)??A`_dot
zloH=<nA`rhb+OR1*Z%8|E}tLu_|Z|ZPqt@7W|Sm;YW0**OI~3%KV6^wn)j#HkDd+;
zH~ka@eE1aOS&SvaWacjwO}-z@roCyswvUdkuAq#}n)05gmKsBUFYDEbfe+NLR$4wi
zbJW`-(&*1>;T<(6Gft}Z?-u$rJNrY$CY4x;g|6xw9^Z-E@%0C2yX-ab??-+=`Y9ZC
zBWPpll4IQ}aWc0|Zd_Mzz4vCl#sluZJ^PREcyw{^K8?KGM~|K^XEci_w&E}=-B)$V
zb^4EaD$ORzy3A`W?$rs&nbyUuhzPq?9C;|X;p*-K8*lM;p8Z{^!rja%9=UHrnPYQe
z%#0u3SP$-XwibWQ8MJMs;?GBnb7bsVTN+h<l(8#TcvxLqu0BEh;rxuh(>GozFF&CA
zUL(#%YMsl$J@rP6?5oZfb0$x&?YQ1FQI+R+j&ath6Z=XM54l1z+fJmWWsh#2AM8*k
zt~7P<$uwbC9H7%mS9Z=;Mw|oo!b@B-E5=BD-LpfU>G9l`PL&<}`r+~h-d_vnb!guG
zZ}Q7JJK&Xk*Vg*jWgHU6Rz070(6RmX2J26Jf6lyg?`ruu^{TnR3^V@ub%shw`~L9R
z{eO}gvuld~zJERqOJyH3r5$9N*VmKvdHtHD2WBs3diMCkO~Jd$0%z0^Gfz2gC&WK5
z-n-vhbHo03TcpMHw>@_%Shu*RTl{+0-52sw3jLv{SGE<|Y&&0T@@VNRn``On4wv7Z
zKHBZPn*ZO-H|^s6Ey<T#eS?m8PwrKUyA-{oLp<z7teAee>+XWU3AZDf*}t<rd}{pe
zLx%iRoo~E#6*hNe_wyG!m(}w6zrSjf@uS`B<=UBD9*Gxq`AUP%ine7pn}6EP&D%FI
zG4t4=Hszffo$vbH18P~;zgJqWmi+Sgj1WtMIZb~Lw+r0b&Mh~$?nB0B*Rps%sWxfe
zUp6c6zPl^CqC>KuFJ3QU*`fpceX4Rb<2-nedMgJS^ptOYw4){Q_soLI#QF}U#BK9$
zq;F9>6Tx^`DQN|GdUZzK8Si~_HXO;lC7h&g`hMH(1WCUwGoAce^rdTUb+_woKEk<Z
zu4Q6QVKMB8k8lFR;!feaP6RxjWNA2A%unc3>;t(E%yYg?U^x@{zTVlpQRHRexr4H+
z#rG~zPrO(6GEk=UAE(e``6KUb>(+V9_@DmOH=_OA;S0-^h1^by&%f8*Z|7xHadswy
z_MY=BJlaR@$GiP8eFQ%Ei^s-ZDPiAt+nOmR#^+rXW~_y8Zg0BD<DIIu+<13T+9lf-
zheh=T3~{L&WA@}&ygqpJbel|h3I7+3K>f(v$-fs*S=E<jv(JB$-g%iX2T!&Y$>na`
ze6hb<{JCDf&BRCTb8jAh5cl>V_nX@<UG~h8ohWa(bZ1w^PUBNEuWM(%d3R*Bq~L~p
ziQbaLmB-{PAGH4GD|uQPr_{Yf%232-%85%yHy@0jsF&A!&*ENb(VdRT*B+Y5tySU{
zQj1toCQ|6z`NwpvwoKriSg&n~OvkpTha9LV`*m=+C5JEb*_7X%2f9A#+sm}22Q__F
zN;FGT{}iRShS%Hn+jb3cRneIns|<EAwTV7Xzi1L-4VvKf(T5DBoZ*?Q|1iSry;;OO
ziOO@XXN+2;x6D?WQL)qa_WnOrQGOA6hxepD{Pp$>PjdI{$v@p8>)V@da>AOd&=xGT
z*%JUcqVDyzwZ;vgVtD4Zf<u=k-F&;Be`DNY*I(Sp|M+ds{*<`#PtQLx^TVC^_&}C3
z`SRNi|6Xs?BK+?9!+=MRf2{t0wyR}k{#V{T?+y8V?=BX4w)*@(>G?H>veuMH@A-DM
zX>Rz{M@Nsb`rL{=aCtG)v%@_+hs&566P&>H=8|R0K7}N|sn&8|xnX``VIf=Ya$&{z
zG`XV2zrK}m71c`))a8~7pDUf>=Xu4uw%mG}+?QvMT3z?e$y;?~_RNbnLZnU{?XF%g
zKF{)1`w{M=7I%tfT(7(+s(-XrHD<$}ViPw({rc{*hpi7IRk~|4dz50gg|j@U+Aq7~
z;Bn6Fu0M)Cemb~O<WsF-%EzSGwY6&nUmo&!Yf*L6N_)+Mx*x79_j&(0>Jq{mzq2Pr
z^URHG;~6~*xjxQ+VcpsAPr&s63vb(@&1d;VYeU&`)xAQ~xofzLHoSVFr<Yiwoap4`
zw<vo7zv<juFA*N?Q~UStX?Ww^#yO++`1eouuO9BS^H{5;bej`2G5X6u{^y~T)47*L
zD~p(>>1=Xy-*ZNkn6}PL%Oz7-4jO(renLj`T>Z7rw^y9Kb?!Uwwk;2CRNwyaWKp;%
zXYpz0Yd3!UN=m8xBi<)pCF%dBGGd}ZkN6gm>HS_a*6-iJHotb8oVflqi9O%0I)opX
za<@d-{`kgR=(6$ycPBOctUB2LA<?hT(Yn#0;3+8DR&H1!q81ksSbJ9_akAzn`%sRb
zMw>0J2Aw#1`dRV43iaq!pNlpb&Oa@t7ON)x_0pZooliAEn}sGl>3Y&-qZg;6rSkDm
z#KfoE-#4pIygRXX?bPVR>sp=Szr%QotUhntuEYPoqcr@;Y47$!0aqeBx9m&)ki{Q#
z<aFDff-}W8y0Y))6qJ`3i0k*Oym2~j!TR{Ssr;jhdS0>Ox*KylGo*8rZ=W{!sC0V5
zobGf31Dis_rL*7l9LzeoA?g3F+@R|IL(8oL*4OzY#B022m-=$cmsS1M>5pY@JaHB>
zt2kzq`yKhDArV$lU!1mRQd`f`>dQ}-#F+^lQ~loYsoFZs+x-V<<$6LDDAHO_*ywEL
zIo`Q3$VK1wMsCEh%M)YOg4}*r%-p7b@87o6wn?tNFBblKa6D69e|PSgYQr4;{ruPJ
zZhHK_z2&)@?CCyh!+)<&yg$eS3RNAvmFtG2ZOIG4)8JpPe4ELzn)7kx7uJ>^J#Gi*
z-2Pf!a@9*v{;#QNM|GyG+|T{yBAmsq58qDsdhLF}wfD;{%5!YPRBYC(C%)VIgRO5_
zzn8?Z{r|R#$k$bTUF<QVL397#GQ9*nofwOC4L@H!@I4T(!ua_r@BRa-T0w3K0x^Q{
zrA7y)yzmlJjAwbQ*e%Z1U6%OhsG@z(`{YBdo#OqcR&cuS-Ej2R_Wrk%j(Bg9-ZtOs
zn6>)i2RF2qN`033a4b8$=HsJ<6(VJ#N!~{@j&#?qR_43*JmKs_jXScByVUtAlZsdB
z-;r__xv8}IW0SbqygR3lp5AX6{pkNUmZFbE^*Iu^s(7=PeBt?~u%>Q%+!k+#VvSY5
zd;S;puB%AwUuhQZb=-lSZ>wBm?owlMgE!A_`qe(1dV4<m{&fmJzOpJ;cvSrrczgb{
zbFh5iC3B004Uckkd^s<5H+AkRXnWtUXLt5;|4E^?gDf|1)TKCaZJxq0!?;)2J6r>_
zYy2d1y6B9-&qYsLbkt|`NT?gNa`Sr5Y0*!1a-JliuCw*{$0>=)i=s+y1=oIf{_wP0
z++<FHZMAdkZurL*9txg*^6hiY+f%<AU5XPl-_@`G-RjN8PJvIZ29X-jr6kZ9jTOb_
z?kiryjzSdpl$utwDocKiQeD%Ri5e1arN^RpzIm@a95G98zv}A3_pz_tRL>~iPWb*V
zK2V~<_LN`F;kGGTBNjw1@R$+3y@q@Dp|7fE#OL4d^q#(M-HC)lt)0_PbIt(GS7<#H
zKOpbUdgkb?nSuwE1<r)Q+fj`x6DR5)Ja}-!mMu>{E#0s|p!`lD`+c{q^YzjW+a8H|
zP~+aJ6nAZ2(Zfei?@la9mSv9JuwUTRQ*Y1fM=nR_T1p;2dODcB_G8g6y*;zVxxU-w
zFFkg8)#QIW?Cp4F>Mq~7v_^Z4^cwe|UQ49~PXy-Omv1`CDHl<F&V7A{-Qo4dEbHff
zJ+j_8_v5#TDLMtWJM%5`4$pTB>g`pJiwNm-@0BmRmLc+S(zTg}d9mWL5s80Sis|p#
z@Lk}p#Cn&58&1gF{oC8@+wrEaEj+v-PUOtgE3<YAf!3%88YC{gx%hYVt4hH-Q>J6a
z`g6G@!kCxsseP4o4V0xFY@wCxnaTQA8<KuZ;<#vMe{hj=il#ERQ0DH&BM&AeFPYoa
z_uzuHU;6BK-#-50kT0H-SDk!&<?%P_U#)h%FYN*?ZNwj;70>6E^FY?Ke7^E+rrUc5
zbEnPs+n2xJF2Y!B_9Hz*UtZVd{^ZHb+jq}9y6Cr=w$QV0s<R*N`FB3%P{`9=j+2)j
z{JQSCw$QWxku}2eZtZJpQ)N7SX=3e{z*YCM{mrvj&s_ibpY>0WkU@Ob?O2stoGeVG
z@a7X!@Zq5TjrJT9pQrmAJ0WNgKKW)5f8x0*WnqUG9q8{C@7`Qjp?$jX^z@chCj0DH
z=}qmPzL)nU+woOLUw(14DZL<ly5EcY`|ES92eKW6^sS|9HrUt-Y<K^uxXVgZ%v5Yc
z&W0zCS|5gNllFhheZ;tZ?}IyUmUD<tOIuU5flYMo*&nqVtm|In^{Rh&OG@0JzHjNx
zHtFU&dfIUjCY;?`7e)0eZC?egnk#9S9rSV0>5tzgcQqt#O(^;PmG7(48tqB-a(1zd
z9CCcm-)olsc(LTt{Mf!3n~W<IOG6I4k?d@k`0?!N0M!JYO_O#uM3x!2h&&YMU};+2
z09kizy!fd%v%mJSOXa;w#4meZHr299d1>U9BcXf6UhR>Kn}3%;?TfwJ&J~-2qI3Dh
z?^at07s?zzjXrDUoH%ms#s7nf>o01|_{4lZX+wF4jma{Zi~r{TYMIxv^ZUlq4|l7K
z&#~WcnAXI=@`UYy+WgrfoW*OOZcmi8;-6oq=sDxO-R)K1cU|t4V*H$#&A0E~*TsR1
z#lPQ5T&uf(9JI;o7-E*ASX427ot~*}?}jV)E*pGIikE1+Drpw8C*sMk1^FA~pYPV1
zqV!vbsqNKaiGQpwE9^`KwB3TjBB!`$e2v{U?RVnOi4K2MPf7QQ?z<7UTh{tj*@1mW
z=XHzU&dT5C@IR?d+~$_*N1?m2{ks~g?rnE2W2<}i`oz)GZ+F*xY*+By^XJh0mzy4?
zmIw3XWSMMOEx-2tqOd(O(sp*ak6f4S*e!BRv@hnshAAn#e&0wx7&Kwy`D2^!$~!tw
zYVefsbGlzJKkoKQ&|+u(<({8fCq}INf79@#61PMcyIc8u9&hL6(Jl77LgF8Ra^rz5
zGkR+#29@@5^;|q5?s@3Mq9-j&{antRDBON`S*pt48yRmUyso`C5ZtEgyhA6!Z^QMO
z%O{uhyhzT8KlORyw~$+gJ8gePJguJVM7Sk&ukLp)^NPZgb7rjNR==2@`|#0!TTsWw
zXWjk&3srV8>wTyFm&^Hcc=3*}|8l!pDj#&3c5L{wRVi_j*QA2*8?jw2JDt6+w@+&L
z`DikG;m1c;yVWj=8my79U(IQ}jaS&<!(X|Axhe_!E^HHKa#Pq*1UhKdEaI&Aw6>mG
z=CK?<i@s*O%x<p@ln~dCTWT0FGi|BM-Y45c7N>9U*W5J!cA(F<#GV(If-Ce)1=i2y
zIi8kle{U~iSH;Z$kp}U8wnz6|{Bu@sd3x#Ss)Jl*x_mKvZkSp>oS4GDXWrwr;%^WA
zvJHq^<g(i!Os3<ouA|(KZE4bV@67jASl%p@npI`)y2D_Ov~6$K^sa|qGW#op0>zVe
z-r(53Lpt>E<>>V;;RiNkNZhS`{)_9!^T+o^nf7l>VBu~1^j*?^PRb^Z8Nc4~?U^LE
zl4Hi3Hm|*ZeGdH5J=MxR<9bl};=SwL^Y{1E*Qtpc@QCpqUcz(L540jHxclY_aW&sV
z(_bzVlNMF>*>U3K&KVgOr|QQ<NGuWM4i7ytk5f+dt$6nG*<n4elGn&cDu=k;?$5h?
z*WW(ud)#s^!lO!x@oC~y*RpQt`!Afd*Nyv=`0;za|NQ>`Qtyn8-SF=_|Kn-wYeYFe
zpSW~gFtwDq>u|nPbtaF|Dx)bLGqQE}H?HBH)bR7w=J^i}^A~^lHQO)3cJ?Ldyd7&A
zey%X{6f(H(1s;6oy~V-ObQ-Z<*vB;{W{!#aWu@+EeSLiYX5=RSJ}zfG_sp~Ej+;K;
z6ZAoQ3O+uH6KXX#miZ91OjlfgUF+_h6(868J$R(LrcC~3yP=SF(Z?^2@@w>Y!dkeM
zQxrs`x89EP;y$3gJa^;TDrs?jv$JMis|;tS@y_!oJUnaj;zzBbGxOiRJ^0F*v#VsM
z@+JPv(5E(6Z<x#Ke)wFd6jzZuJ<8tV%I<=Phgf`qZZn2_>5V_UtaL{&_w?AqfnH0v
z#ihiB1I=|@@A;T7w?1F=^N_}iOZw~P->RNc@3(u`q7BQH{R9n89%lDZONgr4{lU#m
zWV*igmM3PfxmRxPy}CpH*j(`L&OYsgzN^zd{nGb2u%YQsh%~szstc`5c_!;Oy>vJs
zelP{^in~W^x8E}YbwW9dBP={MIh%4X@K4lrKUi_r-Fe&AxY^sz$^F>#gYnwmcY=Su
zDO$T8viW_7>&W{*oL)-ei<TL%crTi!5omC&a8kp;>Guz+&u4k@)qL&>F@wHa-+%oT
zWZYb})e*EH53y+eK*)yos@(;Nhgz@Jc>4N2ZT)sLd0Vg5u_+tA8;h-)d!XbCKi`4X
zI@7wve}7(BVSKsKeea&ypu<nSBh$Ak#oh2%OIlH<=l-ZQ#-{Lq@Ox1<%}Y-&Tu>DG
zBAWd8q=nqaPkD_?C#9X)cGOQ$Kl+r-hlEAe*PirsO+WNcNyN<j(J=>c*F8CBeO$}y
z?WY}aOPF^!Oci=~*9-UK3F<-CeTSB7229lB*}p7dUw_qopP&sPNhihmB{-)4dFZ5<
zvf}YF;hk(e-Ayast`6SD&%^7%`eW<->-*+4d%U=_e2Id$<R$-5v#DP)_xtC|^*>_*
ztvtICAGu)r{$-na_22M6WNP~>qrd5?Px97DO>35{otOij^I8c#G7U86mE^OpXYaxN
zLMUtf7A;<^-qPE9w&mluZ}SA8<A0a0T{8n63)xaLbFWGH)kAszYdbzj_BLsD#B|I{
z`x_$i{FC;Ezw@S6)IPjiC6xB_Q>w?t|Atw8YuY{+TDu;wSu(SIp}#8U=bz7gGGgXV
ziOXU+qyCif0N>l{zkMnRcDJiaUti;3*~|%Fz?;A|qonf7Mfa)CeYS0Q|MF^I*~OzV
z?-bLxefF?SRWJOwX|V#s>Pu@kK3bt_Huu}3N2*J7Dla`c`Zb5EzanzB{j8)nPcC>a
zxhk3+uRo=G`qtKKT@@SGOb2&KuBH6`!1`n3qgKyp)r*>Tl>7|dVeWUuOHg|?zgoe^
zqPg2`#r5}de|7P!W;U|+JGAwnx1b)+akla$<x=Zd4I{V=!%nv@6`tJ6-CdD5QUC1W
zaQ)gh6)I-$pEDg>dW&0@d9l#e+7--<u?zncpMGh*SUtgrt^f5`!>LzFwkBRR)MUMB
zyz(H2_PJ}f&nv}6fL7h-%lFEKAK39k?&1Fb;r%XMUy_*`9TJj|Cb<Z&*eG_~7m%!a
zHE$cJE3vKpzq*m_lBPfDw)=Zu^=y@RbA7sKwXlD}>9zgIN7tU$W_q^0|Hi@O!~YkV
zyegBily7|<yZ$-Tv+%I{2X3-?9;o?wbAG|gpqX*;mybp;I&b~C<O=f*8?6Mpt9`|w
z;i?6QBfSh*lb>=YFZ(+`H}AWEv1IJWO<(8qE5+GxE#6U4`G1Ra&FV*LI&oQ6(I0P}
zVA_+nYGbpl`$pY#{por=-@DfCC`rs&Kb7mXah&zT&d|tv8&*!{dVTs)>({?^w;1)}
zBIb%utLn*(F1C;atybT^N4DU-UAoTMg0^a3ft;*4A`g?opYz<#N}u!Y)Ya<sJYr7|
zEsA_%rDp^_r*(!_==G2b_E-8B?BdW%`19K7XK`ixO^2PIer`<qvoW@MYO%J=1yB}R
za!thh<?%~OAHFPf*fH1p^6m3V335H(A2Fr<=CqnBE39{#udM?dDF(bq1Ls7vI%+=q
zf?FNnEw5&mZ(e6QzgYjk_3zPMB4>6^h+nYU%z3@6x9hVvLjAtxwhd3W>xQeGxxHG>
zHSxjM6&l_L-X7du%xHZ7l{AOXWR^46<71lp?Yu6%&ffI`Hqm&cFZlM;%}r|$%myu@
z6)N}|w2YHwGb>U)YN-)Capn7#13OhFbx+rQUh~20nBN+|?T$&QY}e)%9o)Tq&Vro+
zc8MPkU2<9LGns4s>#pe&p0VtiC3tS>=}#s3Ijbftij|Pob~}1{_fNfo9GR(ligS1G
za7n0DpEB?Hqo-E+*7K@*t`_~=f0xnb($jAi>1*^Qd-E=;E->VIxprpNL`5F8&~rTV
zudaT2=u+3lT-n1VR-0yeoKN5fowJ>m!}u?G@A03{C9a8fo;&FG=GDY=rWG18;=i=N
z-11Y+`<Xdi)ZlsatEo~3k2xhQ|4JzD`oeP)Gy}yl`8a8l_uZ9&HzshNw%va$(O;9R
zEpXy5A-3wT!2u%SW)H7kzq|40_V3y&PeeHWdwlNm#S*t>Q}*i`nJ3~t%x(AIKSAY8
z`uy0rTjFdjEswFD+4=RrD?!j=+DD6*tXA%3X}Z}2KefVu)o0&}bJ-flPR)GT20HR1
z;g!YZD4uSn{S!OH<K;ena%;2YyPlF<+h=|4*^UG2bV?3AdYTZb7+;XS=CY^hvmo)6
zF1vN?*B#s@<q2ByB7MrFtKw$K!O)3%dAf~WTeu(lILm^X>`%Qj9QDfP+IPpZSUfgQ
z^ouC1+S13}U2rpUabvkNLy%S9qFw9V-rJtqFiW?(eX{7wq6CYlt3=Pw=Zd&kyYpZ7
zx|F1^@k!M}KhjUV+P~q%j1T{8|IA@#ce-ehF>99Ant$D)?;KqZ*pzArOk8SNm0QyN
zVgGbF_hsVU8dgg=B&K!wEKv}KEFl5ky9BCF2(O{|`0Mq0X@_2K$bmJ_oS=Q<)%Uj-
zR4q~XAGtsKVqBhvvWeWu8y{a+7rg#^dq>00uM=}SHtk}Z%05kLn}QIx3HOeMpL3_J
zvhzGp^YN#B!N*5mo(qR7C)k~j<Fi?-oDdWYnznT~A>NpvG~>7Jk*jJOze!Bj<9Yo@
zVU11J(WBmJha+=Ne|`Gs=r)~}?&%L^EzuU!e^;4VGx6`#GaPSLd<;HvdfTT4!H+^c
zPb0T^)lSbfwf<1Iv0EuFN9vBq$4#3vrpMjadDQB<+u-A-R|z)r{JI`p)Z6ZAo#?()
zC01r@M7Z^#ZI>jE-d$RCynkuZj-bx6#EE*FBX_+Jx+!p$^~}C!&Q-TdU+;hN?fJs&
z72Ju<yH!43+3&3%dNbsb=>_)gIkhnd_RJ9LdmhwOxG(ho=4-tTiKYn=z7lu;_FP$|
z6jw8!b4Ka$M;ERbS1+0b8eKMno@d4J(}<n$@Pxppqcxh@cD%2D*el-5HsM-k@-FU6
z`K$k>Pv<#5-|%mm`^WRv*F8!T<hC{6-CghM`NZkTbonE`cIJhQ#cQ9M3zYi_8N}EA
z3$FRG@LxQeAA1(d8PFcw@7_$$Ud#U#kk9I0c#D&zSQcIc8HCT&{w{FYx4K?@&#9!H
zC5eIh@18HNs1;X>Q`-7nY0WWiVbJ|oF?(FDZG6G;)zmMldiTa}6+I$vi}YC5+1{H}
zA^G7T`_Ve(N2)yf^S1;aIsJ?)J4f?#z>)6a+3e!XI(KhGA3ywMk@T9)mv)pS>fDxZ
z{?GgU*`ue8&gV9)E}neEd$0Aj@EaemMDx~8?|))pS>x4NW@$60ZQn!f$v(z2Z|$>E
z{ncuF?XLRu`j4-<oE|3#XSKf)v{9+$+xVBWDtGy$WAps>)EhYp8Qgk*F#F~1%8;Hs
z=~%|iCYBTIuirg?B7fb{$0zGz4j2d?R!Tay4ZQ9A2z)=q%x1!)410dPTFnNUcLN;`
zvwNpd?KQD7#?Ro^alO1Q>o3zlKhY}*N9V5I_^W=pR%t?9{703dz5kfcKK;GHByZ)U
zwl{xcl`~I(rc{3(ab7?F<pOJEX4lWpiy1F(sN0)38*&2Wj_QyDF<RQjIqCvu3gJV6
z2V+v#-F5b17mY35U@3p-DEDv4uwC=Jr-%37OgbJ}@4Dy2%41i$rf>S@_QW*&@Xt4i
zd*<jZmE62S%GWsU{-f^eo0zt&7j8=rcKUEEv)Xk}M9BM=+Qrt>^}el@+EJR(|MJW6
zWkLBivtG1r+LpFs{>ekep4C$_rq_Spa@6*sV$3cBIsf9YOFcKQWb7zNIQlH|t?;YA
zMl(_pR~}9Ic<9xxfVXMZI<H!5<#f2x-q`K@=bch+;_qIzZa>4p!w>&_wlFI=wBT>Z
zK^D+P>Yv|JZe9#%{srn?{cruN7z667vi?5J&2wu*hxqF^%wAHPOfxUOc=6se19TEs
zlQvRYm~h#p_~i7R1^XT|z4>;(Jz{;nR-nQ2^7~!$p4BY%O^ubFSD&U{_UBjLQob|H
z{%ag(J!{W6v90a6CjXi3zvaXY$}P?~UvCc*IrDt}`fGCc*M2dUEm2X`WLj3J9oU|>
zKssLThw%q@&jU4IyXSK=Dhu==)(M>nefG$<Mbq-yl+{<tKkgN1Z+NsfX3w6Y9QAu=
z%N{*SeH|%p5yKIz8#kk5<4MnBm)UDK{K?RW_esw@wnn<feNpV5kMq8nMs7L%ZH|kM
z?UVj;t~H_`ldeDDXb)5O+_NR~!Nrxf>n_XZ&;5F2cjm?lWm88%y&n1MTMxt;^}0$D
zZAyJ^AMdExxUT5IqDId4(@W1K9sQi#_IPJX!pmc4RL@GVuKs=~X#K|XPd&1C-Lq4R
znNg8A|7WM&N`wFZ?m2f&I3;Jb_IjPx$`?5YrDq0qHE?R5G|M=wG%sfpe<9=H4`)vs
zWE?)RO8dC+@8%s1m0vxU2!Qtlc#_ooT^+vO4BWQ_9Y^(?NmJGFhTI?Jlq!#vC(c#X
zMeB1dTf3QE+uSwjc<=Xwi?y@AzB&-1qU-pq_3w^@kJ&VNXI`~Qx$436?DYBkV@tg+
zYn%6Q&giby{bBTz|G;}y#?MdX^Nt;z_HuowvcQ=T<gBB2-`Pj?Zu8!bHC9KKax3rB
z5*1rs^RZ}`=slan^GmsvH<aj`JQZAZ<n+yFS!=8Xo5kH*y3f2?6L4d@*G0WN+j|wp
z>I~U^?uT^WYs8%?I6iGvZp<!&IkV;@+*cC4UY=Fnn7&*-=Jub5!P9u!-z0YxB>pir
z6UmpqxtwGA>sr~32QG7IZ+7Q?9UXe>@WxYjGis-|J#<^Tk6U}gc^96m=jK@4t(<mR
z+$|<}(Y<}U8msi;CEgg`7P}_8l4C~l;p1O#`Kz|B{aW$#M&8rkoBw{P(@Q<Jty|A7
zH{`&TvaQKC4+<zIfGQX+k_t19pGMy^-mPkVeo<WDp|KQeaP1e~y=J#-`5*U2&pR;5
zIxhUugvh;r%|+Vpx3t|7-l6?QPnCD(*E-JwHGhwq{&=B$c+tOQF1-yuwO&0xpzqFd
z=8%2Dj_Rt#B`i%dyWs7t8LQ8Ht?pR)AjEs&q_iH_y)qZ>h3}b}*?+zNw&tbO+CJ+b
z>CK^?(-&>4+EcDHP5jdJsd{gIZf@WE;EHCa_;IuRHK#irN^WNEsj(1S|J+LBQfp%L
zQ^wz6EBAGKw`+^*>up`Q;(hKxZ}ue7mxtu$XRbJW?XkPa$4v(gi~WDNQ@C$x<D`m}
z<&CLJZKreToNBJRZ<KmiT%fMdd^*>feWwp>_&6o&nUiC<;QGHx%M~v7@+1Z7`|LA#
zdCB@p_qKy(JGTGS(mdhwGji${)1=cYRi5tta%!W;6SLRXeNx`&p5Cp*csOXn%WGUh
zpmEZw9>^6zoTtSPzDaxYK13XGPK@@9xp>#CZp^x>h3J8-E8L^Rcdqfe2GcT)Z+bl6
z`u?THXIzrh`T6>F#b<thEvB@e+@~vKXMDRPk>h)N6~~PBEyww5wLqiS%9k2u3P5ka
z?D`=I9_-xlU*72e2TK%EesI0x>@(-jH>0I1?3!)+bnRD(zuaVYTk@jlN{bsajxOa^
zUZpoxM1Stzy#*(1Tc%$+G}ZOf^PuK}_%&?X-)}l}_#|J!yGdNHZ=3y0Iy%uqr;q>W
z|DzSjUp3ubcjahpKUA%HE@qd<qocj<yII2Jj&$DAx+D1V_|ntoYh*Y6{$u&?R-fg}
zu8N=gR*UBGesw9htmnGNB5Zf8*{!A7JEVUeZnZh(+V?EM_1nsA&-k==onO>^%<aI7
zW%9Q#%XY6@sQ2XSlfyeBe@F6rGO7JbQtFqw^fq71Q{(*Om&b3EzrNaIo5IqwuE8=W
zG-t#7h1v;ba`&}aKzl2E7$7@P)0{rJCfq|h35md7axTUAXJ)Bqmv5}S?mso@Pvyb=
z7gjS*uG}W{-F6SNbzLogUgd#V+%u}>quWJ(%P+Re)}G`wX@kS&2JRX0nU=TJIA`eF
z{b$#Uo3l?OK9`X(W}m^0#~XjoZ1{QWe|uNM&+C&^*>YK$cD5m9nU3lX?dLw(hI71?
zL|-0S6vuZp_293Z1xm5^&X&35Tos&?an?EFt)1&0(3NPlY1gJErB=;Qij6yeqq#a+
z=Hqk@?;CUdj<hA!&r9m*6o1Vo51MG$&;R{U?8ZqI6JPJnS-r9Kt;Z}wUu*3(?>irC
zySwAU%?09bKP@lF7cukh{<BfFwX0(0`#sF%??1Xb1n*Fab9!Cg8-2UgHS0-?&0-F5
zHgEH?x*Z)k_Td>XvO|;-{I)&UxP80bN`&#SQbH74w7~6Y9Lv-ATX_4HDJIWfxpGlL
z(y4O^KLuaz-uA45NB@ldHs2#ZgJbq>Q2LXa>&y0A5|luh4nBbwV1g1zhvc$u7RI}$
zqNPT#<lEK*dGF&3Zxk=#>-oNa;byJSoAEI>TXx5PI`ZuPz5e}o|1ydjY`4kj<v1O`
zSm&GG6w^5eq22Z@IVOKkre~|q*LU&T%gl9{EtSml?C>?2nzu!~A8I`h)a<L-=_uWp
zpqB_gI1+x+=7eAK#D2tfgfHo?4V3s6vh2Xl1o3Ng%Nj#H7brzCt@^IjFS$;1ci_F?
z)z{^t*Z8f}J0N{rX==vw>02wFEPr_Ju31FU39(tr99FLQ$5GbHSfJ<C61$Xtjdsk2
zwQ1}+uXPeH=P<XMX<ll5c;_M0cC+{;<#St{%i4kh=KtMt&da%c|CWGxfx(@iHBsPA
zOBEki*w2x+5&bB1S7iP7d8I3*)`fAsGQOmj_F+nL$nBjSG0bhAGfvIZO11X36uUk7
z0IQEfxJFi_?F_a0e>2s;%ZJ#VpVIB_qn4oL<(FB1{Ze^eY?#e1wqHEAa?bfYVS09P
z@4k%puMKaUPhfiXF~wdl;hJo>b7i$LXf-DXOB;OLg@Z?;y18DJ$dd(5>+jEDFx8tW
z47xeweCeOQwTI;1U$inPwcK{fF0AAK%--FgCiSD4``3hAnvh#lYY3XfyLQWG&AshW
zp3dK#KG@X!I%@M^_4`MY{cXO)9iJj0YTzr`aJjtkJqM$*0DRuHX=m4tb^|}}c<Uf3
zzfV<qMN3X^JZAhsr|Vgup7bZ1gjg%BxRON8V}4sj7o`V8-AOy7x^@{y-%j=qhaM{I
z6-myY-*N2Ro0L5jXGE|0zUwOZDD$B$b*+ArRMLZ6FIFuPPrhMtQ{#~9G`)AS_daCa
zX`QpI=#H@H5|`a;?4~r_I(M;3*V6ZDh00M`|7E)$wCeDdh3xRY<k}s(mz!^^T;Q!&
zhgeQGJ``~knqd`sEk|u*rM<X;-qpBU?+>gvkf3>DQq?zYL4%)rfBq>h&->zXGC{Im
zE@{55_v35kIT~)unl?<zdHdaemG+mYV{%irn7??Ne*dcYxAp?Y&)+V7%-+4B%4kzV
zVnmAS=iCtR;i5fBkiEB{L-raFm*)^#Udr)P=|bxLN`00ewc&yWqAT?iH^i^%zjXO_
zzpq^Thx69g6Yjp33uHO7`TSakX}_7a%;R@=$YXeRySATu#{HY;okQn~uzvpeeD&5^
zAOE?c$xP1{-%>e{6aw0wv~dNa@{Gx-(Ip{2EzZaMQmbI-+^!W}A@joCyv<S1(ftzW
z;+=Fjqp?Wr>7wZCt-3Mp)8`%H)~t)DIw7tvC+lnY&CN|@p4YcyhkxfL20mZqAwDfF
z=QC@3j@^6a_Forg>4}Ag-H6I%+%0A;`|=T2zn^@DRIOnA{J!s(`CcrQNZuNE<9CAV
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
TvXdAX7#KWV{an^LB{Ts5=ONA-

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/m4mdensemod.png b/M4MCode/M4M_MkI/ReadmeFigures/m4mdensemod.png
new file mode 100644
index 0000000000000000000000000000000000000000..d6a33de632a920235ab076cd8fd28c2c77acac9a
GIT binary patch
literal 15784
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
HoD!M<GFuA}

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/m4mplotting.png b/M4MCode/M4M_MkI/ReadmeFigures/m4mplotting.png
new file mode 100644
index 0000000000000000000000000000000000000000..e77b200c95217a3929d4efebaa491edf235c437d
GIT binary patch
literal 111691
zcmeAS@N?(olHy`uVBq!ia0y~yV2fj5V0_8J#=yYv_`YKZ0|NtNage(c!@6@aFBupZ
zSkfJR9T^xl_H+M9WMyDr;4JWnEM{QfI}E~%$MaXDFfjaI>gnPbQW5uNE_;kf`oI6?
zWxcI879DHa`(5bGjg93;`&eCET^$?*Un(qU>N5Q%<(-t&GwX<puW{_<?&-$jlfCu!
z#}w^+GHJK>yZ=AWf7aQ2hVOpr^w@;*2fyon)@`)5{(WZi=G{MPo*y<&esycDdc?HU
zNHDU!Tgm9Cb9xg5$*q66@a)Y5h}6wTo6^tEds3vFab@56x8DuoX4#%B=&B1|@2=4_
zUH5$4R?V+6U^618>GtfsethET-UWfb^RCpbzrVGAjdzR8o(&LFkLt1OtLn4sLwGvT
z6_yp6m6jEW5NjgUpFCLl<mrP}sEfGuQB=Qfk=Zk458SMrhfh&d&z;abebV%1ahOB)
zpr~FWT=XI6Q_+V*Q2(_~M^Rns#24ok#}@|;jcp$ws*?`CzP{dC^{N;oSfYPH9XM^3
zx6H(>ZiqW|YoUsrR|yE7wzIi+;?Zt*sYhr3@;|z2`}O0YZ@Xfk{-34@@$bT?x1fI1
z^?=x$6A|$7)Bk<-$KKqRJN|#4UC;l&soe8?svEi?{<&$S0tt&PF}tB*$$Ijj_sP=-
zyLod(^|$kxp3sfr({w!@_UFC->ks?;r#-01;k7<`TGfWV^}L>7e`@lV?D}xIKh|aP
zYSaGzE$x~1eQx&QPd41QN{=L8DdtLce7pO_!|NX_1GgOq2T`<3(TAjKDMpqmU+)}>
znsTsvTYjHTxobJA{<-UV?1s@d%bwh9K9YR%MaiE`7rWmk$DRqC)}P~~UU`3~na=sm
zD`MSk+-#Q{h+Wy&{cMg?0jjatw|Lr@%!`ibUVR^A==RvhPp{tn`ZT`t@B4FAe?A5(
z{M%tGSik40e6{kbQ^!|sJ2Q*9@BII`0E0#KI^{b<KLp({yK?-~wCDWAE1tj5&Dh&?
zD;=DKbmta*ND7k<5dk~;igDbQzl-0APgND%6JQm;dj@mg18!DU<zL5(rbn~tx8^$6
z>f~4cGia~syVrD0dqpic>D;`e$*!Na)zQ^4=)CS4*S+Vz{=Ix(U&~viqH?XXXiR=M
z@ATVu`j1D?{IB)q)U;ba;`1(k`aPTZ!|$`-e*8V^b|P-&n&^a&Nqp+_e!qGD<ImRQ
zYyGi%m}|<^<2NWW^*oQOmYx6FY0{xBiDB}yGTy(I-<<0BTJ(w?IECfBoYE}5IV-@?
zR&C4oibS1P4_sG#^Vi-wPkWlnhoCK6yDjI;sQs47B7Z;k?~Q-Q1uMUOtU1`mVUQ+o
z-<lBX5j4Z>%qwT%x3zzhx9OmT%C)$Fz$uPyg>Q<t)<>=U8ne-*Y5Mb9A&1Y4pMPwf
zDo}S~_q3QYvz221DmL5ytJrG$uVSTO-Trr~cQ@_XH20A!ukw-e))C2(bv(PD@`%Mo
zDEe(w_0v?Vdp3LSqXcQOucx<uFYP}WT>bRp9oJG>-k*M@Y5Qvh#T?&mzG`;F=wf|o
z^v*5?&wcNjex>K-pH08@T<G;}3sY&Zuh(dUVvSdO#qpMJa{4_-1@~}Vwc}cOJa(%}
z)AU7aW3L!E{i%^*);V3Cyk^e9Mcmp)G`AhmJbL7O&E{a4oWqOic7EIU&ag5||Lc^*
zaKq~TyZr8L`m@|>^Zk7s-AY&OqH~>fg#U(bpSqf3(v#PEE9#$pW1Dl;ZhFN1V=He$
zGJNdBX7SD64s|W!G49Hqzq(duRoyGyi4UH}td{zbmo8?<AJwtu*w&|;oL{d>_OCF_
ziH~KC4iA~;dV1Q^sa%44UNkXzZ&<|3b49`RcSYju3t4;bd|P?!NN()zu>Qc@=k+@E
zzb~A*aB*w#$G)G2mdWb+3MZ>;Z+(@mSQoMEjdf}DBK@Cf>>I&_qFAHMo*7%k*W9Sy
zV^H+LDf-J+?>L17QIUuy(cFXuY?`-@bKh<Yd;N5qzo}Z@>swo^da|tlC;z;?f7Q%|
zi?h@JKYn)nNzdFp4Jz-yW?nCUurq1qruSFgcJ6yJTlNSfU$cPnbzDN=ltwmoeKV2P
z6O8%#e==V>-FbCexRdww$<%L4ZW*oS<TXj;5;cfuWi@LOug-mPf1B9b9Ld<(Vf~hu
zw*8tRCjGYicagMJN`}(4;Q7n%Z2LN^cz%+j&GoE!jn{D@@3Wb7ZbAypoQsh%4_j?l
zKDSb4*RNdl_p;a8=Uu9Tdqi^IF2c0L%3CFW8OKfi)yHKrqifGJemh#bllSeeE+Z4c
z*DKEl=iRP4d!=~ox7JPX>b~w%J1{918ftTo3dY@-T(!=)=)<Jtx9rzmsahAIbnx`G
zW2F-=t<W<wS<(OD7yqf$*XQdK4L_ZE&SCp)=F$w!9TgYZ1a>wys485r)2kCY@l__*
z`OdYh?49onr#FeS%PVhNxQfm1+U5|hck?4n69TVoWzjoTHOt{x6{M($y#DmTQJt&)
zUaxkxZe7tVp8J2rZ`V{$7Jb*~-5JascN`7~31rBN+m}9U3Rw3%L;BISu;|mJ{X0eW
zSp1OK$Qx=}uxZ}Xpo0HrRxx&OTBlkNc<nNa(Y4o`!6{*v9=m?p%DNzMDI~k+#qZ_&
z<3px-d~nj_nzmS<DO#UVP@Uob7xSk@UoC%?eY(Ob@aRhfSIU6}wevzN9;8T{O@A#f
zy#49|*Na*&3byc8_s`ru@p^jir>*x67Au)-)VXeV|66ft#A55CH;U&!o!8~FT@Rca
za{^Bs?2cSDf6A3&?(9G|{agDiH{B8SR;pN7vajqS*cPw&6Ag~PZB~CcaMCPC@4CHO
z-fGA0MWGiSW=MaNGtO6De_rZocCE~2*Nmjdb+!B6K0md=t-NdF&Gce_*JaVu!PVwA
zjVFk(+wtq|iK1|wi4R=sqyO;S_@}q2h<keF?f(ZJOg(QMvF8PI$DK$?Gx_sd7d$w6
z(JV(#U2bw>ch}eABX7h#_jO+tUsSvBs+ow++s^BY_v-7csONgMt7vDPZFFrCxLPQ^
zyNE~MD{Aq#y(`l9Oqrr8xaURw?y%k1jkUUb?3ZQIx9*sE?~}f^X2fS~<;|^r^!3GU
z{T21IN}m?}t=jerl9gWXa<%=vNX0bke($aP?X3EXmGTolta^Si|K;WvdtYq2vhfkD
zmiL9U5S&hL<pzhbPV@vw>*Pj&h|)Qatw+IG^Yk=3X!~I4D<StKw;+yB^_~w=3$6Fb
zYstXcJ$@&^tp=zwPK(7s+8>Z&F!CBF!~xI-+08?zQB+$gLK-39^tp)+&9_v&3dtS0
z@?vkS4njg`lb+@w?rkZejBSBSvxE~J51u}^e7@t2hmWC7dOhiMka6TSMe}zzoUf@l
z?KY14_p!fzN{9X5FTpE6&j0^M{dMENKcCMpJr-a0bLo{I>+An^e|4z;`}KOz<NW=9
z!?ON--~az?)q?*&&)0kP{{Q>F-n)1I|9{r68~=SgE?>4ZODMG3IBtd2Vv8kN42!*b
zXGebbzRKV>broZemT6wvUHwpIiO^MS2CJ^@m|40%gf}5%rPP5HL66g}{SDgM6}L()
z@aco45=+?5t@*vQi(#>sD5Kj{DT~eTrfV_vXoa#!gl0WBvnzfThry~4!Gw^NjmEF*
zRwlXXhfZEm^daaA#2nLxz@@}kzIM^{Rh}W16`E`ibCeFO2y$%*TuR9Dh>PA=RaVLD
z5!s8y6L>6N{Q?v`d~sclSUiEp@~|&Krkz^s`mXwfLJ-w*t*Vu?T&6ZpPntj|1W_#)
z-5cV&=+r^)i{7Krhm>%^(RVe8FYcVufs`iC<oR35KlGPhx^Vmod*=HUuQj&kUu<m<
zB$#@zBwexm4~MpLzqSlQjX&Jk29k2U6)FO|MWTy79-7tz&JCbwWKO^K7o3VgxrcX7
z<acjS76RoS(*jWTTLVgjS0C}m?I=sMbq3``a3(ASWh78e1Ucc@*)_kxIS7<<rOY?K
zo4zU(ng)+5K#XCL2nA)rD+fTi7?cxn*sTpsgNvp^%wg=&0+j$+cR;zA&0rNS%fV^z
z)v^b!yJN!l+z|@?dG+OAMvLXzJU!$%;HvFG?(2FvCrK=dbU$YAVVM&;J^1Kp%hyiN
zVi<lcxw}b=JSS{jvi>6|lJ&sl9ga{&37s%-F*DEf@zGcM$BK9@+{HNN>5vinR~M)4
zDR_HGY%w^jL2(Q&Z9z`JQA&bSdMH0nT#uz|yf!&jer^(97klH;L{PC$LVPD)uMoWa
z)wgZVqV)tzeNci!l-7{+5_!Gi;jLC(I}*cKuCB0l-n#Q*b4klZq38GQu)D68bI}u&
zPe5KJR6av$INfItjux&2xt~yGMag9w!PQl_c|~CiDBF(KJ)=zrg6)>krUTYyC#3#-
z%cj3@;X-J``1B@KrO?SMZgI!0_y%q4PfP7~UZoaTs!@?B(FQ@9c`k1qud1x-W6|e1
zYyd$=dD-C{2`0Wcm|8<7nLSV?Jjomua6^u96nr=YmFQ7?@St_#ot?`6o*uvUY0`nY
zxAnMkzTSVaz8h@cF<+7R4@K(Ie}APeUU58Qdeg6iDMpGfg5EJf-II8V<*3-6g13*X
z*1d6ly3=u)Lcs2qK@&LtZu%T$`u=j=i{)>w*Ugg6&fe*~<Hx_<TXNG2mh69bZ#l%}
zsdFCj$8i~VY~B4%b#}A+Zjoj35DA{c6*5QVK<3)`ef`O|>Gw>9Lz=={*SpMqv|zuK
zP(-e?&35@G&fBJ~&X5n!4?cb5?V+Un+eaN6|LQKRJq7WbNJl!W{<ngqYZtv?hXs{t
zpF6AmH;wGA-QKm!oU}e4{I_61U8Ukd)uk3&XHA@FJ^RG9BGFa(w^G-AxqEM0H*@>1
z=rg~kfP<n<bHXCtxG9VY0q^3ke+gzQSytyb?YEkt!4zhf)R}WO>HItOOlVW_a((vL
zAhx<!3`XU)pOYW`d{k&ya3<GV)*)+~`nJ?+nH4`*%P*2IihMUKtT`g~_aAXPsE;4p
zb(vIL?D=}y=dIJ)#oPS9`fjhgSM>Dj%F}DVwb$7{S(h)aq_RUq#XrAP-CAGe!acDS
z-*a`(?{$BFzUDO7MtRd4cg(K(?aLK^zEAHQEDUZe_GlJ2Tric{vDNyKeoBX9j(uu?
z#_{Z5w{Mje%Lm_{#OzWVy^}fP^tUOE3!ds19ohDj!Q}jcoU_GObEDbTX3gfB_WRj>
zwoN^{Utb$S9U8nyB=tkkw=8k#*C)PxW@_8B@8+xGcSWa#k1*C<w7<x=QNAeud3Eo#
z&IUadfyCyY`@S{o|F+FxZ+`Yp*0=vI?92OW^z&BE8+YmTjrYE(et(<IeRPk%Bse|I
zVA8zw;Aqx{<+3rWPQSgoY@YIhX}6=)zvtQI*j^8_ezZI<LHFOJj^neSPI)ZXWmK^-
zk~yH1sdRQ#Uqe)0_S`$We{T316aBVh{<kdg=+_<1KepW}N!i)8_G|Hbv7S<o1Z{~8
zqHH&!@*HoAzbeg`9{Ahi@)WtE+m4QM8`gJM&NqUX%^GCYEbjg<>r9;WqvcoQIbgnj
z?B|vGAt-2T_u5jftjiOhYHhnFzMb3j@ll)8&inV-ywJaO(dOdH>^%YNop+zPR=4G=
z)Qa5h@8@HG-*Pg19}M=T#4?92lZun(wo&#GTgs#V2ivGAOus#=FPHgk@!DI4{2o{B
z?+GrKG_YSb|JvHu!tk`~709Nay1s1V#=ajbb1z>qkFLG8UHMnYft<cOyIB|Wa;_1)
zz4gqN4Oziol*`S!);`s`b^Pk>D@H$Ks(%NzU$uI*Eq9A@-LxC~^q#?zhls~SHvOW4
zW&ghVOfZ@;nc1|kE_$Pn<d;nL{42SC{XfoF(D1ZUT;Yn2`o@d=td<+5{(aZ3V5l2Y
z>r<`hT=?-&llcKiUNi7944QJVdyV0#sJBjQm2ZdNTJ$Sz_4!+6F7_@74twHO|7LxZ
z;IW{<GcF)5a#>Dvj7HqiYemLa{%!dhC%%tOW&7K2wXt{LIjv(6FT4~>?CMnFjgv5b
z;#kf9{G9AZefw?GrnB_r+@Ab>>)L<TpLRityBRg8h1yaHgQO2hAr<k);P{*(iB$dx
zASo4m@SqivzY`^#4;<u%g|=Hmvp6KS+7fvfkqX#GB~I8R)U;G@Cs6sU0-qn7rpvi<
zmdn*Vc=^f`928*MxoR)Ml4VO4WC?{LO6iXhD?puOxMeo1;4uieqxu`cV+1fa9t$4T
z4vBGzHrzqCjA!zsNlcSZy8OBpo&R)Q>a+Frf3=mAlyv&}B@7s@uZulhdu7(6sBnYI
zPboX^P4>6TERBD``Q*uyh;Mh#+yBpbIeW)jt}N?EU#+^g<?|din8yVk;5lT#^H`3{
zI?Y^a-4&6fIrn#!X8(*aytnQ6^7(aHH>cIrx_>XuthbkFOYC8SX$Wq+C?-+0mHoxm
zqb+f!41t>KN?8~Azj~ZC;i2Zi?ic4D9qmrF-}2ipR*g$JJRp9t@xku3`wV#wS15&m
z20^$F8~B_IzG1jquyEV53w@hg`&jgqR2E(<FR`EA`n@?(;+RGNcyNa2utBOsX+z$6
z2Z8?8ue<hNxFjwS)s-FF7$uPD%;V1)w`IMnbY91Po8al)*PrWTNuOiU-}dX60ng)3
zxB;sEiO1i#9ho`HsYT{hamdkqS{=#9`)<bEzxX#s>>htxqQqhc@W_(^&*2@CZx!OL
zE3W?!oh*@%{eP1u&$^=y(fxI{h0)dv!Y`ha+gNT8m+LQGkuiV$+-q?$ue)A;xl;7@
zpYU(z>i9Wcy<FBV(YGGC{JA=F?S8)El=`ggD?|5)ZLIyePh-m)#cSffoooAk*{%M5
zblaAW+J%2tTQ02FSJB(lgz4mCZ<ySV-(u~3e01B2!aWXKmdgI`y8X*;g+-Km=JvJk
z?XQYoT=r{6*s6Cy7q1y#`PKLC`JsKFc81SKP)iEjM%p16&3<evbK2j=(zUl1IYukI
zxXO2i_16AvS}RTnZohi`OKVGAW#L-w3*W+T+eiQQ4}D{;drQE1eVx4JF8kcCj=bhq
zjvqbWB(Q$<g6~l)a-F5GycA`fc_8k-L|dW_FDyeZOVNBYW3NGSZuH}j(7zUO3bz(q
z6AgOUx43l5-hD5Qe|%)x{d7@n;P#cTUw)q`Rw{ifcjasAUGuqr*MUmequj70^*C({
zbN*I|i(-tonqR-_;e2Z($j7mz-f^j(a7MRqsaVT`1p$uHPcr^jox1iQ?rnMDTJ3<}
ztJ%1;Zv9oy>rQf6QG556l22^>9=Fo^3};!>2^w$yiL7^wUB_>_rC+qA%yy&fN2?=d
zTUZyr*w6L#dfu&nEB|V&0OtcXm>YsOiIkmr_aY*Cf$Z_suf<LJu77it)#l#%Hsf~r
zmOh~yue!^2ZgUfUyYlzq1+_~S<%MtYT^k=D{i^8WEZNxpM2R*{NqD(uzF785Hvf~=
zEBd<~cZmgRbOdLvJb#hz(9vDrmd)s9+-Xs;UQFzj(N^BACkj&v^nYn+tSe>BI<X*D
zE#P~9!0!Jp3zz7HcWZszvCC(F{grP=?L<Y&>btHlSf@X4_0h16E8TP4*DU6rzNPl!
z{<_ABwTtQ(y?<5ZR2t3twKq}X*kyQu>LvEp;>ta63^gi0u6TPR{I9dl!U%WQ-TTZ+
z0&Z^)cjw$vyYZ`g!M0-;S>Gx*ZmVCEn+3}AiQv4k?8ib_$$qD&Y=VE_48|?H)`bN|
ztWfMyUU5End-#llwV=3|{_=m5_ZsDCuer4ve@SfhjSjkfB`X{3V&!d%U3bU+O^&)!
zx}`Q?p`7s1McWvzuFhM%09+<Wg}}>%MY6}m(rWU(-$sPp37h_Up<M9$?Arl>*RTI$
z40e3|dgE2`x7nLa`}8v8Ij?-Q`pO>n>KE(Qg?i$(B_+0Pi4x0F;NHH|v(|B+S0dYm
zNBS$S>{q+2-NgAezU`*d+x}g)?rh()B!XgIbLFgE{eJzlrnqk1t#3ur>bvSz_IJL`
zxR@9Ie}-78?5nK-{VR=ATCx<D%>BBEIM)UzKRvW={qeB)E$dHSv)Hwuc8Q%@7T4CY
z1=sXf@RshpalUWeaRZ*iQ+Qy0?wP&O^h9`|eDRWm0KwqjM;oH$rB*yQ)8p7`|LWV8
z;K#YV@2);Qx^>-wYrC@Qx7OcXwy0kBmXqt9G7<N0%U?`iyG{1)x?O#@;&p!=-;#Ul
zwYy_&vP9cQ6Ie2wF=?qma+&+_JzB3KVpq7=`dA+ezrOU?sn*!|{<ZroKF!+l`o;H&
z+wL~$tl)k7>Cm+Rp2G${ADs~8VRUni=&BBZ4h4r5w(N$j6*2Z4;-{~a<?`OxTCcg3
zjqg_Ktw#>ke9s?6g~u+74PS7#e%m?TwRY~+_cGu9T*}S7)t>dywp}rM*mQ4QUsCVW
zz4m+5@0yK$bu+j5&ii=Cpywhyd3njXAAi#-o1K09<1fQiyJF6~3cENjDZJ-v=!(Mq
z2d>?STBCgBTHvjH85j5YML*7XYxV1v*X=yVE;E_C=UZ1UlxRzw@u(HntSB_z#`aU|
z7Pr+Z=E}DRqI6YOd}T>`uvYK-xt;*_H%=GlIq=;3dYa|d(?t$~M|-z@xEK`kde!o)
zLchJc%l>zkx*Sx$b#dF;y=vWMwO>uMzR%s2m-v18j<tH?uRH8kWXK0|+q|m1zE1s@
z{w;y^?$us~U%zJCGiN!jid74VkH?6E<={BD)pV%u@z%!$rE7GSY|~tD?DMUUo6IIG
zJob#~?XO$gwq{(v$gVH44O9t=Kl_^?(U#aH4l87joxLITB>SRV#dU2i0nyy(Dp%>G
zfOU&>W4^rl@8oOKALYM7w~giK)^$-n8Qdz4j_V^e<gP1q31`_Zyx|_jwb_1U+0{or
zA|bDgM5EtVJHFo<@Vn~G+STt4Lu#H(<jV5c8&}h7Vy}*WWXgT9ApE|#qvPY$=+Y@-
zxgJT`e{!@vHny()Dtq^QYbB^jfF9&gM;=7+oZ7lM#6`f7_utW~TTIS2?ti-tY#;r+
zwZBj3{ASY#@$=Wktde4LH!fOgaZU7Z(DjS^#NPfBUhn?<mTQB{-n>A8toZFW+>`bP
zz7|WX?<(E;y7Mi^6}zt7RX3{cfBo%j;~ssW>-ox#{ajb}@qM&;%Om-A7o^Tdc;lU;
zy_;@E&HEP-(bjTe=P%ydZrUf_vCF~QPJe~|BHoR;7uic|FPhiNre^1?<Mmu|%X>kr
zd(LS&*0t|n)ajl1eb9jCu!uY?kMbmQxGY#8Cg!HCb*1ie`~pLs!#CPtDNWP%2A4Ic
z@rKl9=}8RuUvzF_cWD)ul1dP_S9JYWP_BOhubQXGL0axTe6XtLumoFa)IN*QhQI)S
zMuj?1hwU38Yw&|BbOSfIt%*FXQ41s=mLw;&bw~Mh1uq44<?w0%J24qvK7zPyA4Op0
z1elv>!v;%mkY0j6S~Hyn+P9uz*H@d|37(vvmU>fYi^7UoE?d{I>R;KH44y?#ja(dY
zYhjkqRU_FwQ(DdEYew7DSGvB_g}Ctik$VzzR(Xb;PWo`jz^CrVYEaA>%;RU*m)T?B
zv$7G|-?5S9i<4=8E!wv5;d(YuZ>YjfW)G;tw3H3nX({BsAIJP|F8jT1=D2U{|K6v)
zWB&Q@!O_AUafuRA|FS@Bm7eE^4|22X^8{x>yH_)koLe;G<IUQ&^I4d~Ya2Jm9pFs$
zSsT74nHgqX;g{7Pzg~~8Ui1IYbNlWqudc4HUUPL-sJ79{Z?O|^wQUZ1Tiq2>A3O0D
zTXuY#W_H1cptdIQk7=({ArT^>HhsRG+4;Di3cMB8S7g@NJoIhXKJ>Utz4htZ>HFu;
zxt&n=ruQItLQ=dfk&9WhqM|FL;!)QX$2k9qRt4|Zg#2FTk$X6D<u_Z`Rr}X@<Th{B
zKRm^>D_Q6Farycum*;{q{`rc;89^-PAwy}$^!C>!^M%`;Gdk}h`0!Nl+cnL?dzBI_
z!}%)~85Zml+}p>IaU}DY%kKSm&fblvv#2PPSr;zRcI@JMHX<#zsCf9GHPMR~lB9du
zm6<pNt3+B~nTE=IoF)IEhhKI^M(v(P%Z|!IqYsBPo_-7XQ@2fV=ev0Sk|WmYX%8-a
ztYFE%!TJA%u-N+jUn}2npW|oO?>qdJ>+pqw50m0wDXd@aA-khIY*lUel5G#yg#4bT
zbTvMF$+m;HrYD|~LXGxgzAF+>h%f(is84$G8O1i`Mt8xVcmGN4n0fWkGV!2Y3fXhz
zH%*$Rpsg$NQu1Vk$VSf0kJ<%#*<5yauFL(m3sa2WZ7;j7>GkZ}3bsiif0&sb+N!N}
zIB!>OQGWbMd$aiQhgGeK85R{M^-CvQ4_?A|$6EX9KJB2}1!1dd{U^R!uN`zd;q7UI
z)ZRxDr~f^-|9^71H8jsJ6FCU!wu>m<x~chSmb#Kp;QDGMk<HE}jIB~ZD;$qE-uQQ-
zOR8_i)yWU`8wmXK4m(i)GD^GJzhC`u_oJd2^%6=>%N|zzIsD`DyQ(MI0;i8YPTKp@
zJGm%*lg*+Tt*je0Pgw19DV+4E;N{otw;nvJK9zm$>u&k7r|*vZnbP{eb&uQHbB|6H
zOD;2c{35)W%f`l4%cjtkK;RxVoc8p=QH26UP!==b33mGP+`YKr<JYHqBo(t7K4yr1
zdaytCsE^QAUiUqh*mqpfJ)izd^ECTx(d!nKm!vK3&$u`3QGNf^<L#EbOV2Oee*ET<
zc@~>C>I)q{rdarvM~5p)CB2zL{d~Mzo1AOr^v=R*Z&$LdkLf(Jg!iY-)>O5;_iwxE
zWp~@(fBozJ%2PjHddlC~E!EW@Z`t(!_wD&p&3!7@Pgp9kX2lz8jn_f<)~s8z>doow
z&7zB^EN@{dJXKx&dfF$8-o1tvp_5lgDON0uo4>h~{kk^KW2uk_4>f}xc4`N)SK7M1
zI<R+h(A(WgTY1x$-uf=G^4~6{t-ROv@dRJf=-a5nuFuxUExByVL1XY>&-Ra(k3UOz
z`q}wez|-D@;v0%ge0Lk`uAN;aez0Cyu)^(Q)TBpD4{FL4&Upt$x=XBmv3lz)2?y~P
zd)dA}eB1Z*!}jLc#r6_EH=468hc?3nEq+~Hz{KxAk;^}3qScAlnnCRE=1$GJFS;_<
zLi_5z<sM(Ji>}Pw@GW)5Cbz<#)cW6Vx7X|vhL-DIf{<KywC&%N=toI!l_D%(yUO2L
z9+E!qPM_I!MLoX%*V7y9Wf}6=p4jRwnfJcQUT#er|M$`(OWy=qY|WGrQvNF_ZpVA`
z;$fL@Gks-0{JU57$x5T%>W<`hzmn-)H<b?6$4gDP{(ln3oFhJpZ|#EjF8cC#&ZfW3
zx(%_t4QEePZ&_z(;P^AryK<fc^X?b=SA8n>=UUE*z9DgYk+ibfPC2zVt5ZH6eQsQ2
zZ$W{tEh`K;Von$=<%N{)LZ&7cSD*8{sNE!3$gtl)yjA?VwcvR{@hRfDHFHw$9s7Ux
z%-a^W!nYzHcW~sd*rsuG<$)~^n`UfW_;xc#%apf!Kc5P&cy=lI#I0%`?aSv*Wls)R
z{_>Kw+LHw_<{F-r?><gna{c}<8T0$Ux&M{C|9z)#ceA|c)W0*%fA+d6xcE*C!y7qn
z7k-ACy^QO%o+(f2d$Cj_ZR3w?ruNs@iwkVORkO<AYWDRH|CZ+N_~T;HH)nSH4DQ1Q
zuM28dPjFpbq4Vq!$DKXDmt1m}s!lFgr532$d$7A=O~Tpy=@<6w+q$P``qZPTQ?67g
zJ^1h>Rmni}RO+Jx>(upy+23>*MMl1!uYKC<bGGp#qgUTW^&h7$EslIQSE;30yg5K7
z-|p}Z;p&6j_0J^RG(|g@f8P4AK;eJEdxajC>VsuohTJaF8ZScAXT1<T$RZjcGBt^<
z{YKx89eZ9J7hitnuju;Yf=6~e+8JW+8u9sN-s`^s2dcl{<==n8;mLuY<`e2(?BelZ
z)4AiyrnM?pJ@wRArEPIrTD0Wr{^jO%A3b=y>(uKBazP>AW|}wz-t7Mw@{PNcSM=)a
zuH_;}H4hxg+`mWk>{Xt%5$X<xz8|C3$$t;LC3HPU@Oqx$-%a<9*Z=#sjmO7t-_(vD
zryp<l<CAU_6DZ1fdfjxVf_GtT_x``;{iC$*@neQ>$CN>BI8asuWgCIS_T^ellNW`^
z-1!=EwDI?%Nw~Ak&!^LovW?`U;<p_<heZTmu^tXRdNAXyW6|4}ldoLXekm$0eSO=)
zR}UP04u3qX`}(?Onq9{CcDsmzZ--vD2F_g@C?fXkO0??IwcQuG&o3{#zxvrZxrtWN
zah>Pa&j?gbTlV66=Z*VGx0O<~7W^%@`zLjN&##p{USZ4ZUtV}KHPP|+ld4CS$LH6%
zvzgfc4H99Tvm;{9Ci^8yI|9B<4yml2_Q9nxHY6iR!ai)z8V#mto?UlOX?IVZzwOPk
zoXh(sZ~B+;w6b{0^KSb?0l!yjIW4fM`*D4{k%l1S>20sQH+<`COxdtVOYVG4-MX0n
zZ}sONy#Mc~Yo2%7yMGnp*Wbk*UisZ$@m-XR)B2W2&uo*H1?zn~rU(j9P}S7hNOGkx
z#q5ft{-e#lwflY=pEocI(C2#RArLSlSYLGduVB5EUEMRYf4|i``i3=kf0tE9Kzf{o
z($|{uZ2c#K^UbsNuj<<1^t&=tbbfNV@-GD^=~vZC_sj+Dx*qs8bGV+_96rze*TTgg
z`tNlza&Mosuq*4f=gXqK9czD2Ed3*~Zhxtqd|h2&rteLDzm;EZ3N6w2eP3va#{5_<
znL-c2Lo42P6u9NqZ(p?OY`$D@oyPv`M`8?&r_5@twEctUrF-WWY8j;37XDfvyu2a$
z+V6=@E!u}p%fCJPZ}!8Ywo_Y}O-~lMYNYMxn5n0fAZQ$agh!&0BhY2Tq*7yVHtm1U
z|2&+1mzOsv`>@FR9!rk$`JGeFFWq1+2d(SdKxvbNA{}11{atzNNP5g~%jf59f}aW0
zd+PIftlk`O#o+<x`#<chb3^L|V(ag6zI{1)%jvDRtV%;X&wMFp|MkE_UgBkQ>!cY!
zliz=dZ~b^ZzerkFx#PR{p7vkwuP&ZC<^3Ior`8K>zGYj~{Zi#KzwdpZ@pJdY9r_ag
z7CwAoWI2x~ETsJ`M{|9F!lp^>k1MVn-ch-QwQkp<8vP@go9lvAnEM|E&EfL-xyOsS
zL4NU+I-LL!`^`n6b=Olm4P1Z9Wb^Wd&Rb^VztxNJdg8}VM(c%TdEbQ9)yD+e1zR1_
z+<PU!b<Q(y7S$xTE{?D;`)jj#LbvEy+;dr{J+J&>^IeN?=geaE6-ySjiz)nikn<z&
z9n1ZSe4~JcU%Jc=njc`<zf<kV&slqad!*&%e@@P~fsJJ-7kpXm$SOBaNp#*0CBn_I
zp#SVhwdS^UAC>H0ui^>T61@ILVEJny_P0l0b{{!d<DMQVqGlmjb!ZLK-<7*UT;sRa
zmQR1R)js5OltAFC3-gk^EA#7l!|k$@8>Mp_+)uyU=dbqXOO5EAJNKB_jeaQ?-n97f
zn|;yg9Tr9rfnUFKy0}cwJdtH9bN%(!gYBl$v-($AE-n{65Z)qg^*4*T@SfS1KpCDE
zE&Ez)jk42Q=P&wH?bxDtWm`;Oi~gdw4-$$al$oZ<*H2mgQK+%YV(D}ax0WQaE)K70
zbFzHdG`EQ;o|$D_yWw4iy~|tajt^JfMNGKBy6+<I1-(sDl8XYiaT~I0GfqCU@YKRQ
zwHWEkjPm_2C!2G-uV8aMl%G@o{%x=OidimKISzI^Sbr-@{qq#1efa9YL}D69U+Whj
zHFCwTr+b;c>R~CAQZC%3Bsl5D?43%7#5d04NOn<BI3#&DvrppKjgFfRN&*Y7#9fG8
z|818_)oKl``|@k;BjjDKRV|3GSK*UnufOk5BKff5K+)#X@H^6ra{OEPZ{1XkVrF_`
z_Qa~{=qIKr=av8Xy9(dSkLH`sKK<~?k5A_Y{r=^@E~oQ*`nMCsZ&mip-*w^ES?`bk
zmgb7Z@0qF0JXJaW$O#8mi`NCU-m{fVF6KGQui&`){AldVzALM{pHw|eUzU97TVK((
zuJ9aJBbSTOsm+!lg2&G0_UvHDkNNh#HM@}eLvqZNvj$I(->;jpTwZwI{~vp7SZ7w%
zir+uHuZXQz*Cn32X_aS4uh1S5(Qn^G4^Ih|`I{Bc*uLCD)}n}rw)WD0{3Ytp@_35T
zk>|7QZ}%hy>W8d9cSm6P1EK4!3!FsPCdq!?CGjZf-Xh*~eU5Vmd9EyKPG4s!PZp^a
znR>pm@OSSbi`MITM|-;-i&oG05d3@9j>zS$fA*_xJZvF3FN0ZZV~q5=EwXcY>i2K$
zklZP=``P;AXE(6ixxFbkGrBVN(0#7&cT2wfH#uD;U!QYx8i(aNyPbUM=XIUVJbV|i
z;W?wx5sjWCYu3BvVOwQQ8w3}gx)**pb;YK~Prq{P-y$$EyeV~s;u0qjulE7MHO_k<
z?BAsL=n<Q;cAm((-00@n{?GQQ?@a94;jr(<I*uT|yH+WjK~4Izwp$kbS1>uB;hEQ5
z^jcVPQ!LlUxlOR44NV?!bDh19nC9`y|IBwep<^&hIo|e&fBYGiD$(y~`*n&{u`7$}
zqbqSqVO@J)&s?#brP}(+pO}knGguEBv^maM=J)eX*X7ca0j@uFPD`q-W6@43>&j>j
z`kD7vaB)qz*pr{1G9EaDD{K<puts~|+gm*EO|$#I`<z{xfB!mHr0Mab!oR6AE?LMh
zM;}=nemkY4#v=4zy;9w(Z||mey*zy8^yH>*k1x)VuK0LJ{l~-E^QY#D%l7@TSMxio
zdQEt<hf6}@`zbMxL}FH+_uD+@)ak&hxg6#D1v=-f77W^G^X861x8m|&i8nvCdp7rf
zbv)6@=wTz?^0oh*`=gg%Zxk2>Z4=y-&-`a|^V8h<74|F3YpPB9@>7HFhpsiS*<t(a
z+^eRi!d41dM;LM>?B#D>HdiVO<Vy38Y7gAZY@m>^Y0KXtc@y4T{k`^Y7+)W7xw-D@
zU#2ya6P)^0HaAc9jhK{GRym7vw~<dpP58~dJL&?H^5YGCB9B7H2(C0LhcDs#RMmB*
zu{wR}t#a2a>2qFN-)jc_Ep^S3UQ;W%Y)zm{a%AM@j6WZb3++(LVFZm*fCgT=9%Sz^
zQ0@Ab^yJmi*R4ttf{dLXB;~DKG(A4re6`Yk<$6uht^3!_-xjC;SO%`we0=To`;JJN
zN1L@o7It0m4`fk@>pGQwdtH+4#(na6wVOq*zteNLc2=~bI#KVD9+$YnuLDUfs`?HQ
zcT`@k>v6pyXeF&_oECXT!is0(T(#uVzpfoCc5__3DzaMVkW#S3u~|ZM!~V!WI#KlY
zzvZo`w~k-AJ$dyKrCZ-u?9r`T)%Cl5@%-bRJ6?a<_T%T5ciTUFd8+=T_UhAgtJ4au
zyM1`WuN*)6HSo#>m#<%=<sW_8I@{vsk<Fj#&OI!icKk?g;<ta7IeO1;3$j@k@3wNA
z>so)?WwFabYkgw<_rKfnLm>12kHSy;q%MW@t<TyzFEnG(z3cYN?uCCj&l}is>*;f2
z--_IBPJh*H6Yb=#^Rh2K=DB@sT+^2a1t~fmE?=J5R{T8Feky&Y+m=v)5a{F!lly)W
z`nhX3qP`&2kKfjPR1%n9{qK$OPXTfJaA%Q)R?@B&$w6@wl+xw~Bz7+DQgF-lW-(b*
zTlgU+=kj9x1A1xKI+$Oz23E#SWB#z}(Jb~WyW|QE*7-aC^;z|>>&F?3qFa^^7f+q?
zb!utr_H^y@@eFsogKh=i`sI4Y?Be_S=%Dutvo<Wcq^@yW!CB-~*^h7Q<l}17IQ!;t
z9s8HOkb^I#<G@XC!=g7PJu=3Uc59y>K3+Z1wYK2liehP(M|-o+zODN0nt!Ki=gzI>
zdkeqrER(QGvUKZ-@0qB~{P4P-&%gVMb+4}KiElrCe}2Nt_8s}Jx2-*{^L_W(gF8MR
ze)^WPlUFyfOKMHT-EcYa?drO6T_-=QCm(B4{q%3r0TUKimD9I>eQ=Ub3Fv=$)y~Y?
zjLF!%CF$6)tJ4@<G_FaC*&jAacD=JaZ}s=u!)XaGR^9ycZO6~khPgpo_ShCz*tq#c
zY2|<Ck?qXOZaa0mcaiDx-$C!hOQPpaRC>31bKsNYuB5Ooj=&?iA3u9NbN=+LwI}Sx
z{AcQ---9PRExE|~?7Qo3d#<3C=xqXTMOdJPTh9S#?jvq^@btm%cBOkcdwGIq6{u7s
zdP)8-H`dn??2=T<N?5dN-YIYG(ha*dtXlq0sH?#3?z}1~gV61Z=H2p)(Ra38J9+z}
zOZA5}e)(wf{n7qyJ^#_xuIq2sD9J3kb+w{=>!w#s3fWVN$~Rn+O`TsBY{&oT|5Yu~
zGvB}dDi6AryL82!u-;6jH<@fQ**r1<^X7*#pIF0|6L8CNjYsXfmUPkeTK7U~`~zdA
zyWFXdnHV)cDA!rzmcYH>KVcukT|ezLcxb;rbOV>(y<K;t)E~7y*(LDXiFxCy`&BI8
zyBr+0YxvY^T+#|CIO;kzOnK3xB$wG@kv`_W;c<PNa(j30ZF}goe_P6b*{|>VPg!`{
zcC5FZ;U8hXc8=@Uf6gzTUMTDSsr2R1@_*Z^_Q$5V@|;<rnEyL(U1MV8_vhRZTect1
zU)%h#^WKa-Wd>^PJ=6Pjd2cjbmHx!8cI)@6w^IcpIihw%-V;|mKEa{*!9~T21tDj|
z1iv3W-}dp|7rP7kmshU|R$s7l%f}_}KGkL4SIjU^Xxa1j@m>LDX`SN%`o#fP_g*gf
zx#QQK-{IFLZr}PgaC`QXd9S=}&rYxKY0>;GdNfyX*`7rersgq~S(`b{O&m{q&(?nY
zvS-<+eH_s(w{GA1?S6?Zxqj9B+{dEVe<hY*Nm=67qM)n8vVPvZMK#khTz0cU=O=t#
zwp%S&6kcPx;_1=1<@amDQ=o%i-it#x9&}wXvvFOuA5>&}2Gy2<hcXjSNgXwu_V?fK
z_n%+-f#wv__Zav{Hran>b8HdazW8io+cot)FPTrcKG^Rca(u7w-KF;P>Lu#y6*crd
z0{tJWd^wT5s$-^+l0iVadPl$_gUtdmfj7VZY~rsDI@nS5N^|d`+N|AqU)C++Fxe#X
zSfo~Tw`SLU<#(GlEqc*D)r;}*>(sXkq8#i~E8~ye3q4b|nxkYbL&+ZgjP*9>1+AwZ
z-~LwqNb9@98;_=QhV|a)aQJ`rlS93up49Zt%6%`Fbh{r_xR5U!#v9*$L2#mn%x0O)
z!_yQGn@d>NG8Y`VE`OZe=eY4b=8yZz+3$SJOZ}K%Du3p-xSidp<DNflj%v<deRucN
z?_NLo?p&?2uRknaUn{d*FI_q7x{!{KY`$T|@xvW9HVo18rFxt7{an8FmWgeT$?p8>
zA9~=Hx>nR7zR-{L9**CyrdDn3e|cT^#m30~Ek_T{UHtUzgDs(9id|uEKYe|<TVVD^
zcF)M)PyhbNwccovwRg{5#bxYrHzKwlFmSHkAJ!s1OD{92upvJF=ZyxzT7!*~^%w1}
zPHZkOUt;!R?H{SL-20ALy?Wbn%=YPD&3*SI%+A>?R++!+?W*n4YEkFII?v2qTV(aU
zB;HAI3&-^*c4lU(?=62`$zA^P8Jon0?X&mTzq`P&#@0CSUR-gGL0{~QW@B;j2Zx>+
zR-O;9Dc@zD_kDeTwCvs``I@J5D$Oek-=!PcFIo$0kuCe;4DNR=u+WC|y|$Zp$!376
z{dr1P<3WR{Dl6?kBjd|YK!(DLJ|qe3n2_itoph}GM~wBN>J6p4mNor5R5wYfbU~D9
zRN(G?+wMkpu{~g4I{#6L*Y!Vd;uGT5TYQ~lHb*dbk9d229LM3ks~7(G(3x8C>_^oc
zt2w<#mhfJ3Jsq~-?GKfTe$UOXXH0005U7@lJRX0j^|kz?XVvv5-<EhPFYD|2{$;|V
ziutY$4^Ob3l-B4v-zn83@uFDqVw}0l`#jgT>G7d+Z$54)pITet`;#a2={Xm{g|6FY
zomv<CZSJFRUb#(HAM!W4r{9@r!cgAy_ru3soIB=-_MF}w{@{~vcER&UDtB&L1xVIv
z{H{x`{QO4rVbD>{mcQAWw?p2nUGx4j&x_?>QVaOK<-HE=j#G>IZ1%8Ij$bivhrZJ?
zor9lPr8nkOE_=FQpLOii37XC9S52d*C7wNZ^2Ub~Je7^#j;KFB$-Qg6tv$=Z<G&MM
z-8&Yh=oYB??|sDYBMy}xt3o&S?3(}cPt0l87okU!A31%A)?YjAuh{qNGBa9N%zegv
zdi8q$J!ysUwSRxzw!Hbfu<k!+jNj==-`^imeK^06^V2uQ|79I<S48$LjQdn)>|3E&
z$5mT&xcy3`nvL1Tn%_%jroHboyT9LDYyW(fS@OR#c5YoBHhcQ|h;T+@^RQ{#kG4Ml
z>e&9TZ1$ahE3O9%_IC<AeR=lT?W`S(?(Lho`%m-Zy#?R14BqXPYJYEU*E)aSp0@Y@
zKepDqxpg;tN6qt1d**%);VZM<$DL`Jd1G(xb`vc{FFUaf8#bF9Ti?28zQF>yZ(8>m
z3x8`q{q4Z2>GaWJ>z`gP(GOnp{VMJ*myVnLtiAmDx&!<3ZvKnazNN_l8xOY+d9V;t
z(CLbjo<3iJ24H6YsuBO2{%?;`p}}+;hIgE+*dK;6i)>|I^Ywb2#<I^YU6IQ3-<r*5
zdb37cc3H>k`8Bnc`O7*#Ebsq#S%3e@56(MYpIL1r(0QXu@ZUxC6c49X^=&d@Tm0?v
zQ-4j~BztwilH+f-nI=5c`+4V!^Yy|%FUwQvUGB%YzbXG{*YVx?rv0bdRf{z4*++LB
z-}-*}kC?79FZ1$u_cs5ycf5X*I9Ir+I8V5~z`V`Rci05Xojj*^&X2PvIPW%W>2NvS
z_~wzBx-E-kq;$d00u8<Seuw9V%O9LuKJU=`|G%o=X=}3V*PL%?xcPA3e%t>%>*k)H
zG|zr^%MNGmB?o`29jH%cZ&~-QmsR_>Lc&dpHIYJ1q1um5>|4E#^Q=S80XCU0b9=>i
ze_ei9qj&w6SN*5=)$OoJveEnPYh<0Zs4{Y0|4-hdjWg{ot#OT=xMS1WlCQ_w1?F60
zV4S_zu+l%&h(UAB=CW&tXU#F|IGL$?cYn5q-@NFQk8itYR=iFVi`&1K*Zls;DePY~
zkN#H-JAF>aOy7E^!>^kccYgYp{X6Z!t#vDR%*mX$wn!;=%F`;vTiNX|-pl*QwjO`X
z=6Uq*>(fyW-Wq&%ePka~IG@Mz$Xouf86UD{{M;5G9}w_;Zmmz~oPFBXw)zT!jh(77
zf$yT5MDPCfJ5}{)U)ixPj<C4<S$lO&Ln_n#ooh;th2Q)4>83_a<q4T7fBwD<{;~Yi
zO|k7q&%fuFJ+${iMCE_|?xYXX8zd@!zk5>pW65oi%HM~dG3#{w&hl8ackO<E+p^7-
z;(KoGF<!?zQ}unk%yxm<;^!tETgN>$v9G}AkOYs1M8d}>+3Lmncl;DYwC9$}Ykjz&
zy-(`Or$^Jc0_IGG%mzhEE#=z+nwkR5E`lbPCxy(PJ2mTma0+BLXyyN4r0(ut(>U<>
zq5iG&dn^v<-qP-2ox9GH*Z#|`-2&UnVhw-0-cGZ2T_t}`D=i}9wzx6(mfP3An||M7
z|48ZKqmtNsmdVSxe}A8O?8moLF*V;V@!5R4QXHn(!C~IA_j!&wvp}>TpS99_@yW%m
z+ln9jk)C#7nL*8upWAo*yY_q&ui%A_vXyUFmd%`(dUG8|+mUbYYJc-DRs9;J?NoZL
zD`2m~&gqMG&Ck{Rwtohr>OA#Z0=c5DHy+!`@cD~e&%Nc_#&k3@?N(HAK~O{W=0k-)
z6E=ML^Uw6e&7G1nZ*QCUHZbkIy>9&dqpR1Sxc>L!T>U5ie*FDzU-W&_r<XtX_dkvQ
z#C^WLMy~Msqa`!@Ta}HL@jETvtymbze&qM8C7H};j3ef~3z1H*je7W}z+PtVHintr
z*Cy3&keJx{MAUnt>0$Zf$N3Ly8_OSxcbk5A`eNS-eVg;;W-|)3uit+CInRE2f&94~
zZS}La0_A3xhaUa5Sa;*I0Na<R4)dN!I3k#uG3l4WHP!3x1zyHFeNqi4rQMTq=EyHo
z^*g%ehQR5biBC_ye)?Ka+&NsoXP<KFE*=ZMWj#lVJNX}GIVta%Rj@yFM}q&y`VQsO
zSN~V9_`3D<*Z$T0nb$QlZ2u<Tdiv_`b@S)-F}u%YM}K~|`eD}&d(}|!buX@NQB7ZW
zWY&h1xtxI))!y`NsQdHwW85c^*zHx<r!Ief$?jX-db5-liLbX+#fR>XslR`%N`9-n
z#$MxszCHh+Z@l;;>t5vPU-l~h5&~n@u6zFxdaqLRWP-nand*1xIr1079ASO*UkjP|
z_dZ(fsH>m1vf1{WSF8Fu57{TWy+Q1^Kr0?V^OK<E2%f9XgIbW9Mm~{?Eskxv@_*&K
z7RZvC89^;;yI*&o-<5Syfp1#(ww5ccdu+cZ?D!Xw{wvt@rGIvs?d3;@&r3ggR;|0n
zreL4_rau1dMXsN6mZ<)Ic<Gzpk4Fx3YaCCPvu8bU$_jUB@j2WeIWeUn@$|OIM!JbT
zGjf%fCT>2zSDl$paMBc>{SWVL51*3THPh(0z(&)^m;POs%y*q%_3vV1#7sNycZ*-=
zZ=Lu%cz6BTuI$Ynwfgt?KmP0Z?iq9VXklJ$@#&%o^MdBIx$pO}S-#@OqkHB?7CY)b
z%Sx)q*p}~%I5hdq1CA#wEgs*`@ot;ttC$uOk(Tf!L}raebm79J9PK~4e-G|Czl4K%
z>+;pRB`1{E%KtU3Yf<tjnGj$9dwb}LFE5U!Pu!DkFH~;lv`_7ns`Rxa&k_!$CN<@>
zee!;MuX;|~Mm@Z)6*{%Q=TiOA{nb~etq<MvZSv%_)3r+Tq-Rb@<CHT~*WY$#oy8;@
z34fJsM;;sM78))KyysXUZ8t5KFQhD2zx|Bt(}{aF+l0Mar!jZC`Twl}GKI(Pc}+80
zR319vEf@bYHuFQ<{k|RA+PwJVi*K4Y=9utrZn}Qr{iTk`(*|i}3d{6owBOi}cw@4O
z$n@>y2e@}jJvg>)*OP3G6XDxKw#4}SJ+bWGjak>&Gc)H-oOmtv-o43YXEwd(DvEzq
z@W!i7!t$i;M)Qrfd}`YcFKkik>MgKXH|?nN#_x~4vT}dcPx`U)>&7clH+G-p-Z<Nb
z;|#C8#`0w|HrSkPF>o!cS|{FleW_O3j1||NI`7_;&@QX;W?xdd(BDb-iuW3?c5SKI
z>(@5jTfHjz=N+~P>pin<bg#EE`JD_E*%!NT)73SiYv-5DYqIa}pJp8M&&Zr@JM-fY
zr(Ut_k($_%zFq#n<aR&L@6TioZF}?glI(B!t$*|`)t~P=96rBN|GT+k-5c;w_|#_c
zHqE~QjqPzCuP&G-zWvHUx%8!ngh8WQ)7(}vzXh$m310H+HE0ov%GGpGQ&$8!y7lUR
zNI7(B!bkGZ*YkTgLR>5qZwW4JnNb?>r^CUKg&RB-D3!BvUeRg;u`;zzUG?+vZEs#A
zR-{J<ub$xg<NadM4`-I~dBhm=9sYAz=e*<wgP@nr3#JN2FV|eJs(+;N*;PKH2RpyN
zWj-}e;P>1hUhSNf_Dg5Q6$Vxb7I3V*so}JdH|zGby6?`sH}A)$vn4<5+b6NBP9};)
z$Dw&<quA?n^V!xPNEDM^E7R#GeQfdNU7at)jAhme-SWPzUhdHU#qZKR$+-__-}Uv9
z718C}J85<FwwJqC^VjhIPWscy8Btls8)hTcW!Wy4{OzFYj9s=2tG}xxMAdX0wftC?
z7WQP;-nR>Wnk1~ez0thRSg+mfagl?8-f#AZcJ;3RYvz@|71f@t*~NYEw`N!NvX)BC
z|DUzrU!QvQ*@Gv=Pi?QVsXq!SH{0?1@!gt*Q*^IJs;@lXmVCRFHB7H+$?T&KXJ=1l
zKi9f<vQ|RQA?FC`ixQR_*A`gp;_W-~?O-Rb-IK=J=y^A;+!vCunf_Cu^26O}=W|Tk
zwUj;x9CUwj&MRWm@~#m1X*Y^L&FhNTar;e|tmuZq-IhL<Gv|sg3#i@j{NR>T6-s-V
zenvQEF|!K>=>*Q1AH}+OlH8#qORh;wGT>2uoPPRzPC$*&PsyuV*92epb;dKyudpxa
zeBXcU<Jti0e~zE5r}#hp9QB~~kXP5E%|_|PzDwR@w+Fjdod_=6Td`^Wo*%zY?tMIQ
z%T4yxM!S@Bss-5cbC~Ay@?NmrCU-N=v8sK>1>*xP4G+~?cnnS!JdKI961n)hZgO1M
z!4t>d9)I*%F)*yovetfIT!rHNTQ^l#{h$8B_TKH^Grz1YmVdbNu>QlB$Kwya3=W@I
zd_rcPN}9aN{pF8Ba)NpD-)N?-zj^3h1*|l_#1C56^^4hdod<D6`Td&DzII5hjA-My
zF1EtG!i5ZRHTg#}7an2#IQRMe|372fG@T5Soq7GQIJm^ryGZ9$G`%tnYzh3h(qfV&
zzr5_L_WS-Jr^L7Y`LcxnUy&)-`~BZqYI^Vg{m}hs)mP)ZE7>zw+}fqF>&Ek=kMs9;
zcN@;O{lAg3@Op;2ZC2C!hcWzGY4_Jh$i6jNHG$pWZsz8zq8GD0MSrkq{4KdqrdSf~
znz{XlnuJ7{bRpxt4(T|@d0em4lAqQJ{1?Busfx9D#ih{5@^zo=&Ib3GR%b2$bni`m
z&yM7~0_ABEkM#5c?#*U2Qn<AGo8ufaPx-nZDzo`dycS&^6U2XObr<u`Tg<<o8=pNr
z*-Z1ir|yl7nm@P4%5(+0th~(=@XOS2uZ>#Tl;cfrK1oaum3v$scA#ahs9_nu?7SDU
z+qVn-dLCl3`?q6{)rFvqrfu@C6lKd+Y`=57_B*e`;htk*?{~)K+8_NlV}V8;m-E!x
zbz0w+?~v;#`_Fl~`2OTIcfLEv6}zliZlSi+=2@!wgl~7>>3%(%*=l;6EqeEARzCSx
zX-lQjIO1*1<ih!V55K(IU08R&rt<e!;bfiu)m;yqZN5K|w3lpn(Q9q0^?#da>WxX+
z`+LH|zWL?%^=a}NmezWo>3-eXu;U&F@7uP<)me-ESBURwy{htkwXJrp0lU_riI;si
zKAFzGwpWVp``e!GyH!z4lkLC8rio7bQ+ecAjpNDk#O)_H9GO=xw12~#w%0|cv@42k
z^TwR06x_YdzU}Spa|y@iZBDuIutVD1K8e%jNG8|SV6lCl|1F<(Su*SDyi@08Z%8)>
zLpBEmBeyjejz+Hha71)v^PZ)FuN-!32i^W4vhtsm_SJpro?mZ*njEfen%N@9Bf@y2
zx9mbJh&n31r{jRb+>Z)+0r|hT_$Va=FpCz(3JNY$dAB=@Q)kZ1M57!ptv`Q*mqaR0
zQ^+pT*fdQ=QTw03rcLLgout1iy>GoJ(;;%^|L?#5AO8RAe3L!tS;G#6e~(YuU0~I%
zIL5w1Hg}&wU8nNB{yh%QubKBhxOG=26Ex|t+bpoq;7{N7rhGr;A8F<a{C;9v?Yv)1
zP)?Y{S-7uqom3=mx4V7A(O{{>G>7svGt9!Qif^Ux%zdc;)~0x0>M@_qg`Q8IADNbW
zd}-pfKP6}4r#2s5XgzUSi9lgzwV?R4zS4`)8?>^x!}8CZDpxQzu)CNg)?HBYCgw|@
zR99Sf`-00^@mpeynfTX9HAqd=4N$$4bM?cs6+74Q?ex!yH2i0`rF(sk+{aJ@&3#tS
zj$c1?>xY#`-}?4iPkxaX_pbcjsJ=GKPCs?pb}mn@)fT0brFN`f|L5wV?9?o^ZC!QX
zA-f4x@~@Unb$)&J>BTbd$}e}q9NAP3ms{8UTEB4jnT1~8-*9c}eKu3{g3VUNSt{mE
zZ+mqE>XWN8SKml{u_du<>RM&>MaMq1>%QN6@06hL$vW|=rc=**->=BejJT8Va@~%V
z7UJ6(%pZk4c)eps(#PQV`%HVEK20^1ei9S=`HkR0sj8mvI{p=j9UN{eu7^*Un(X^x
z^PQIV@X%vFMB_MOBjR^u7wXT6w(+SdKgUzE-%ik8E&tH@+07qbA7uFRFNEh`MIQ6N
z??G!swm)9uzt;V%(B_Bh_y0L8l+w}Q%xk^ndU{c6m{-}mul+4qx5MMBE^j)!eqZ7H
zDOKSQAd7}iDUmv|z{?kB0a}Uc{E<cf(V`cR7X5hiTS+f$e)%k=!dLpH0SoymZWn*r
zVDRHNi_(hcHYr=>^lG0S<l7OsoK-IWUcfny6Q2wdZ(jLrknpnhg+N{WzUKVW#l8|-
zF0kAWHGL=@DZlUIy$^S9xz3Z)`jKRC<+9?-y<bkM#?-!D^<`)F`G==wPTuk9MPkgo
zY61KAb@KM_YozVf{_Eb4*ZDuAM%+H#u66$AdB^wj#T;EPe(&J=^Zy(E|9iRr$KUSz
zAAj$?U;N$g;>XAN_NRiaIsNz6a}}nCE4;hF#Pa5&@q9MzjjU2f!q0Edm)RI~sa9g&
zt~n)dc75WAnt5Ne@#%(Y7fTuGIj=W(S!~<U`=!RAdXN3)_M2B)f9~C)yT5*K;>Yv%
z@1Luk`c1HYp)Q}<zMuDGZGQf_ZBhGDG{?GzvGBIPxAG;vq@Ty;URr8s`J~aOnr-dr
zLw7!`oguV|wXSidgwTY}IOEF~E4_AIP2c`?he}6w&KbTB?k*<`a(Z8NHE-1Y5TsUX
zrD1wRz*x+s;o*7>rk=FUFTWRUU$|+WmA8{k{Fx4cw=dHd-1zJkB|3*MzwPrw-wn!p
z6on4S9XYkN)S#%)_=iq4hwh<07c$m9p1*XJ;METS4$dZXZ%ZiG?&U5iTi+LPbDMZr
zX|=@Z-(L<b;aqE2n3Y`;y6tvhrm+df>fT$4r2&b~tlqzWB^*s}w~P?n9I!&b_5F;y
zp@tf(&De5QcK5AJlg`g+aofnhuxg3<siV8fkA*!~t$f&gZ{_1pPF$g1H%gs)Z@bFY
z>unByA(!q&zpUkl=DMAp)-E;s_RTwb>-+cpQ~Sbcw(z#sE_>EURmaMGpJWApY0tmD
z<87M%Tf>L9>Nf9}4gaTqiT%FZoAt+6Jv#R<?2<m;_QT=Ft-bbJ-{p;;E8lG`F7`NI
zf6bx$@iEQ$_wIDs@2j)Zzw@(yMrM>1bfrA$i5|4_x$f`R>pu|-8I-#0Dt4~a@Y7b?
zr)}3&$=-N`N4PMt;(vpKw)^=Q)x#o*KFd9)Ik>iH9<mQzufDJ5$Hp#CHtpxPXEslj
zEPn6YzM^ZzcEk4zv|rD;w@{5II*~6#OiwGFGa#%z{6N<esfdj)89ym(Rp0R;A)V#T
zd$rho^*mx}y~mq$lQ$X^OEp~%i(KL~xrxXBR^yeq$0zRitFg%Z?a^Dlw>Mgqayyyl
zI=-1_!ty?{bLnk$@Al=t4ymt{Z&Uexu%+eN43TL5gCF@5yt^vXI7KruL={hObP?`z
z)6z3}c!@*!UXEw=+E~*$Z|@28|FG%!Ay~R0X_2^_-m0#5ic>EZ`MO@|&~)E-yefO`
z`lelh-xq&9^(#aAlR}M{x%=*!vL7!@S+_#=f$mzVSE{<pInCZgDsSP-TEC+I++^zm
z=}zoVtM76K&W%opzTW;JW%;X$UD1|r;sfM<PY-hnNlSIfUDq<>jMt4V>zJ;!PCx!=
z#i|)wAJzS6z3$L{B5K9<TfagA*Rg4H_isIX>N2z7_Y?*rz1HOSpQ8(ZJ>9(H$1l^O
zw%H$+rpF)u>^^^r-S*ckdTpu^Hs-hF+ueKJ|4eDTVX#2=T4`oR;kOM|GfaLlYA^qF
z)oF(Ry&OI-tqm7`MY#&gWk>TpufBHV=}g;CJJTz(-$dLL>QC!A{`E?0g{|ccqt8#a
z?zmc1cKYKE%W0DO&X1xO#ycfn@!wE?u;S{ek54b!Z)_|nElPJ$D?h5N*}Oq{ss6R4
z=9Bq+K2F-^yUu4@-~`@DpP8n<xMQH2ogO#i(__P@|0R^RY<L;`wqf<Y)=$sHr=Kc#
zygGgQ=?#xJtBXJEO*UR~{afGhsj_uq@%Qf@7n&yW<o$8&OMkW1>h12w&O7$0{yd-A
ze!aIkbNMf=NqhW&{kCy%?8E8*|738U)34tmu72NZ%CQ(3#n`%Ko1gx!{QPe6&hw|B
z8xexV79z$#h-;S=d^mKF`|uRYBkA#1&7O-MW&Lv0s;=wNp<iiAM)Qx%uw4D&a^lm%
zM-L{vTHCA^Q@zCI(59@Uo7bdX%!rd*YZj`+f99pAvEi%bVJ_Q!dkwOp`E_oZOGUDq
zKls`F`{S{Hwm%G>Ywp?pQ%f&Z=J?Me#|so6I@xVk$#D#E5_!3yCGo}dE24iVTO}t1
zx$}5sYQ3Et^n=?-e0!)_*ntHTeo4s0a0@%{-B6S%tizYzlzP!uK=_(ax4CW0fgZJp
z8DAG%b<A|1vu(fGG}-zHsqZ&pp2YM{kgL@_SLBd;-CJ>5bJnVh{(P=Cb~=iM&v}!b
zss1BE-n&9+&hF*LY0tK7e>ugr^u#{PSBIE=H@-=T*0H@Jw3=`0A-2Nm{9+dSp!koD
zJolsGHp)aTdwPHSD^q{9?wKm59=|H}{gT*Kl~{7see>yu8OxG36<KeQk((dS_grn7
z^RW*_9?~&<jK*jA;$p;O|FAE7`bRTkchQWk8+EzO*3INm{!(^ID}UcT4@SPTD>I)S
z$c=lkcd6XV1zYU8?8Iu#e<rw8#rJr~Hf>zFc<E*1V;;}*<g}X&X1|h~KiQU{c-z;*
zXJlm!lijU-?%oa9$f)+N^v$8K#>K0W|6PmX@LJn^;7l9GtxHU&&B_nGdh@U2h8@qu
z{+`Ve8?!|YZLT?+C)r|p^6P|c+g9@EZu8qOAAM|k`K`5+wNICYd|$?Y?A}@DwLYic
zwRX(8)?sr)^y2aA=$)TDsy}_roV#>KT|Mg(xit;J?WZ5ldG!1EsfUx3zfasBFZ*@2
z^0R`+pZ4vMRH>W!Ou&Uj>DPOg#GL6_dG1@5Z>!DNzG<(s>BmD0*+1?V_Wig&xywE3
z!FT4rYyae?^ZY9_k(zJ2ji-EGTwnI|zlEQUDb1-#o%*!A-=bjsT*c*~-BqvFzBa!<
zartAb>W{3IReFW1^t3v*KD{+%uF(SD6Lw8Abls<3PoC#Heaq)Umvq~{pRf6o-|bpF
zZRWgWC%&k&EsNh68S34a*I03E&AgU3FA~M(-@L+TzBwx5&-aU5PpV(_+?QR|&i{Su
z_8-rcV+-E9ly}F^$!It4=Rd%G-0k6~i*glDKkln|=%XHEbv2~wmxt=log(x7KU(UZ
z$lN*q<?8uzCu`e3CFk?_L3XbCyle-rPIf2^gKS&L&kU)ba5r>iZber}J!nLH@{(U~
zK*KUhknK>X4|Y2~aB9=c6}j6p-MQ=0Z$%yfZk0z(Ke!k5N4M%-=QDS>C77>s`?`Sh
zwyQ4fU%FPzbCF+iIZAgS*DY0Z=l4(gB4*i0O9zELh=`Jk^V>D+{g=4bkF&p|f2=&_
zS~I7RPki&WhlNMiym4@O_QUJ>+e(f5jQ$Us{!SIBS|BuuKjg?XX3-6vg^!g*Cw%d!
zT%x6B9~Ak|=XSK*&EKaUU-)Mue=}6FKPB|oy(Oz|h{v*4E1ypGU-9VHs~fVh(Yv(Q
zNtPY0P7Y7fe&h4L>&;r;iF5C=+`K(|a^Ag*JPUWf_Imzi@BBB#j(RtO#S?c&c@=Nt
zog{bm+u^CY#Y^Ouwm&|%Ohen=-}|8Y$>kr*J_+9OxYxXV^5zc}UTl52UPt!%8hZG0
zX$pORd#6FU`r5-2>k4dkX-k}!<vmjDF7|lmUf~aa9OGkbuEqX3qI`D3;yq#;&$l{O
zYiq1utt6)DS61N05^WR2IP01xUtQYrw^=&Z=g&5cU^Df;#QdaZ*@Po1pAJ+M?ldyI
zndLUsRy=QBil~9*qf5HU8_Hfw97*YQO#7Dc^tZi~`?lU=D|731?AcqeZ~OMmSI>0s
zmtU7#I^l2r@pqFKo~{0!?Ro$1RgUe&a)-1tV?PvSnaA`?H)KoK=-O>fWZ0Cn+fzK}
z%QU`@)!hpVzdqcZv*AkpLm!qi@eQ2GLJw~Ln{`w$cE0QMYwx{&msN^x{u8{>AVIfD
zY~8V4Tk<ZQ_@c*gJDU6T_iM+y=DtngP@l7RZU0rfOYg4#jCPN_CLA<h{*-5R*3;iM
zUgc#i_1_N9m}@B*9CR%437b7nr(COJ;NlMjH%=&Rz8_`p<2yg^Y0sIN5v3WO&gbq~
zY2W`GYM%DnD_rHv!B;=zDpHEQyO(d8_3-W9t-5uE59(H*Z?UfVxYBgz;`ToUA0Ao6
ztT#v%)cQc$+z%UcKjgxUrs)>0AMXlu-A=3f_0+s4@*9VkPFzR5+^VMa{WAmXZ-}ei
z|6%i2{h-JK9pStcO>vxi4HOa^Qm1v!X#JFPXmd(})6e8f=08eK%k7!CmGk<hq{5Wv
z88UZGIbMI;c4SwrufW#xr{6Tr>aeP?U&N=qo!`FUCp(vXh{VwYwxS!0%3aso4P~5Z
zeaf5v*||d%I=}bT7@MAII-j%mypizJjzu@rC9SoT%G_gF-ml9n+|;~TN5lP%nRVLw
zW456NY2l?k_oGFH<yXcm2$_AzvpOs3)H?R1#znVIFrP^(J2B@?-KEOt_9)pIVz2yn
zyIJb%v20FuHqbBRe72k=A@7#Sk959+Z`LyF<gV_L)R;2)TbTOq7>(`bhg#O=8rr2Y
z96Q>7Xvd44Gh}x1Y(8Xuz}|%OU$&5;x)L9E(lsqNg@nUWXKo5Kxq8iSp5!brV@H_R
zUbpHXM(@bN(nHT`eml#HbzVqLS|oq$xZlAQq1uKO7p;Eic^q6Yv-(2)8{rQhYtO#f
zApUgIx90PS2e#Bo$cZ%n`#i}&rNVUC+UT~Iucs&bz2do<_F$RA>fA1e?pO0y>-l$X
z$#MQY>rZ*fHWt&qw))hgPk(Dq+8nI&%;f00Ibjw%cWe6PyzOqD{p*3lr-V1%b2aV?
zi8Ot2UARNSSV&*(>#?s#;&#lt61<Upooe}E(~rjHCw{-(@RQ&8(KWrZvW~DFikjV^
z5p2G=Pu_&*Y2xdCrXmmSU2hgQ-0fPU-;#8#duH|hf6UC!a;GmmUEddWz_|U?!$wx&
z#8n?=>r}nDdH?tR7Z=}zf64u-&+_YEgQfGowxg5n9DR-`cl6(B`k{MxL#2PQ%A3rd
z3ANvqGs-{88O+{$T_)~^QAv5+iiPHN>6JM*A{RP@zg^WJu(Y>n&i3Vte3$3uHF$dF
zKA3-r$-6xK_$gWb#N0CLHMbu%&p4ylao>cMb7k>~n<|H7H7{?pKYRXB-<HN3v#uRq
zXMK6sgN;3I6-tXa*QIPZWGuP1&~`C%+{rgx+l}RqRnKIW`IMSk`*3N`vmJ9TGCenv
zI}&+xvE<c4h0RLeQm0H`{#odqr*^-)(CP@AJ@>eGCs`?~FOx2F`+jKNR(@smEDNLS
zXAeBv`tQ<}(9eRB+ZYA2uPZ7UMM;ST+pt%>^^|=uZCOiF<S8kZ<@$#v={{b1`f`}Y
zIyVn5k(<+Or!Q?gxo!F~-R&|NV#cmln9usn-IN<`>*M)4!EC8?+8XIj&(q9H>m0KU
z65r+d%=;&icCJ#W___SSMVi+)=A6=w*i*ngjV(>v{<<ggGws@0n?0<~=(FzKWz!e8
z_P*2mY3~>0_3od%Up!`G*yitnn~zGbxb#RgB<rKns%;gVrcwKvs`mOXeEZ_;3!5_T
zU0Qr|CM+~`H8h-m*=bo)-IWTRo_U#<AI!+?@Rw-b(em-eJS(o-m(mi`fAPrNvf)hM
zd~D+8pf7e)ZTWVavw1I`zSQ&Y^0||K{kW$yW%6^o#!SZN2I@arN+PeweKLNW$>#pP
z<^QJUP45qV{O3P$y}!kc_id@RAEj1)lPL}S5vS92qg}M{w&0WrZA&{dBPP!3OPw7w
z^?gQHZ2Ivf2}Lt%m$xlV3UjeK#&)y(#2=F{bLT3(wX|i;J+3}&##d{*=3Tk@3&JMf
ze)4_w3K_enlkWt&KzA$8*u)`P@j^77H><MHn^glbMfr+<({8P;yjbSWVddw+xDQFc
zjrG^vH}VX$(OdVrDDvW&_WAYQdckJO882=A)NHZY_C~*d)#u}jzBS)JQ1AKg^Yisx
zclrKbDDjD%+oG9xafXaFck(s11Nw;!ZtC_u8zX&RG9NITGbfh+a!_<~T$ON(>b5Cz
zH=-iX<$3hInRuT!cf!_nrq|YdI5=tR+6^k(^q7LPlQX@vyC+YIO7uw5_VN30_==1D
z5B2Ak_Njbg>aB_fnoN$GiKl*LFFB~0z0t%pWLJi>Oxl|0=FE$$b$+?BEO>v%<;Rcw
zARqVLg)=jZtGI9V-Ro+Lo#(js+IsDMZB@B(8zVNpvQN4AqJw`+g|D1O^@{JATV&^}
zycM^T*?YKcak!KAmz!~pasL;)rd3vM{1m16J#y}?wSSJC>Iw~X+_*?~>-o#KP4=yv
zbJRpTaoU`tD(e=14tW$@Vf(SP=-Q+Y=R4yc&28!bkamdOVtxT{_2Y92xsM&!bl+|K
z&>Q*F&e?UT9Z%#O!|SisKMobokLoz8sl8F9bn4NgC$l#sJhsw_knayIJbL~3fhPt#
z-?pwiZD3p5Qxf#dq36~uv1F0wIqQ2Pt+i*|UAklUqN&_v;Rj~fK3&UmC-`8}o5tT0
z3-@S#oA<UY*}wnTTytez@q2a8AFqFU^jZJBS|N76JojJIOj6W+9jo5BKnI5ktQIO-
zo<1RZ{&+?5^`iJS(XYA#j=FCCdoej;&z83rOs>4$&ivgxe4qS1{X<V`d6ykCKl$Dz
z@I~zVbx)^6UVMC6z}}Q^w>WF`zoP%g-S>Q&Yj<+mqxFA|7VEkHJXz*ay(o5mSjqpR
zj`<(w=3RJ}YLZnfoLg<jEx-Tp3*$$eU!=+;|GLF&X5n9TC0(QBaoL)#o1If%7i};p
zVxQLXRKXx7@TcU4H1@Q$H@jA9Z%@d(5!QWq%J0ug+mb&13du~U;R!pk{KDI89<PXg
z*NxIkxsx9mrk-{4iI8pHs4etBd`2f@vGIlMv|UH}CYw*ZZkuWHKtal;hN<`W+?MkB
z%P+22yop~#KIgMq)|LO<@78}84_WzHJY;=2|JT`d+PN3r<Y!!c_kKmdLw=KQ`_#YY
zeQi};A!+bO^mJX+jxRbl>y>vDIrm+y){C3|ODcNXZ{cIUyVPZ0eiyE(4b=Gk<-ho@
zpMN^cZ1=a7Z{NM*`a|X`IiJl|{Yr~IAGcJ*I8t4w@ua@UGCNCyQtKrm0_zW7c_8NV
z^#A1dpYN`dEqE33&Y`IRvcn!ye?ewoLmD2ffvj-8cS)mk!Cg`8E&e;Qdph1`>vkMk
zvl@D2%#5J6f8wpWpML~ibPv?|9JFU;#<O41^B2EuDFH1Z*qi)4@!>o!qmq}Gp66B;
zXlUe0K1=1@I>&kchUH~5Cti-S^Plt1LxzF>hRwyI2{oZkYGN%iTdf#WL--wf=X}Z8
zcf93FqmF;TegCheD^{@!wd(HP!}?y&f3e%!{TlD5R`|<?%bXCc;=iYTcqZTEM}{e1
znRsHiau`2){_O4JE2b&-n$wRiPmHSJIJQ2b?c>4IhI$2(Y-eZmu46oWvHqdSm-*Jr
z&f;eecZhyA)XOoPw$A^+rx_`03XL{I%%9P_^<-zn++BR9gPfnf?EaG2`E81mQ{h^H
zzt6Kzoa>U95G?q1`z(F0c>A?;c$R9ZY29bcT_Ez7^>U2;=Yy_=3+`X@(ffF#!Q`{%
zS0$+v&%-*_cO3h;Hs_YvKK7bNe=n%++n;*#??=<M+ut)@^DtQGF7)<Ki10KGy&ECi
z|5koAG+im!c5GW?d<A<?q^VHyb)N$_wr)1Cs@nNyPESXSUTsBf{+F&-t>-SrUZ`+(
z-r~2So&C}3$+x$s@BPvKWM8(_eVJzA>P6QNIq*c8h!;=OIo70FY?u_tAQzTno+rGg
zX8WT5S2TKNiSq2<Su803PTO|s4bJOk)!r)~9GSO%`L`p|D{Vi7Jh6BaE!Cm>`MN^E
z6Gk7&61nEz`<gnF*Co7J;VohMER;v8R5tk8{DW6m(+v|p+VybX>b;qH*}(enpPRa8
z-k<Kgxz?c0+q&oW9lkor+@;EwZzn~}%Kf|Pb@aUbCE@I+P1kLCma_K1!L_eHR~rA#
zoV{;OP3XtUJyX6g<sI3pR9kY_^-YIG_nfWi8*RU2=0yLTbM&qD`bQH#Jon^ZQ!A%_
zZU2Px?#b~J7}tJ?x;A^q#<#h9w*NK$lluGmrTp(T?eD+U_P_uBzd_!(PUUdiUZo$Q
zp|{1JsmyULJG9vBuE=i<p=)#9a-L+iw9a#U@ya+trY6iPdEwXhB@1VCF8P0VUFnl0
z={kO==5AGq5viK+C-%jP)5+VD-t96ub5o#yru1UNq{l)%elaS!GZ)nSFRK3@P}VQ6
zQ}6Dn{QQu*iG-b_^aJ4uZH$}0B}Z*|w8(vJ-19pRiX()o_!M9FP5K*H`|(ceor^AY
zYmO`agl=dhdBJbp$D`sumn_(pyV|}rGMpc}Wnn2t>9Ot~Io992AFoTYcHMX<`eU-I
zar*pf|9`2QIAZnWHQOIrYcKkC=;REW_l1=YUq{~F9W5(!#j3D6*2h1;xiXSJ(YH!_
z&FOD$ho-&rIRE9~wVr=B=D$j})D810;&?PETuCd_w(`L;4H5ROTiAO;Rx&5c?yAiY
zZ3^Ja`pvVfry|7a;(MKA{gphQoB0w8PsHiO?Pce_{zmz?<ID*=#AZ*Lek!<W?yW2A
z;zm&quj-2GZf0JWzfN<yte14(nkStR`xONmvnLzs>u_(L_I^5_)ZdzY*DqEdn4NMz
z`}$u-(ba!5I5VR7Za1!5T)yD?5?=}1eBM9LT9TIh&*GWCk1P4PYr>aF2|n?%dUNFk
zyN_P%<cP}uum5=Kx}MWV9z{Ie*=2lv{gFE_wtnc3Y2Ep7;*9-jQr_XqlCHfnoguo5
z&H1oe;+=D|o;>$@eP!=d1NGf{%kozC7Z&OoW;kb?@V;%_wYvIAY`VtvS=_Z36F2G~
z(=$B3;l>`HpO%Iq$<^{sn}5k`-{<~#cf+>EO?Q@GSL~4%?_7{t*WsPjaaGz{Nnw@v
zF}Ll`-#74O?A_G9<>ID)AGl<uosT-TEz4!wglk<{olzIpKG?d>G;4i+^DgOIi{f&u
zuiU>87p1&O^_7e1lxy8}0=`Ld6Sj4%Z7eGcT^cKNdh$V!#5FE0Qrp%{N@&Wjns@QG
zPsjblZx`}}*S$5be!XDR;f@7|1naHB_}0JOFQ5N@z3$$v`|a-Czb_&GF2ChGzxna$
z?oki()uSG@Pxk%r`K8*A?~m{O_*zo?=k=87N9*PE+UN7l{IFj6iy2o?&i|`_LknyF
z*PghQS(p2$bk+KujhoIN+4n-`e&o3?L86=YE_!@j(XLGRk$Ij|QjC?6*|P-qL&}%t
zv@lyt`Y_|@WzO(5d_U%QpXQd5i~KI}ZM}WF?Z*Cu@;7#GyiW$yx#uZ43qIVa%bB9_
zDe;wb%8LN8+QgsyJ7j(+$8qg!FTY;bVJ&^{$n{S153l<)Zs&O)JwMambER4C&-#_y
zKg9kDt(pB(W8W#S<9pxm{z$s+^D_Lnb=0318P;=Zk4PW?ZhE*Y_a4WcYZsHgZ(+Zv
zx|7jL-PB?Fk7X}5ZJf>Hy65rj=dDUzlOH#y?$G4v@>i<AUp1*e|J+I0z$eEc$0b=-
zB+j_RkG&O)f1#eL=pG9$cm{>-K;QoF%>5vd#O{b|z8|GsjU$i8Pg>|*bw=Tc{F;V?
z*4m5CE_xavw%7aIj2T;&PqX=9n^&ECCh~UtyhqvPLCP_AvwlB%m+u~W!2PuE4)aqj
z`M+m<2=*5Kw_;UJ^vlb8*}gygyY1<Rvg1V-mizXYlvc8?>CS1pD*ZXlv}3<Wc)CqT
zq|q%&ey5FjJ}$1AX$h>}(T6_g-hSW^E#I;|wq>U2_XF2Ud~<FsE7huP&-;4+$PvHg
z0$0kl&3d_y9sSq*G4g!-hw0CHU#MsPaTk{Vv;23?A2SuM`|R_MSR3A~FjnQuU$*wZ
zYstMIi_(IN>@8n;&t84X;aSG5gg1V3b)46nXfgTpV7l_E&D~8(a*FcXm+tu1RB>-c
z=5*bK{ZgOq#Q%-IvQBdg@9X;ZKHc{V-MimkTz%BuVCm<20n;buXI}r5_j&Olo+s~P
zd`aF9_C2fq*?;=^ZB4+#{TVm@IG22~?S1!|>0(wazvW>kX{RebpPa7i?lnmL9;0(I
zvb*K)?g_;|WX#g$JA^zl)QS1ek+%MB`@-(+3#VILz2ovr#QWSU`PxILOgD0Nbo9FZ
zIL4Ba%XR478iSPdw&e6GhD$+*C(Myiv={c&4D7miV~$u`tt9u<<I@xUj|Iite3<@m
z@7}o|PyT!MBjLaEk9P&WMhgsPeg9_HFgsFr$<2$0H)P+{c+(x#utW6q5=Cx51>F~s
zIoy?gRd%0B_XvF!jO+f*pnrUSv%b)N75+`q7x(A>GTwLV6W_j*`&!p;zjtVQ<b03&
z_lkJUx5pj(>MQ<Xikiftv^tLMYI5#wufj5KElW{Jb@SMsdh9_)+OjVfrtzjkq?}(o
z^W~?X$Bi}>80FU66;Ilh92+AO=ALutl&NIIrV@50vA1sbHD~Ky*IoF=tk|~deD{G3
zQ@ss~t{VC9`yRVBCH2NEW6plLFGoMS-+u6H<FAVU5tf##ga2FpdYq8{r!Ilv-`@0T
zJn8?Y?sa7|{(q)o+MVm$_DrmBo;Rs5{g9vJEnT0849<_Qj$hyYD>nIb*~)8<D-7~>
z-@NhWR>#dtT+y4NA1?Xy|H-?xbz++@J5LC1c)nRZI=1`8%eTn|Nw0Idub1;3^L)De
z+S<(Q2{X5a{k$uzb~|Fa$N&4EPZgUNxr-tlc>}IQtB!^@N5$O<V(i)s--30eu?%#u
z6==I`*s5CpNv}487J7G~oj;WLT1x-X=5<-mW-YQ<J7dTD<r;qGX=-ZEyKF8<&%VIS
z{Vn{+g*me%wrcs!o0WCTFaLB;bL;EsM^D%OWNzFZ<WpgNuq9gBry_qX=lk}VDGwHX
zeP>e4kbHi9`z&UutNy?4wcp-XdGBR~{J-#nRde6(p8etK37hw4&fb3aT08sk$}M3=
z8v^d`&oc;`cJAPxi!&wO77NXd-q-ef^}i)=v^U<*ur}SzIlpYW;1lWoDGM1b-ahU=
z|EuERZacL<7cSoUe*W*-+cnCz|Lryz?Np2F$?fA#wEgH2VeG<g-%zOi$<NhPZ;#fr
z_VB}fTfaZ{sY#CUTIaug^6S+zzdvrh(zj!l9bbA-dZPa&s~o=@ZM~e;0&{1yerQ?y
zgwbg4qbS?W)8qqeIQB>OC%w3``Gxs2&yqLa=WOxoHV@qWGrBAPabwzhjeC>qkCtlZ
zM6Nw2Znie&aek4S-_I^Hzt2w>iE91nI~Dpb_haKO`vs}H&tKenK6mxyYk|xE%oN_p
zdfCZ5`O~ViA2@HQ<XS6>`m)(}iPg?`T`RUdaQ)Zzj63i6qRRGkTlMZ!`>pULC~k3;
zuiz=c*!f34aawOI{V4S&W~0pS!{U!C-sH^jp4;~M<z&(2CpJ9!dbhS9y?!;*-{*nX
zokeo1A2F<vx;E$JtiHFi!<4jhMa@*(lrmzcP5rkp+93648c*~L&9{%|Pt^Aj`ZjNq
z+&{*~5AhO9H0rdvezUG<Q=c7n^wE)9KHd38<{l}|wx4pZ<4=nX!@D)})oL1VpPc{b
z?vwK(_D3(DT>q%%l<Ay%C4%ac<{!A(vwWla$Bxuv-eHIImZ&U=j+rl@%)evzqhl=@
zIleu|)-BOkvXsXm?G?kE+bQ4no?+ZE_2SGwb04f(pEB+E(iy*Y2nF-&uUX%l{gJmo
zcjLq_KX*K{khYgdzvLEGnfJP4&WWgwzc;fd?5s62DnIA^N!eInYyJ$AjQDz=v#&Zm
zXYWpYH1E*dS({Ws{pVYnXK&#7b(sIm@kMvLmptC)d;Cpv_XYEGfnDj_4jpKzGjb42
z?5ddWvO~#9OmJJk{H0BQg5Rjti2c{GyZd9tr<u3g{DrnZe)}b2&n!{3+O*#lr^_dN
z`t@#O#oLLTJ8Cb<m+R>qo3GFI=)L~BiI4VLbnmyf6S1HFrepp$^W*m4Ob*ubg(>d;
zf9}`mmuWwK@09!Tui3rWT?~|UuU}V=0#~i)_4oft3I}ybUxU{JGx4YYeKtG4E98~J
z`pvKSQ{cl)*Ve`E_E;OUvuL6x=-jT_li)+@Ty3X?_UGOT>i)N{AxbS^`s%kgqWVEc
zMVt=#u;^}O>)lSCWEPp-JN{L=7Bbcr-i{B9iID&Cy}r&p;8JpiogHUl^x5mDB_>`K
z>NZn3(d_?<aplX~uO3v?2!C>ztNx>;ZF}g3PZ3q=l~Vd^qbEO)_A6bs%KpYaovQ6N
z0`?_$K2N(`cJ<3IPSaKYI^=ZMCl=km8)2m77pL;4B*c1?ew00r;lo}B;d!%@59!ql
zPt!L%=pOi6{A}yS$VH$2qzF%2GIPS*C_eSerzYPH6<w|`{QL2dW&du>w({HeY|RBx
zkGVVLGHtIbmma9fc_-F4)pvVx<l?&$H-5O5top5IWmX|B+R3ecJGe#q)tTvfd+#;(
zzt6tpS$zKVU#+xLD}GxU*a=R4H2qZY%Z|&hL;9}AKCs_0+a@)lx8Y=lm_X0A$z=-`
zzZd=Sv#x=Y`G3zv+kMXcY5Nzh|6Xsn;OF~Gr$pz>{<|pj%#FZ!=i7esqvD!>_^fd3
z{bi*jWVF%Jj_Kq>LyO5K%*Pi!fB5#S?T35Q&OK>7IqBPSX48*ZMe%oTSa`%S*qybH
zierd-asRtWTWOWt<@PxTSL_S_v^V{4`~IBDy@w8PybgYOw*N-g&WDeh-p+m~o>6w;
z50B;N;}>t*U;fkY!TfCAB;&vq_pp6!&z0?ugdW|0q}E$*McI}A-hbq_3)ye0Fn$<o
zx%=@Mx6d#4ZEJ}=vg+yajTILS&&;ajO0MBc&iH-y(!<iegX_Nu_S8Nwa0{v9venwB
zmK*(4cFU?NY5vgv0$;Q1nxf+7FRuDqz2fQv?M&Z?&EKE6KH3_V_r*@D)G~MDp$)w&
z46H;%kGCwkH09_Dfh*#ot=_*E-T8GWmj6U{D0}APos%zQehld0lj!gKye#qH7Y7!9
zuA{3mPj5`$>Tjp7dQG`Le@cte0sGBsWiDMWb-J@lkHb4hleh0>SL8zHrHm8z%@8+~
zJXpASL)B}gC39n1*81ljIk{a(_S*?*!-(oSjzi~oC+S)L+hFp-Bmc~u+k*Qn<6dmu
z+q6e6r2FjCr(YMydF<`KbAC#wu-4+lOU)VUzU&R3#Q*Df|K>SjIq`1{=lEDOYM(s+
zDC(DXh4x!BJHNj?@=I#8>}Ov|-=^??+ehs?oYk!HYbxaCpR;Z|?{?(DXT`uD8@?LU
z+^Au=mw$&lUvvMFmhF4?d|0`DeaM}#>9;@C=I7s;dxgEedsf4HbKL{_;+H<&c76ST
zO<y~?ef7RdyL%0P_BNmAKlzch6>>&k(T76@OXam3E`qjU73~ELJAsCuZe3WSv4roF
zl{WJ56KH!DXq(on_1ZzdC&3Tl{P}$T^T|!Owypiw@$}bmnYY%VlUID|5pUB>4cM>q
z>Bd?OIpMZ0)=Bg0e)3K}+_&IuPr;e{q0Vt%Zrax>iKU-8I>om1%)-<2vTjVd!DMbe
zeY1=~b#3v#=8Qwzjb1+vOH0?jfBM5g_m`WOyie~rvU}#=rH}sIo}KpK*|N-vw96`U
z{O2|ZYYK0)FWs_dU8kALMeTL<PPzLvY7F;Ety8g6**oQih`+ko;nmSv6JD>H*Z*;v
z<C#wzFMg1pqRKqW%5-1gJ^s}G@ShiJ3cuyGpAjz<7cY)~q|ATgYUldycHx>0I*F1&
zLO}(J51H&dP5vL<x1-kRp^4I?#fJKIoPW%E9A@|CUewtZxsSR2P}}KUi;s8ZMF>6Z
zz43wX#@2hY->eS1_&nl2!x{NmYQNWPO?<oi*}vzvcAfGyS^Y`P>Rvr-^PX=D)?cqH
z$(!5bw>Nyj;{~BFjB_=te2i|ZCRogBDLK--P<ILM^=&STU)Zetw>m5~-q|jE%R}o7
zPPOem2Tb0J_+-y%GJLu{Dd4B_2SeGZ-wfwENM9~p>Hl^5!~1`9Rrq$c|DF85O}y!!
z|GVSY!~eE$bN|mQJpH5o<mIWHM^~P9iMYJ{<@<^QAnw-9k$-l@x!svkk$LM(hGZGX
z`)3u=XO&Lod+*t`Tz1|KLyrFU5)GGc%O5*=M$uwZrUBm)-bwGgUiE){eLUfyRDI@y
zSqy99HXF^K+&t;R)%S)aANH!`zWvN3RZ*YEIoZX+Z7<(duF(I9oZNe&_N%PD{@2sw
zgJq)|Gv6e}t}rbh%YCnS_Xzyl_~rDD{gJXhWmmQf-9Mb&`ToJumg~CL11x8L5Ghlc
zv(d_Q8b|99o@Jh&&nF$}F_w5=&G7ejjLZ6G{s-6pE8MdF8{4gry{gVr+Md3f`1`=(
zHNQ7LEBBD`dE9jNx}nfPm5;JYykE`i(}Nmz_Qvf#n`^dXYJT-ptv}P=e{X!%k+(eM
zgO6t2^p5+QQ^F@_Pu$-u^R;Bzr{7x2`zLCuu^bI?3dj+Cd8zcfvc(;4;YF(!e{ah=
z_5S*=51ttfa)rDXQj?iY?nO+$XU`g8zRpJKXlO%unx*8qlJn~t>a4XAe!T0NX795=
z#Mt%do0&In|BCRNDmi5v&%w;qYnaZ&w#yyAx%d3zTQ|3Ve70Er(fRau4_B-2J9@wW
zZ^!)pe?9ebf7E~0Zg^gvxc%dGSAWm{Hc5+lGT!mb>pic|@^JNwj2c<-_WHQpZS{AB
zmdkHFf86~2!@b$@9_N?r?GY}#zEt|JpTe#0D@ux{iu9POKH9mrX4S?;4X;I)={T`M
z=Z{!5&aLA0?Q#ND@F+cK(3*768Zgi?q56@(q5IvAitc$)xaB?c@Rcb_Ta4q5JdeKW
zGiT8b-7`YmD&P10Wp87B=epmtZpNnVM$6u2^&Hru`pIEp)dLNSwVd<rhTJXs@cQeQ
z-yhtTac(!ZethzQxPa-C@Ryqs;}ai#IIq+9qFOq(>i!0c6Yn#5zTWTO$oShJ{i+^I
z%kv1?ic7Eer*XTnryfc#W3%%2b$`v7`9W^A<&M>Rb!Fyu8E$4?s;su=@Q07m2DMcj
zPHF54Dc77<{#gB9*W3N%{!e1;O#JIU9elY}b`9UVo1dF`eLwn@RNIJN7Poxuu29sh
zZ<YI|<=02vGwi0zf6hKUwe0y>cGIb`aTAi%dMyO*-F+@=c{t(Vo#Pww4l?FM>^p9B
zI$_WM;AxT^x4561f8JIbrM*_kL6F_Yb%&6tZp6NC;+I!XSbu)r?^1SK<4b3a^SAVQ
zoU2bPf0eLoLsAawmgsvz!heq^KYj5u;+mGE;935tIdQ77az~1fKIJV_Uin$`jeHU7
z_2+kwt?ss)P$%`R^(n(sdt*J1`qQ10{%O`qt#ey9rSas#rHs=QR1+>JdLMWcd){hB
z(Un!9|C+;m|94IE-GAsoqq*UM?^?%vtePXazbC~l_-Q3jWv-SpWw}Ao-R=sjcdNbx
zz2S}NtCS4Cw!bZ1Dc^g3^zX)}Q)2sm<lOjOVfTt9uH#-af6%o@=ay+)+P%{9ht^Lg
zJNvsoMAo&$Oqv%}BRTW(I*;)F3O??d`G=Y~s~?!#xc?K;o4G}NCS&_V$tlfeCX}SC
zKQra~<h1xBYXr9&Za8b7GU3|JgYr-IZBG}yoc|*Jo$Ki{V*90*Kj{%q{QvB?u5seB
zEnY4jj}4AoiEoQFO?#L!m2*ehr_vLP=eB#Af1TTKrSY-ABXjO-HQ&R#9z5)bv1GZJ
z{mb)u`}f0qPs`7){Bf+pWS?N)y6mpKpRFGsDap4nJay~%vTX;aNj6V3EM_=#NX{)H
zrQYY}4W7$Ew-@;bv0m|5u)jV^TQ$sadrthzCg;MizVm%8_n#Gf>5Faa%NAe!^VH7@
zg#@cr=Vvy};&y-hFEln#b&spv>-?O4H%{4$hU-jsscv;$-+R`u;IFfU$lU{PR?U(S
ziDCU49`5u%RP^cjD8s(<CYoH@$L_a3+h}^RqBeI++)ZZQCiX=K&p2dmF7&+i|NIlJ
ze*uY~+9Z7ZJC0rX^74}AO}iO;x2ym8zI_u{JXg`dk7^w|Etr1iw>`eImS@lZ>(V>E
zKUUxQ-}&;N_s{=7{H|X&{ol7+`9Hq?S|22MfusJ`*U6zjf+p1a|7X0P_usx|>-YQ}
zTVL|Ub?ooe{+O>={3>~xLTXz$+t2E&^Zb{{pLttYhd7d3^KSrSR~%A5KqUQYo0_X)
z>4fW%DEk8|9(9F)rmpH^C*GP4I|A$GdAr{_^F^*~%l*3C;Porm=(@<m8>Vr7GsU$)
zo2g&e>T@0rI-_`c&l<rQ>owB$bo}Pte(iYST~pV4_X7CV=dbMP4>C(S(XsrqY|kr$
zO5v)rKF3cSohh+DXR{6K#Z3}c5rSQpMSMf9Nt@UU$1cxxI({-U!_-PtR^IIP^3qKu
z7n(gktexC5F?8}1#<>gQPJY_AYS+9C;kQ#xe{Oxtw(QituQRvE{#ASa;Ni`W61x-_
zH&0JfFi;7eB~->UQ&4?L^<uf>mpy+MT3Q}`wC;&2hj+|_3r8nZAN$_qf5qZV)Mh?I
zZNJ2OM_xx5ep>%Q{)g$VEx+X6M*kQ6`uZR9)p!3xEB%lB4le0xKKttBHnDI2wx@=<
zKYF4+anlFUHFL`)gQs4%vsdAm8}`-d{f2KZwm+;p<8P{{b+aY8^O{}udnr@BAJaCi
zzjuD`Kfb5zt6w}9UAjJhC%^pLfNje^p5Ci==kkxcb;5O4OYWS@YsoL$a`^6+lRKvG
zk(uxIcCzEye$V?Sm}ks)`FGf8aoYbc@jAcb_VB-|4qEN?Fzw2htfaboZ{Gc%n42r}
zadoP6`St%Z-YaAk{@niLwvo!u;#Xpli)v;VhQ)=q+*&^Go%ZZo4}*fjV|+4~*KEK2
zs3xd*j&D{6cjndtyRBU3mfvYVKKcFOi5<TS^w!9nYYROhto{1&NmKrZNiM}Ul2?WG
zS+^gVtsVX7>(qM{+G`l^^;9zC9|<>?F1L@155Jhhc9-4%^GaROHHSj{3wQ;e_;nd1
z7ER#X$9PR|MwZi;CpOp5nI%RBxVq@AYnxk~opAltsT-nklF^H@5^n#>vRJjn|1JC8
z=Vn`vADrUo(LYy%`R~18<<EcTr=RC5p1%0<pQSJ4Y?%4mdt1)mN|cy(O>FTtvrSwH
z8t!>{Z6|M6oeD^gI%Ram<3`8K*L;WVf3+O_;(UE#=^xcgN2YjNY<y}e!@ak0&dlir
zInTH)%%7+3+4IA8kKh->pRw1quL&z|<yiZjr$$P%_M6hydN!w9dhU-FTs$FR=~7jZ
zSUo51UQCtkO_^8qpUdvaSH3_Tgbf-W(*Tc;t(y*QU584~5^25S7?+N62m<KbZqWD{
z=ybXGP2ZsBVuKD*J^JyPagX&$`8^%4w|8_u?>@h4$Bcb5Y7-~fRXx-F60w6Nx%}Fc
z<-*aMDutH4sSA*MGjs2DC8I^VBlzt0uKRIn8P^V7y=n6ed9EMMTX=N>vr)=RVIiLT
z8=gvtZaq|FSH1SpEm>RTv<1Is-F&eoXZ>xTr~ejgeQ`_Qk4;5ML-N82nRz_x^Y}u$
zZU;_1tJs(I|JcbF7YlW(O1Rv&>uFz1YO$ZyJG-sT_?M^l+Ozdhv$zVUUeMVj9M#6-
zaQ*0HgERU(u~qA~aWzz#+-#HUEnkp7cdbN-@#Sq#%MKndVOi^Ie&Nm+saG@hIZXYu
zf5D^L{U>i9s#<1$@_6XcU#k}TuKZN0Yo_a-^w#u5hD7IK+2DeGXTA2^&=ko364Teu
z*79)kyUkC3{tvys_Vz5B<NIyz=d9d3n=`-X($0XNcXX~tFe!FL^R92ce)#pHvnSXp
zigRoC?A@W8r~9h&c6asTZ<Fp-<bNxuu~24^*N<!O7h8XPt7m&*&JA9fIiGaHzWr@4
zsaPHVv0(E33yZ$hPkq1Qhy5wTy{rC+%f9~K!pB>8Xv@0qOB4^=9d0X>PdYqh`ostI
zg51Yt5AV6yzA@#2V&;>>mJ!#}t<AojaEn?Wdr8?@d1HQxh2L4pe&f%73RD;A&g(9G
zQ|0$O%yc4q%w>1piFtC?B9_5!$>+Chv@pnKI;S_UQ+uKEheyZW?U;EZ`d8Sjex7_8
zqs8*~d|zdKEc5Z+vw5HBy_oxMKW_?czixi~Hs`zDex8X-;}-w8yK26wib&#gj=lTt
zRdB^GlTp4d_%G9$;hz7C4*qNAN7*NS{g@b`TeD@+9Muyw+B4sEFZ9*vH$S}U;k8$%
ze)#;HTkQYyuJWI%6`b~(`)7xo)c0HWe|19K^V|E>Y9w!P$g7$ixE-B!;r8jTi;egF
z_;T{(j>;^lyts(=+2PwBJ^OmuqV$6bUv5<6-jk=7SU(O^&@~a+t$h5^iCy<DZGQdx
zQAtVtoZBA^_}so8X)JwPP_fv&PxboYPT#E`jyyZ{#O&_UAI{vbK5NyOZ|1)TTJL6b
z`*B*D^qmtqeb0@f9<=7JEiA2e-&6g0iP5_S_%j=?PnQ>wt=&F0eWtGxD^`iU+PAip
z8#Isw8YK&ADgE$iagw>~N6Y*L8Fux*pKsrHaL>uzh8oWjB{!Wp^{zS6de@2D!QaB1
zvTiw7*qOdxFqtDZu4iTE?Tv9iyie8T%=*e{)pNf~aP?x)mTGT-K+R1HZPxa1=<IPn
z?>7Bi;)|<!%{-<qI}F#PJ#-S4fA4yU&u;O>W91xD{;&G17|#Y8?`XU;Z^zoG{JiX)
zeXA<^e*M|!JN?l1GK-zPhgPwfDQUdAZX(HV?(cS<PxHksTUOcHpo>e>wW_xJoqX<K
z8LiGK{MzNf{&QR}_slvNkr<V6`RcOD6DGWlSyvZ|`G@E91&4mFdApA*tntLoS-gcy
z{J3YQnZ4^gaqV#Ku4R8C*1X?)LbbefZlAlg@0QmazdbJ4b6mjY-gfVQ3hp1@@~PEW
zTxs#YJ^RDb`JDB0vksMa`}}y$w)wyHnwoWA#f$U~{C{_<`knc|XW#x^Io;yFYu5Iq
zcemNORMp1iRV<O3`v1X_)%)b~@_wJa@cUz0gzg^W<%aiW-W2gaFV%FK`<P;SfzqVk
z`EEY{?|yh+$XY91v;L2~+CTZ?GuKN0A2=XpuUK<qa)<P0;or~MAAXoLamR*byz7oV
zpX+vEn)#&FCla0>*mKS(BJzH<(X{O`9V<hxPrg6((5?+$OiFtn2}asVMXzFiJmWf(
z%z86XbDy~%=dIlNg?)0+iU9p{jQ7`G;oL6eH(}r9RD*f4R^sWu&rhBwC)g#dd}!ju
zbXK#kyw1NGJW_tGJGkwmvaaK$bte}W7Cd9=7N2%*s@?7)#klzWO!@9hALkvaTphfE
zbHD#C?{i84f0f>F@7e#N_T^^fc}JfrPXFL?|Fgx#8f!f>|3i<jG8f)8k<g#X_F&yf
z>F-)w4!l3h{^0)I^oQ3K1AkQBi~O_Y58KUculirVfArkd|6%;&?I)ghJpX@j!RD*~
zewOFHUT|pM_9(N1!MfF+^0}4*nYzn8CrAC#p5GnHk+(BQ>Vi>Bou5$E`iZ|ocO2K}
zTfQvSWB#n|yzAR!ChgnztK&}k{QMf9d;2B##%O)}cy&cwMP04<OYfCePn=(|c;D0&
zHQR;uE|p*VuECmnPht70={z~_H13z0uJ~2Z%K2XN{^{*W+LPXH_*VPVVV6vK0O|&4
zk-eb9R<3)4Hs_Pr37uc_$rEWY@1)&bZ$*x#$Nbr&t>2<;_nDcE?R4-QpIgD)o40?R
zWbgVyXzyaJd7h{Lhwr;S-Q>XXkN<vT-w~f-^Gs@4OU}UuKTfU?iDuSqKi`&rx5cSl
z%Q)LL@f@>Y#9a-6iT6GW-aaOOQ}*wNjKy{pS9fpr-KyAZ8^1P&Q+)Q7gFD%?AH~dN
zFYuGxF>m#?fT|TW>`!L=iu5t-JvPg0^M*aIYFNU#4_wL0He8w|axAp};EP!spJXo(
zS?(B|exXBBKX&)h6Tx%0N~PIdooU<tvt6;|n8=dz@?FBKtDhKKS4A|gi+RTXzrmq@
z;w$B+>m=gu+_}Y+pR(nEJ%5;DvvBmk!YxAocJ1u`;?HUPG4+zspUPwvI}U4&TH6ny
zcY6PL$u<0QzYtop|3&1V9WRXj?Rz2l=l-JSpJbPW{Z0GHTeJ5=Q|y1M=OX`JZD*YS
zi+6IJ%>wN_{&$__X<H6OUzUFOYzuo~@-gR%%uLxmQc=9-;i1P)uU`FV>y)CO`?Akf
z8Sk`Nr}zDk@#)u({uJfQ#9xs0T%4|OGGFT(`?q5cpWZ$3{N-MuV19p%^Y*qIoA;!h
z`21FGf}_aSzglbbr><`n_l>JK7s5Z?mv^cEj9HK7thAIf-8xNiv99?HwddSw%G!Ph
zcdc417@BLc?CNap{cPP2!u2KUwuUEu`8)B+_hQexvK<+92Nqf6PO_+-^x;IubDisd
z1?zUaD*jaba7C8&^kw_?Zrj^_v-<Wk?$E)+^AG+Unf~;f-nlB#<LY+J;^KR{iyy}y
zaZKHNxU2JRzv8Z=iA%QM>kGYlXyb}1nQ7HW6GL-1Yx`(F%=dDik!h;rt9WDS8~KMv
zqb`34IlQ!D^Y5xXH}!@4zgxF3GtWQ3xYYO5+P(6}XC2$UaaAH;(_XXW#NM+rVpU})
zpIWN<TbuW>Pm<c)9UfIu$7UEkP=BwU6!<oGTJ4HB^UhoCSL!xC4S&7=pf>mY<C{0%
zn`Ga5>$U#PPq&|3*HxUpqwT_MX{VHk&$rqnD@+f{T#M<?_N==X`}OUaWlwYjvdkuZ
z`{njN?^e(DOP340f3DQK{L8oKpyR9?`qQV|cDp<GC;UC7u%>%n*McwSKHQIU{}&&3
zrOW;V$IZ}26Sd`nM%&5{Ph30s!;5K_6`Hn!=Ei1`ai^CqcAxr@ofmR95ok-G2;^)S
zCXsZ}M9)Ak*(ar8uaFKZ0G*8}y1+skbXn0iRp@n4;9Y@IM|#9R?kqc!X+8T?v)1Xd
z@FO$NhU&6U)>FSe%|701eb4mt>c{I(o_}=IG(Pdwq~-{*`E2ihd|R>cPhv^c3_caL
z@XljBs~&Q!w2_zA>OamG9A8=Rb<aw}N{4G6afVl?u20>*(%fuzo5GSuH7p@9jVqsC
zoA}%5{S+y~8P_#r5^cp4xBb_ViF?xR9{%O9e!qxbPQkVF!O{=*@}BiPe@3Q5IJoV>
zOV_=+r}v%xrgU3VGe2$X1iAitiQCF;irGgsbmnbfIjB4>A*MaUvfE?tGyg<U@!S0w
zbpkK%o<G%mLEU|S!|coRk83BNdysdb`$x&W%@tLL7FQ(R5xgT&&+z=$y2GCHJ!0N&
z=EygB**m**%0A6|tA928IDaIyJ}+&a=ZEs6`6Bl6!E*Ec*Y()@u5RjI7J6ju)u#p8
zdu;Mt@3e1U{8^r*Eaa``!6RQKkL0xc{2D&-w}-`*EwP2-ZW{LLGbb6kMw;ChX=4ap
z!J~8Z`+>=4bnLliUVZMd{mPu~ImfamKHlrNYjdd3x02iGh4qXJTkgHBoj+y1=)-02
z`W4q4^<4AKuJ3B^cclZ`-d1~~w3c1*IkIFH=ZuPare)VWru{CMb7%Ua;;0RiwU;G^
zJyr41n*P7`gUi+T6K>cml?Wzwu08N7?UQju$;(Wc_!2H@x4c8I91A%zrZz<K=c?Sk
zwtGPwQ<GemyT@ggoY&twO73?gSESu5-5D!*C*bzg-`|hquW{;YViH;#673}TYL(;V
ztC9K9PAixfpI0b#(7MFFL}7D%*C*TPme9A)BG<Jm^}Ml*)teuYxcJl0liOM%A8lU7
zzsZjG|CBp+PiJSIf4K9Du*Hs2+eh1fE9Rv8``nGTjb9LS=hyp{?MK|dJ?r!Pq<l;f
z>6}=ufG=I^*|x^$LsRc+GZRg&mCbw3dC4N3@CiB+iAf}V>95yaSN5g6h2D5o^Z|Oz
z=d<A6%9H1}8Ql85!6w7>m^Rm9q1&fYjf<jAH!FSq7Ix(0n?1!R?Pc#XREK??x_y4n
zr(^D)k3BqnWOqk=Dd%dlyvf(IK61TpX<xR^X*c6aiOJOk2AjX9>@iz!X*VzZkazI=
z!%r7}e;9H4&$}0O@{`=AZ=PYcNv4P`>_FeasRbngsxopxoY86F$8Od=Er|N)@ST0b
z>RRC?=I{D?q?iT6OL^30$yj@+^Q_p$6F4<ZVcn;coIUkIi?V#&y)I9@aVt-utmNCl
zefREuuG~@Y9y8-cz^^k?-t90uD{h{~d(gYM%{!l^^w?BU-9p6$l4X5wJ934s6OT3>
zykU~Ba83VRr|jlm%Gq~3awIMJBAeBex1F|e*&g4rGww_LgV(VaQ&hvYayTA3+&$sN
zW*N=DPHXPmDHq-LCAPO^-9wJdO?vYep1q#n_%>rxZa8n1hRDLT)0DS-EqWti%36KQ
z@5sN-<PS9&<uS2WMc2D*Jv!a`)q~S-OFvxgi~5n~U%F@iQn`C)*L2^WEqCDU7xsee
z=(_v<l??JK_6_2Pq>^8A#UIfLy7zqL!4ESfde1$6d?7Ey$kN&)|MbMn3~LL~xmC*-
zMT-}n+MdFbHLGD_>0^UsPE0>%z0pi7>Xh_(w52GCE%>|NGfAJhwUZt`-z;Wlrzvq+
zQF3pTywmz)=>eP1$!=NsQ>w=Ix(?e6KE-<?>4n{zJo)#obLZc`&isDHu7|O~={2cY
zDu=~So%iMauxh2P-;6eeyC$+1m%0A@Wx3gH2~R-C<|_W99|PxpxT2A7b8ok;-}X0s
zwxR9GYd^)DYW14`C3n-j{0*8M+Ec#g@66n|XW!&kGEe`Cu+40mIzL<I)%^WSoK&t|
z_jL=Jy!OWB*A{-fHoe}D^&TJHS-IJ-#owpSwM{#z%zMZ8I-gYud{elJoI;<Lmu{8l
zS-bS_-S7|J3u11ohV`wLbX}KzUUKc>CEQgl$!}NmUTUe_XtC!@15bV4GW%Y0^NXAB
z-s`CrTz_m|L-_T5J?`N$#~YVcFFncqXhWHcjZ&?d;)cgoKAB=YFMFFG{y&hliT~V=
zHAkgfN_vm1t9)HplWQ~YeCP3Y|Dz9Y-`@T4^7QMU_I#D9t9tDF=lkdShl>yI_qu=P
zkN7?RKa%q9^}W@G_b0ADJ8%7(pHksY?;h^l;6HKi8_N`(y-K`&d{fVL^BnnL^7+TX
zz|H4m4f*{zs5V&1Z~w<&KH?C+!<CYI_Ehb&4J*5?!2hJ<{EmV@=b~opTD@?OZF0V9
z%KH@^;oI)1J5Lk8ygK3MjSQ7@Gj-p~$3AKHca}T&aYwq~u0nRFN5&VE?^;}$Z|pDk
zAkS7sG+Apl{{q$Tvs}-G2;8&HIkfe!+L8ALiV3SZ_q|jL@eKGix!PAEbiHct#f+%*
z%G_yjZ_^SJnxBTTFaE;!m22|#yqC9fH$9!D_iAh0s<x_=X6&+3EcXxZ`(XX9eMffT
zr8nBvg<q}+UrL`}#jE~ko%i>4`|jR4^{KVn{CBPR$#;5NT1a&Ni*MW;H=eGUGsDWI
zs@S)Bd0O+H`IiLFyKgx<kCk8fUcO1#ProB`{J0J7R?oc5F2SAKeDx<^-m1RupAY}|
zc-U~={gBUp%^D@*ul}*rT2q|Wescd4v14DQ^M7)`7j9hpG>3bk)DO=4K4$U354$@n
z#gG2W=S|(;dH>?he<2&W=GAYI-L^%p?}OfD!~6qIU!+Al&m}+l)M{WRJ9~9ebW8i&
z-%A&z{}z#4wc@&xRA6(R?=~KD){_P)^E0=6GUGp3yzE`Y9TTSIzK<uQa=FIbt@v@>
zuJz#8_~!7X^SA1sOtQTEens*xCRwdLO%Jci9iOtw+F<P^wW8`(!tcEga6SFoxmKu2
zH&p!b##xs?thvszCpf+3=`0QZ*;S|QR;tZTJGpO_Uzx!PnWR$(9WL9In9p?&zs$PD
zWN+8AW!I<9*I(TGa+jh^j#b~nWrt5?8(N1|`fm2&3~QSy7A2WJ%jeuG%{57Fb7p8h
z?{N2-TX^ZpwXB<k!PS+gw$8b8v-wc>GPWmcY}{*ahe&Q0u@B9wU;3kToxRq-;E&5c
ziGF;aX7}v%!7I7C_ABIPOPxM{Rr0dK&&sIesIJsy0zL*uv(vsl`Tys|+yFhk&8ys!
zT=y!pY>8~?U3^@*^p?jPu9quU?D@A;xxiq9JfF_HzUJ=TiQf;2dC1<Ck5=b9qPBlw
z$2(I|PUflKSA5pe`Pz5j(zL)C5gR`|d^M%$^~OE3K2@)JrL<;Oz1*eOJQLGG!|UR&
zN;Dn*dh5ZKaPF9(xXmASt+d*y-}=b5^3oL^b06N1`(85cS#+oV;+2D+VntRlt}2*+
zlK;3*b75%Z{`)4Thtj=n*N5Kvxp`5Ae~Y|k*S7ol_t@rdeWmdC)xxwN%Zl}DZtpa&
z%ex@4-z>B1ziGyV_tU$6#YL{mzH&z1e@o{<!S}otcl3nYcfFr1R-(15$;RT35MS@U
zhT6LR#P1)NZ<v~}Tes!8e_G(7@{M=mx(^=@AKh`{&PUHWY83{Nd~rNX;zm9%A4=EM
zt=`HLccs~@cu%mvrrJ4wRtJBK46jYht~|~7eo5uO6~T|CkNjdk@!Q(%+%aK8aY4!L
z+zUV6oFTiLk5}Bc{re7;&j(*6dd`TuDkIb}Q|7+xnuhe?-4DN>ZT^r~Vp}scPV|26
z-hLjrkp1;bgz{8PkE~>^onV}^hsR7Ue2X1}Ob*wPMNz>9&lgUdabvCZAGR&W1opQ{
z*q>}sh~K>VkIxIqgl`XTPJQxtRqpY!jV(rpCf*G{us(K1Ny%xBoxB@;H|8$XUU;gW
zFZ{^v&bdqa_2+jjZuFjDsVnv{xnD8ki7L<QZ>>`g|K8U+G5LJLlP7;aaPu60k*X*m
zZZBYL#GaH?6Wqg}-&<Jx>y*HeWyWkLzG|qen4S3^e%$rpj~CCL<*v!ge|dbz!^fYR
zW?MZBd%tk;IfJ#^xtPsnJ=5H6s;^@algkm8c|%6OB(u+Z@$AQkraY^#+}rfjc+Ibh
zAX}N9+{PuQ{K`p33w+`Y_?6Sy7vKHC^7=z-!Hb>%9nM+pYgRlISZ=m^`*+5l_f{>B
z3HCoc?Oyq=KX>$VQ+{8GnewQ_=TNBcH^m(X^L|{v(Y7*lv%x|qwqy6w>%TQDX5SKP
zy2jJ&(Ck#@MUi6Hww^k6aN+KPj2~<=l6x7`m**Uqz4}`89%1R;_;Qzr^0%8lzL}*n
zC#r<=wfo&;yB^j%UfG~8X!<2u=j~s~XXSq-CP(e-FFb2^=t0%?hrzFPOV<9;ikh8g
zk+T1l`O?SoHQd6|dwN@S?;oBN_Fj3>=i?FA-v+;&&~|@8_}l%9iofb#T=**1MC*U}
z)AG4C|6-n$neT0xx9oM-^$i!Mtg49<f3#sGzut^l5v?j!U$3ta^b587edYIp(vsF*
zql{}-o4US!cCpTyy?W~V{#|EvE<1P!6vw@sZ>LjmWx8*u&fAU;?ptKjrSfksyVXB+
z+0#$fI!-5YmvvgCehU3%Ai3K&`RS*nYQOJG|9#Kw_4Ymc-gaN*CuQ>uz8+S*?2-CA
zc!`pV(BJ5^D9^m>lIgP-rOaNH?c@0Ad-#@3{vp9W|2&M&9@D#QF(>5L_p;u9KNfrH
z7caQFLi=CF4N3WHat-CH?Q^!(L`%+i;T-(XFLnOw@2wnlEB94)`S;$PY3+8hALnYL
zUmuQ$UOlj61(B<bU}s$D{`vH{;7o3U-O|%{*rGS)|7O&&y2m&9+qEM{_Ffg(wVO#V
zb?Wr_m4y?k{<j@_aplc2od}l~pMs7tcgRoedGTYeQGsAt+#V)hwRPKN=FGcWYBuev
z$$2+H_IdfatGli*Gdq4eZt8~!Sz*13nFR|HRo~=1XzlssyYcvL&c3ZVecAk155DHV
z+aG;z>c`CL$w&F6F0%jZy1ae$^yAgK+EHJfzrNP|`M2m(ZAGr1>YiPT)cDS3_QZac
zKlJJ7+XvR!jJJHF7$@h;`0On^{G?X*{HyyX{=VwkI_r+x-EHQdZYCe$<&phu61Lu|
zcL%TfhT^MMHnX>|=)3wJ<W9c*;PAe=ANFqF{^#_|_aaB**Kz+B=)Lvvf9MJwem1WE
zcWcG=$%}Svf5!5l_kwRlS*{3QTFc37wv<$MrL5-yFE7Q)o|xd)VxJ{olGl|V7cg1+
zQcw0IaRIT`;92XAeJML_u=Z`xQH3)f=Qiz3Zja=dai9C2_piibj~XRRb9a?|Sn>43
z$KEN+`4+#qrphX8rJfjfUPb5q<jy&I4=wH(OZ9*AIQH_LWKiMV2}f>aFNt35ckG;2
z-Ho_HmQC6{2d)(z)?q*JcBR|_zLS4fT3w#<U!-)g@5&E`vRjKPmhP1ZHf1;7Yh>{H
zpomYtZ2!-<*Of~yNU8L59eJiXIklifdC6u~AE9YiwzlSIGWlKX*KXW*!!}9uQthD)
zr>bqmu5GM4%epcy-R1Qct(QNqeKwTcxM}rE&(#g?XYL7Z?%NV(cF}l>hW4zw$>#;%
zmYCbN9b6V`wB)jupS{LC_E5nWnfLi0@>@?$^118oU%?z+yYzo`o<QB68@%=Pn}qjS
z-EsJJa^I^<+snd^Onx1$puS4gNjWO9u;tTT?iaZ~??vvbmldDkeCZlv#H|XRrdNKG
z<{Q6F59Ez#xpjZ8`rcWOJm!kAN?i&4S-;|Y!I`QG3pXD5zLm`F$xlu^lrXBY_@#YY
z-l|IHSARrXV{Ld+!TA85Y|&0b->K&-*78b3H}6Y}@U&bQqq8iWtG#||XhcA*pp*7_
zH<!wKm+AA{j&LPUSR;`)EBWhRkvyZ`4Y%s2+?weXbyXm{IdfBv_Z+ppSF?@Rt=KD>
zEuE(5lw5VWL?)yzos(a)>!yBGzgbN0V&1SvH<%eMo|(!YU+sJJqt@htMRy9GKF*M3
z>O9_8sXIX~bI-LqZl5D$-c$yB6yW<}sh5824xi;zYp;_?2Sy=AheDYrFPda^;`JKj
zmHwb(Y_uE-!$7xqzx4)RH~ip1YqNNpp+)2gt{FP~+d3rmkH>XSXWy^>Zf)OzyP}0g
zyXT~a9e8^7d*X)u`)vX=L>8X@eDdzbf`6Xg4<3on=~J7mtlN0{MBV8pj@wr}`~1}G
z$XBm*N3I+_-MjkH(jw8deCI@`%5Oi#?P8Y|&G+79byK+9n$G7^W=AgWy1S(R+PjX|
zrxLy#t%|Ey8(6yM?i#JU?FQ}E??aZ{*3F7vn{X)Zz0Kj;Piap&ZdxXpo!hbTszH(y
z?-tGvPRDm{H9U9ycG88Gi4pcSvr63V7qD!**(&h<aM-txyIS48*Ctk;*T|kxH>JMO
z{^}NK#bp_9On*L_v~uE`$Lz9J4;JU${ZQ8}_M_y1!=IJe;V+N-&VKZmjrUAd<<sii
zZGU!@+15O|D<*HA*Vr$moy$GD?0D|`a}}E_tM}YoWE_`ylg&KJbaGtoRfg%&)t>rW
zR(2`xJ)L;^$EV7^HBD!0v!CDGZD+PUb7K1Hup2SY|8Fjr`w%$gr~JGY7N`D(jwT6K
z7Ex0rg|I~(iyBHCSs5LddJ3{knbaay^&*s2d-?JtcmKUN_>Q#AWA%T3q-w?T<B`dK
z|Nr~FZ}(=M$@)1J_jAfN{jRn9UUNk8X1VcZ;q-gY)0R!mS##6n-RiQ60E5oV#cy+G
zpSmr6Y1Jv&t$mYuO`}e+eLdf>{c7eS_5Gjw?Sj?JUJH~xDU4n6IWs(av-KU7n{%Ie
zzCO44>7R3(mjoX$bap#2sq^~DF3-hJKHdy(SO2y3e5v@)YP~Q0Yac&7uFm!JcJJgT
zuXi#(y{~+JvcLZ6Y2W?V>FoY(`RVJ{z8d?x6X%Pw+bs~>_%tCT(c@@GxVqWpg-MT9
zWe*D~7ft$^uy@Zfr=1Hu7v8y0QkEe5^yCt;$ElY(C#QZodHwX0H=Ra*&(!S9u9$bn
zv}oNO(|^+**dN!K+ME1-!phqD6IR|eJ2fYJ+ldMD-h0n}le9AE=l=(FGXtOUpS1id
zEqS|scFv5Qk!5#wTkZH4C%yZ9RoJQ%OxGNj>}RQY-4}ARD0<bMJqsuK>{N}_`|Ng}
zgZ<u|b^D)f>z(@Kr0L2{wPCAj%&$&7zRYq{W|6O??K=rcp$~T!K0ocr82#nFE^l@3
zgk^WtTk8K$-|lNAc0fSt<;j}|Z!^nze81Mn{^^j`(*2XxWWOrex#d;G&Nas}BxBM$
zc-9y1)LB#7rnB~L)%+h{Dy}*|u&dr+7rS+)P4w=iHiaqtl_snA+f=+M-Sl(I(M@Yl
z$Nsjdcki5cU5I<X?B3XY(tkGmxluY%WWKd$o#oRa*;oHLWaJOXDm?i8*1f$i*z3@T
zpsI>}PLl*h0`{zmW!C-P^{BH~W5En5N#9dWCMUDCW@T&_?0qPH@wchyQDNyo2Sd@l
z@zNJ}ExD+wa<k*;#y8RVdqWRZG@bK|k&NS5dEw2J@a=OJsJB(g7&^93&wIV<)YI(C
zsb4?sJ@|C$QB9-TxLqN3`K#<a16RHHH2u#(qk<o5vN6A6cRS81InV9iu+sC^()Rf(
z`EqgbX>%@HTDbixxmgsbT&ZTOY~#2jFY&Yb^2pBr)7QUNS>FGz{xe_cy2*^^dK0TH
zH-Gsb9bdWU^O8A%zZqZF-w}$u@l|T)?^WI@zpv!{EUP{8|Mzc&JDcr-yOupWaqVUL
ziC^05uCCjaF=vf!?T(xbLx*?yx1xWa>Rf)>T79lY{@e>8_cE?1yy;VZb*k^M@z(Y7
zL9z2}mlWN|4GsD}Q^B~<dGX$j&yHRDy|eaK!-M_1`7F;*oV{uFi6_^#rqsNynp7TR
zyYpv9)TV8Y%f6lLSyiADW%h67v5A{jMor6ERaJ6hcb1H9`86vy>-CAzGw&{0tCM>%
z=-S*1K6&Tug8p4n3ywRpQb#}j!JF&L&#h8CFB=;A@79i8HupXVylrl*eYfv0-}FU2
zSs(UJX?^GN@shG1pZhFtf7u1hXLqXmO=o-iZqhGxTRrtTAJZ*AZfSoy#Z3Q%rDb&L
zoe4^k^=Hjy+T=}N6SXB+Zg+b7pRX_Dm#pw$Qvah=|0hhRUPSzKc_ZtoyR59AD)ZLW
z+*rPu&v^dVHR}K0iQaHOZfV7|;P3U<@1-w2>A$_|)c?DIpSWb-iz<87UOl0#`{a8&
z+v)Y+`zFlK+wWT!@w=yH|07Y0g)%S8yZ@`%>+FlX@Ah_jsrc#l+vi0joe^R?cB}IC
z)lHQr)lSN84=y_QE6~Wed4KxEwNkZO%znRSKat%X@$X8J#vQwU*_le^@e{O8vxiTT
zUbf`qf!3D>>vr_bE?psV=R!nSZ2hh^Gfjft%{Oori??=8<C&{{onLFpyuV_nRHvVt
zp1k|aZS|5j=MSIX;VU(#Ec}M?x!Rap<rPLbv-xH(Ep7k3^ybO>$%~`%rnc$MpZqxL
zpR1q6KhDfsf9f)CeyOwE8TlYvDwpfeo^tQozsgVEY|okSE_C|4$CZ-hp50%jf7=*4
z)%{St{hnhPJ2zfSy>+#8Q>x?QVCMN(KY!2eH)~#6ynFS+SN~la>cYa!-|YB!jMro$
zV_JQg*ZhV1zs)(E_Ue7;`M#j_OXsOBjobZq=i;Y}ir3vc&V7z|hOp6x_s_Q<e}8-R
z@6CKxTNm@>UXJ{=^}U|0^|wjq=IT$KV>kV@W$pS2lh^I@y8W<g^^>O4P2XPGJFb(M
zu==gBMz~dctXW*;UlB=}AA5RbEaR4a*&mhm^^Lx^RgBW>b2~lK=G?X~Sra7GHvQ7!
z&!Urh7BoBxeJho*GhD<!$A$6el@l33Vv9WEf-k!4?#<xY-*IpKN{x9pI+(gvzW95q
z<C<!(p5jTi!`z3t6-B2c{Y*R``7pXCcH^%M**LDSkV(&-_|Jv^%igLS`+QmF<jKqZ
z11??eN<C;Qelj_D`l%Cz<tLVKmY?1zn?H52)%k0}E@!RIUYTrlJLvM0n|bP|cb)dT
zzW)B%nR@=Rw+w6ye{7k&a=v;&VY7A0jn><#UzS=wZObnA&RdsJT+}qL^p~9F>bvGe
zRjXq!>HTKTtUkn-c{geImaHI=t7gadtI0;$>MQHtRPk=}n{Iuqf9iGdIl9(j&tBBu
zv#VQVvp1pVj$QIvn|-g+Ds!$f?JPga_Om2=-M4suo=N5J&)z&feR^8U_1>+UWp3Ij
zy~w_PeWLmAl+61(H*MWhTj%|=dbQ5Ys#6y~1%>^sRSP|R`1h-~|FdMCzWn>$O>XJr
zaI3rP?W;E^|6O;8PvX&uFAHz5?v<^4P~$H5>ct7==i$@c&#&L#zrEDL<=WivYWeH)
z<K4yLZ@L`gwfFiJAM9WKw8M?DpXYY<kB7hi*sv#GpFPX_+%Xx+FAo$>>Ykd~B+m8Z
zTKM!V`Ks*JJAYsC?5w<9G4oS~`^-NNT8kd;nO1b{Nb!=dFY+!GJ=l6_$?bpgAL?`7
zdtS==P+IiyiTBdG7bX^6yTDoW>S^%9>vAWq{T1_iU#R``(#O|J6HYc4y`GX<bpFiM
zPnUGRXV_O(oVHtd`0Lra!%vn@K0j^k@$J*58^1nu{#4_gpsy1Omdw1QqW)pGVuk2g
zR&~EAeaHQ$rX9bpIP+}Wgs4b;Z@0Vshp(;p-#Dk@k#pkt{QW-XUYmH|UU@oX=c7vt
zi(IEK5Z?WD+RN#s(=}~(d!3(m-Rr&g`iXY6>!;N<dslrFUHtG@;JlmJlfLlETyd%U
zyGPpUW%~CW_l(~9+}yQb?jDQvvu(_i7un3-9Z+`mo=$bz+a|?hUvIPKpZtD$-o*K4
zYo^VY-x*w2U9ss;*?EPZZ>Mek_IkHD?*#krJ3WuhuUNfh{pU$nynnm>=W{b#^R;bG
z(IGX<-JeA)lmGlQSU1P~dBo0|ldeV^9$4+ndZr^;_qgh1-P@|2B{yV?lC~c$m}_EH
z9%?a9=Grs4y=$jL9^b3vF1OWNO6J>Anc^e$Pe1IsfAIU$@Y!L%+GXZ`TWI24AAj!j
zkJaBMt>QmDL27%}Z@FJRJXTqod8`tBFI+25xp31w<9GQ!FFhGU$F}}hmBY(p^|e2T
zpPKYXwdf{q^3wM$g}3^I*}TqvJiGMOj%3x(uRd>eFMYYzcI8z8OUvpVbAs!;%#04a
z@z0-Oxir<_jLWKZ-yCmmP^x~pV&THaQzsehnIboD{e%=2R+05t*BwspT~v{z%%!o+
z&291~0pCxvy3}svx+MQ_{Zi-a`tgXHNPv&)`|EC7^fF60J<TNIYDD6$9P9YvAamyW
zJO0b>m5<0jY0Eb>%Q&K1ba$1O)t()J58kF`etOF0?l(jBhP%?D(_gZ8pSZkpcIvq$
z)lVPk^`8tBKg;VsDW>pL>Zg*avp$7A7EbxtbNI>1jmKYJ4{v;WbjsfFkQMD_<wegt
z@3owFdVjQM{{FkF_v?aG;`eNfYM=C7{#D$!LY1y8o%}gJuM0OH`grWMy1oxz{oUyD
zl*8A1o}IoPI&FK==B@eh#a(Y?F2()6xAgfb>*eX$_icakRIYr>xoK-?@Z0X({)x-=
z>?41>ykGou;%TW<^VLnK?sgBGYQ3&>Nx~#$=jU%;H%Lle`q`#qSzM8wX{zGR_ueod
zE5=0aN6suq%L}g#pP%4-eEYODE7d)p_1rSp7vd6qu4GB6W80S436k5ct>O8x^~4(0
zNUL)t|AWhA)2Gze{mzq3@A|hh@|vA>aN1q-U_EUKZP|PY)!a6}35$21yOS;?bo0-p
zk5>0I{CEFzdV4e2f3oB1-KQ^<PJeY_^Ou_ot^R4q|N3oI`TG9(R@0+<l~T_?JNwY%
zbK?HGO`qp{`&6_#>{Hg$Q@>eeT<Tc2s4&0y+P6D<x9hLrDBGX0?aP~uK2=|1_4n%U
z_FcF4)A^u+v%yQlYWuIBo4$U+9J|vhyC2J5`mkib$6Mw5Q@*_Ve|7)ti7!p}O?&0O
zN4wob|JL(Y!l`$s`L`&0tzG=xZT+iK`>9@2>)O|s8>FSb?|nXz&AIA8@czlhoA*x(
zti3nkW9_}EoVNcIA6xBLdaPF$s}}QNW6!$}jXhTH<&FN`S)Y1o=h5G#f!F1qK0J8;
z?c%6s|7xE<uA92K@1Jk?z4T0*jR)U9ye?5)_Mvon`5#T2d*3eJKXhZ8z5LEszc-wZ
z`M&i}sn3PVx%VcVv|N93!(+LrtD|j{YG1Fr?#4Ewd1Le|9?3F~e|COZn_S%H1gwy~
zmea?RZRNerzH(KMb>^ZT%fvlBd4^8Mwn?n{e_r_Fo_{UgsaHCim*#)t+4<s0>Ls=6
zUYU#U{BkFs+q~4XUwY|LQROF37BWAXlKA(@mO}B9m5=o&tYh5GE>q;}p4+b&?s@&-
zb&vB0!@aNDO@6wkTdXa1=OTqK(#usY_8q)v*{OY)Td|X$OC#>;y2feM&}`M)qn4^T
z>xrqT@0Qyg$3Aqw_&W>Q^zOR1zBeQG(hH$UIY%BveCsv8r;%vm_*`Y(K2H4%=QsI}
zWzJle>xjsyaXQtW`{~uRw5p<I?vn$JYEpA6^9l=s{;w~7ePUj;?9F2LlP`?QBJ_X0
z{-X5vW_jr39@%qTcE_Ji?UDMF_Ws<a1lL*nQvLsbdHvMy`Hl}qR_$7Q!y$dkmenQ8
zZ9aMwzSw(&Pc-%4?}KTwHf6T9KF9lHUvK_${^zqcv4;$gUiGSIy;yO*<IDH?-&^AL
z=KTEHe)pl_zN+f?q2}^7*YyAVi;JDO)4xH}TbcP%_4jhF{{6qV9{TY6*T>Vc3(KW{
zu;dEg++$f$8ub00(%V}r?{`L+a$09h@JL+vBHBx{tbTuVSDC2e_L>c<GU^lNzI(Go
znXTx?;=^7wf4A)KnSPR=cgmstJ(G`b6VG~ITk^o}UU~fU59im{{C-zfV5?}ZUyyRO
z#{JpZ+tO>J&Xz1W`zI=>I8BPj@Wh#&Gat@BT>F0Yy#3B;wmYKO^ddBb-$&>iHt(63
zDD8gYiaPu04eVB4|9#hPy=x|;oUY@4*(^R}Wr&=medsIEH(MVzy(+r0$a#w|7ysnP
z)8*INmOEQZ{yU-Y_Sf}mFLS^ASgrkZZMpf&^BvN{SM5TR{<_3m{aUpD!28B-<1eAH
z`rC3;qWjNJT9n*(Q+=Py=cQLnZkBii|LvJ|lh^8`FK@NS|6h-n)-FyhIwz>TBy8^Y
z?>md!B0rxnf3o3?`;vnzzn|V1o9^{_o$->l9ruc!we%I$IU3iRp13*r{It*N^R%D$
z?2Pb}w+OiPRchtgcSVcmOfq&avbwh*xBgX3W@%s1yg9e(8t2r#%gL;KxJKyVQXAV<
zmziXPLlOcPy0T6`uu4l<+T@1$oE3lXmL6_<@NZ4+pW46HPJj0W%;z+3I(+_!<Epy%
zFaLg;*Z1js@8Tz)*{7e_FCVSH`~6=3f5qoNZ+iZ)JoWsu?<wbh`9J-Ad;j%+@1NcM
zb$@T&=2!BcDj(kdbYTYn*4OOyO2Yd8g7S8JFn)Rc8Mo!)lmEADT|KLP7PF*eA>R@%
zw>+C7+0s+LZ`JY6pD0&qc3RcctnF=F@!}HSxxq^Nrc9k4I#)aV>J07X)z40gzBGPn
zq#j>gd7x2oebJ{Uvl`Z3{->6^YL8Cts`nGjO#kaG533LLTl;laO1g;W<x|!Vs&3BT
zeB4Ir4?mm2Ekg?{^}^H&MU{_^j7LLAX!pA5^rcG1@i<5))k`m5V<zRF;&IgFZql10
z4|I=RT7EioHQ#h~=j~#_;d@r3^S#-3cA}c`>aNS{3_e)$yG>_Q)|;H3cSmdP?)$>?
zK4e!qez4Tba{BUi^7*N~oBbxSU$^@7vZ-p5#ow69i=1|+UH=+if4NG%;MTQzx3aff
zdqtCax)kyRe%4ewT1?sT@WATd-(O|legFI4{o79K!^+;=5S%kX*4oS>aJ8Mihe-0D
zbN=_=@T}i$yL(&9^_TmV@;tZiw|I7JrmA4!vb$>%7Ob>ZW^J3%mGbw%`HLxKU2_by
zU3clN(QBS&KE-hRwrSRK`<&!%n)x4Jm+?pF$I%CIp2C%)XYHBfm&|>8>#cCYqR0?)
zUpb3ylkOBdt<rfF<!N2$GAqX1&Uf9svIo!SPPz8&@~syYi*3HX`e*e*V|MAR_0Ij-
zy~_6{zG~kuyzh`p<sV7^)1K+}s>@4uJNv!e?GhDl>3b`tx_680W#9O%OY*p%eZBMY
zxySF1(@)+E_nm57y>8NF>p!07PSyDp^=UVy$!~R@CHHZr$%@}=Q?BVuJI33s`ufED
zrLNo){pPx~@o=(DX=>_d5Gvr$IQ{Oubbf^84T&}DJ{5l#6`gvw*5l>7XfM%4f1A3_
zWbMtlDb(e6xqkA+@LGlR?LR|q+q|~_P#&|#x4diVy3ZHw%*x`XCeKa(ez7)eR(5rI
zXy=Eb4Xdsf#QWPepOt+b@O=Km?@jlLr)~BwnseZ}`IAN3&)T;+c`lM|UR*2`-X`uO
zBv0lTi_YP0MsUGD<*M7`PM(!Bc)0H;<$So&_vyeSuPUcE6J6V1eE4)^)w3Y;{j)4?
zN;A#>m{9W4sHVe5rmEj$XGJ5Qp0D1$-!jv4O|wouzuq$?TkQIj?|Hrx?+ULvwK%iA
zUERTa$sCQ^xt&G(Hb>~?{jGY?qWs0o<I;o^hH3RH+K=p>d?NlmPn@KXW#ZZ|eqQ$5
z3jP;9ODNfIKI`%`w&TTfHyXGWiL=Ve7u<Lh^468{ww}k-YmCz8ZdbSZ{Nj;GTr|^8
z+1D@bs?)7Cr>A{3o2MK6-^w-q?&e=puRdHouZ#Kh`@Q<{m$p0K{`Brv?Yzmx^^w-H
z0eO3@bY(){<<DDHyDaaN9i!T;+q17uxIQcW#An~&r~19wrw;4BK9&3S^{KXE{)_8g
zOwyd(es%KecC$&Z*XJ%d?kc!!%PQ@hkQJ8s$2vc}pBfu>?NWU4CDFxCX7HU}68HJJ
zis@d7cgCqtPadz+dcNhCgWmohsWS~eD9$v<n4EJ)R{Qn3_nQ7^yX3;JOl^5(yjE$+
zuQ{t%f4h=y|4Ga8{zumb(|cYopY8f@-tOzUjqxu2>~)bl|7kk^pD&bCn|I4H$Rc4)
z*@K(XTeT)=YA^rGw7vJbl;nNyu)VRrBi5(oJoeX3FSnRsQgn}_eM*?-_meYMZa)z@
zHE$|+koYtXgQZ`0^%NQ{`&KeDKtG(Tf4=JH=(E#z<}N?wTq*1<y_zb`im%t>^&!m)
zQL8;G>UYU?oLK+a%j0O$>3h*Vb1SyyL`1pz&-$BMxNFZQt?2gY&YxGeEcTA7+PLMN
zajaiw#fPH#W(S||SmAkEHa%ADe?-|jo~RJdZ)<!e_ggP-Q#YAkXp~xVYHHOQ<GInP
zf3DW^?`3yt`mg`#>T1_EUh5yXr>IK3`c|5^q;P)tl925+!IN(;JI_>9>fGP?F8BGJ
z=yNNdYiaKL^KS8n;-q)C4dc^nO1IwmaAWDOZRzRjt=;_M!o2UFJJV~@e#UV3s^!Pl
zi2VKgYFoyuuTgsIMD{J6)g_YrE`M?InWpq#uPqke>#p*zzyEbg&DSf+zh2GMXOfpI
z{d#73^6^)z)2FOYXIXztn|r(LvlE-o20fiA7W!o7v#C!vUN(9%&3n~}v$0wy&o<ug
z{9F}w>7d#r@98Ycr=OnAK7Ey2>O_mBZtA@`^EPE26Ogr;P=0>C_V;x+3LNBjy_Kwd
zGf&p>eO27N9Z%1Hn*NmalgGi3nPoqvW|qBldU>yE^FF0zM+L1P+HJRgonEJ!zOBah
zoK59&AE{qceB`!!=k5Qhy*GZ<j;OppaeMMj3Ty2@a6fhU_~iUdgEk4tpS#YN^7rrj
zy`Fo?i)p-@z$0jJD#^d|C(kk4z2vUv{6nQ(ITydUpR`Qw=Xtd|x=8&E*PrsL<s!Bl
zoXg(*@Xugv`QiUy`ifsW?Qd=Qp04*wq{hF0%Z=xM#q!TgShUiTeM`avrIUM$7C+(J
z6TH9tX=(HJ(;bTWr#~!wfAZz?KjE|FzS~=F%KQ?2)3t4u+PQA4OLJns>b;DqT=-?}
z9Cc=G|9k3t-kjfU^}N0A#v~VOHuvW$>v;5ARP2r(JsKFgn=R@cXeinK|DVrBpC|j<
zX$D_ZS-*1S%9W{FQit{B>V7;-Rjc~zB6?I-dT-)!w=Z?ME`eu4oF?@~$ecFXBhg@X
z<eynz)VsgdlX){g*|r?%xBYZ@-m!zxJ)yfj#q?IMRZpECJx8frKU4ksd8svryQO2b
zmj2a=e4Ee5KE3QY>p#5@;#rG6cDl8nu5Z(u^40tGsY{-{Dd#QR@2%Kdu2i(Lcv(sD
zve%uHCtsGEw)<MWKox(>8~Mo+!Rv(g9pNy)VDtZcwNv=IJ1Wlq@5H&!S|=SfrQ+F{
zgKo3lnBM$tbN~3>qO{w~FXk^5udFtH8s5mipnA6P)8mc&i>0&g*)HC?=kBW?AKvfW
z^kM2^CbPmPPd_uoZTMbkG4Y{}*uFhCCYDcU|C`dQ{M~n#hurLIvD0|>IxhP<Q~$8l
z8ov9RbUv8zpPI96{;L9moCnXXWTt%B6@EzgcVfHkTJ^u?&;6$^a!vkjwOGA}CG}C0
z;HIiGkAyZluD+N2q<UVI&SdrXwby#XOMjbf(VOt9eD*K7Qq~2#N@rGuP3sEZom^UO
zDv}YR6V&+Z;{pB49S+4C-o@Qi*kgA!)Mlsc=af&XtiOsc|J#xN^n=y>gQWtQ!q49q
z^%bw@Df#rI>Uq@6DVO5Kte&3=S>B%W#?JhU^Ar`-*464;rWcCVLkAE+lv)pXfWf3K
z-i7gK$d_N8T3za9iyD%&s`i5^#goX5tmg^)zBwF8dMqqt{i`+KaGH7Ud*=Rh{d3cf
z7PFsD{5jR>;!(}1wR-lE|I8jq{Cx0a@8phG4}Uh#dHqE~+_Lz|52^E~wx!?W?)5)$
zF!+|l<5g-7%IYpxk1ShVC2M-{rIf{YRrUotEWWfCImNnuzAwL@<8QQpsDFry=AEz=
zi%%_JXMcHOQ4)8T!SBs?&%XWEX)Ilqb7B4N<n6UPe-|F-`}ysq`qS=euBZ2BKRd}D
zyzAs{=G!M{*KSI2?`QjT%JSe2q0E$;o9i<duFse3GP>)Om=<($LXcE^+$2ToYo?RR
z-{tDdi}p^o{a@u`zdd8WOI+lp%>iY0TVlGx&Rs23`QBw(HEHkVlhI#Ps;@2F{ovn>
zoV1iFf9^j1w@>r^-<J!1<|o7l+_w6(Ejv{tW6JGMw_iGJu8b-Fdo%Exb?xOk=J;uw
zgSp)9id7Y>1|C;rXPP^ChtVE^Nk0}eB*6k56!x6S!GSg#L6bd!pjn=}MM(WojRiBj
zJT?VhJHF}NoU|qD_k8ktk^h(((t2Lvv_Y|^d!yi7y-E99<(9>7Skq^-YfbkYqs`0e
zChXhwNLBT#`QMvieHKfX*Y$PJV%f9y=)$U-^LLj|Vf1Z3Wi5Vf%Js|5!Y%qsm%VO(
zUe38^pOmw0#P=<W=S`^JfBts&#7*i($7Ig@zx>C+&2NU*RZ-K47u{?ltc`BD1lwL#
z`}BL_-(PR7)r^dV_usErvCgEcCV!%Rw|UmlH~;tY7tH4l{@~Qur}g*U>*dPc-J-e)
zEgze8pIw_^%$0SzcQOA|OJ)AaXQyA8uC8wp5@)w>+n#x$$@dvH+h?~O@|hhl<E8wr
zD76p2?!DM#A0K^Lp~iZr?e1+eY_HfUofFw_6ntE=bnB^0*<8)xlPmWAx>&njFR-t|
zF+TFr-$nmltWG^N@AGN9`8zj-{pIWYBO`yJsj1ff@x)d4J&zpzm&eKe=j-O>Pv@@v
zo^gHsp2$zj?&ycd?{(bvFZcg(0X!oIpaBGv#2j$c8U9h?(pa|Wx_<~L>R3gBFaF*t
z$SM-d>1mc!sI%Is`#NlD?xXGQC+)WFFQa>6FILR3*=+H{;IHZFODTUZaWA=jM(q*L
zDbAXCuI<wg-@2pBtUqJf{Kt#z9-oR{C!zG;qPp+j=cYA!pT143p8xby&aZ{P9{$wY
zp%HEU{X}Z|`Y+#|r+OaRRQ!CO@S?0uSLRxLy{ENe%_833b=B3XsmuL-`(;!wKHJ!H
zXC8Zy?~2`z=N89pOexpDBv+i;n8JH%YSrRh-p_8xoSZR-^{~gD(2FZxUouwN|3GMG
zWsrGgQH6fx%5wF}*KdrUoMq$v#%u8V<J~towcT6%kM~{O?t9y`u49k=n&KZ>GNRdg
z_7%B3sP12@W9_#>xjuiY>iap@g39Jy)vMWUdR=zr%z`t2SI%4cxbA)X-(RiyPrrR_
z<9&F0&)<FR(-%y<B>YxF29YyR=0d<-L*Zk~Kx3s+hxHeMN-0nU1)mQMbTFJcAw^m2
z_$D6xwiUgCnJO>tfB(L9>CrF0IW*&ozUUs?wbXaIyI9E8i%;(@U1?O6X)S5?+ow=1
zy=$`M9^W1={d%iEOFpFB+_IT(V!GSk%=xuP|LzXY3Y(-m)o;=|zdDOuCcd?Sd2h`7
zF1Chv?74Vs?ve0S&kdinsD1KY{Jl%^$ui&HeeF8uwcQsj{1^ZC-S41q2G@T&ub%h*
z`T4hGX<z3%(SWSoyAxOF+}xTzeV>>6-A_87#Ihp2y~LZYT#t>q5Vq{hD-~(?IF0_;
z+rHbj=WFNByXxo8_x*c`YiauKgf~AI1TQaG@^jG!1DVV#n=2o6cDcN(4isA*FSjJ7
zvy7(?GXLvY6X5W1RT=M=JKCTU1zNrkE?5M+lW$Ist6JIek!9}W9lx~pOfk2asQ7QW
zK~49@-Rh<nE<QbZRP$2()rUsqj_E5WzHgCV7QghGxYeE&p6hvgWp*Z3m{%^iY<tNz
zcbm`3E8cM{=Pk4U{OY~@**uMMD{+tM0VRRwi!FEEdc7&}&(9vcmI*>4txdf_wmyFx
zE<SVE#jnzzmiTaAvRm*`QS)^xUo5IqSGpP+bayxV@qmw-VW0p1<~HUPGiy*hw))pD
z#mSqWw*C41WL@LirwdnGpT2*Lhv)Zt&B=!xl`pMl(tc#me_w6>T{GW*Tay;-nL1l{
zecwvKx)=Ljdv9j?uNyH@^ZmWq%JcUaMDDw{^JT;N?w_UK-xVwg2;aHm@n7Q&Mt8Ln
zeVQ-cZtQ)u?^ya|_4fGRj}s3cI_>@VVBFDSg}VhydiET0|GEDFd;7s-ayX{$h)k}{
z4_hC$3e4XWTz4Zl`hHSQ<o)2%lFGcoE}n?ldad4i6OI{cbza^-+3sb)`BKU98>v-?
zJZjfXO_o|aweeg=rNZLE3Zs{Em-RiFSGiHds`l{t;+Id(Onxl2N5%h~<+O{f0+R$*
zEO-=mb+Oaw#yoHF*Q=8Stp9HgZl8H|{hE8vcbYBbHIBO~YVzuLto7ZSp8MCmzM_8Y
zM6Le0Db@0_8r$#R3%jPfTDf0W=jWLj)2FAs5l-IscZtRu`TbMhz5G-!BHn)Z$h@<&
z4<CHA*s=ML;o>dZqr0bSUE1;9^=DX^*q>d^Qx7itg53DjSTF-tp*`yC%{X)e98AeS
zTtRaj>*uckWo}Se`aK&o9|9SCo%V2P(*a@0NAXu!!nf@^dSlDw-LE(ezdHO4Tzx6!
z#-^}KezrdKMa!0i{15V%*=GHR&-Ufwbrm;nJQsX9-7va-Uu=0u@squ$gPuIvynd4O
zoWzIG-LVgJCiSV`RgLE6PcBhxQL)il_2ca;i6j3G^<*Dg!y}Q9v&MS!q%E^vw&a?h
zp1831o7J`h6GK0L-&I-aESni~zkbE;x)b$FCO&lg`TM-;eCE%Gx8*)m5R}wX%J&VE
zI{it~c;b5L%GQ1(v4h8I=k*(X`j&I*#Pb&#p*Or(@Qg)(f`-H}Df+5=%*{knM_<q9
z>O<9X*I8d;g57_e&s(Q{GRSX-`}~5GYxh)d2VG7HUd$sK=e8_HHF{MDzyFIro3}2u
zf4y$*+?iIpVqWM>xG3I#dHd`{P+(}Qg&+5sC-di5bLru>+Wd)OVymL}_nx_{$z0rh
z=-b)5I&Ybo)B4X&UE}CzeEiv)<X65kRUdWtwx7Qtojb)=c~NiCy*;&u&wg(Fzi#ig
zWe4p(KHK}-`enl6b=y{U{EzM8QF$tuvnTxBirbrA`y_wX{VAL3yCu1x#P89m4^AGB
znwh%7w`MPR8V9XQL6on=Y;e%<><7<$e-#de3=@Wcx+RQ9q1}?aySuhR$}Xcl0*|b>
zo#d}Jda~<r`-$S>RWDv_N{XBnvuWL?p6G(Kf=R2(?Q1tpob_vV&Wf5<IsU6vCbNIz
zwtwvUGi{mj|Jg52Bo_xSxw-PXmwx&zKD}4P>tiey2ruSVY<bhZXwjv3*~tQF?}XG>
zd))r=M=A4W^=3Y^%;(R$CJS#kZBWSew8dBY{sxztjfM3WpB=M2o04|0(x~{N!_MdX
zd_TR3FMFLU{QUXn>6#P6|GM0p=esg5@>!c`srKd*ACeYT@s{2;H$SuD`S&Bu8OI;I
zm~&+6!`2B$*jZ603PIUqmVp_l2X|4TP7u`Oc&)w))S`#33}EV7*`gx7(PCO^#Y%+_
zE^R;fOSvcNMA|LswlOpfcAsu98g}(!>WNLl&HR_=UvLOoUAx%d>{n>?0`;I{rP*5i
zOSW^gPd~3$_+)o)pU&*5S~t&rzZc~*+sHeNKkxVpCk{pS&RK85C;!{|AZywsi;Mt+
zM^l#0wR*U5$~)ui31$zSVr?GQ?seV%w)*PX)}5_I*RJdhzjH<MZsz-|X5mi{{3tTG
zHg}q0ubRoEs>S_NcQIReHrE|2?Au$jHKkmC(&>DyOW{mAUn_mun|E96m!p=&eipMu
z(m7Lhb8{U!`0Vn=<nKGB++t;gB@>itZIzwtdw<D&7t2qrd~L>Z*!$(fpIn}9<W+$O
zKOFkt)X^`Yw%+bq$UDixEq!|ZVrd@dw@zJpeU|f+Cy@tlA6jBs?=wF`Ciro#ZQ`aK
zscmaAZ+QQ&bkB9KoAda@^717oUPxYjvgX(0@11(DjQ7_in1Rxb!2N(f(Hrl4i<u~r
z$7l7a&*|k>oA9Ed<tI9Srh9(=lf3lj7q=i?{k1L)np=gX-t0|U9{>G8sLiQstG|ZY
zX<e&$YnzzwQ8ahUE)TQZn_K-)+zfR5SG+2zt4m93-HO|jR^C{&$UCK_^UGN_-Y?f{
z%qok1L>i~EA_Bt@lvF@Hilf5P7h5)h7m*zM&<!e%<Lm=Ljag6u{QkOIzRkvokVcgL
zo)1o(k2xXTizDVa6XUxsKbfZOfAQkenMYR|6_oncZuMTzdhq$KC8d&bKkjsec>VR2
zE8CRfoi^*t&Vm^^{wtTLdtMRMe^neS#i`gbq3gtY`#00V@9pw^S6i~+PHn#W%zdw3
zd{}$Z>ioUx(?(zK%oJi&^9!4O!zupl<&3Ppl&5b^t53#R-@h1J)Lq1%adF)u=3UEw
zJXjx6xPDGyey-TtX=(Y}GZf`ZUM<=GZ?^C3C+^LkMBZ9}S})M975NQFQI>fp1pY60
zxAC6CyV*5OeR}$OUK2N-?z?nx>HDt5UzYg3`q4IN*1ySiCuhtsa`Lb9c~iR2W@eQ8
z|DTur4t^-A>o@zgd9BRDo=NZg*6q8M`j7SbtJFQ0S_PaulotK_{p~)#*=zPMtJ%Nq
z-?^~$U;e71&rN&zzs<M2J~8`Q`iT=$BQO1b7X0K(YGl!=L(?Yh)v(TZXld25WybD&
zmBO<#=7cJ(2@$z{_l{qY*|RAH7n+;BT;~1EUN`mTV)he%YuQgtUOrd#ym`(Z|Avph
z3VL%QBX-V@5w>_|ardTDx$cesf8S8G5|ZC_y6<dzt9nNfcmK7EOMlOb3MzLCwT%q=
z*<t>b)2-~6ne9QtrGFQ0+IA}P*ZF+;hbP3hFM09d(Y>&o&dag&$EKQZeW_@DE&Zdh
zKZl~q%OBq+EB$=+miN<1?#WN@Ru_MHYj*elJ-6W6y+N%qq9s#y$7^3%zw*z^*>e{L
z8oYaZBCk3p{YO~VtFqL0YftM*?)`o(%{_ig<$K)=-<DVJHc8kjXgSHuv?BUaMAf4J
z_g!V>PiD!uUrV$6l)@{MbnV+dvn^s?LT@XmkZjJ|evg4?8I^ZA6aLpmdHyrA_x1mz
z6C34SwQl9&!>4C*_g$DQ6MBB`T7SJ&;lBdsl}Me)?F~}>|KeX&(&Mis?)&DxKXK)=
z)$0$J&Yqlne6Cs1QsY4O85ecgUYy^ht=J+k>CK;ipXFrz?C!@cNItc0d3&pJ_b+>`
z3TB_oko(W~9os)oLWIvW{{0;hKGUo_9(Di9!hgJ1`dd=Ha8E&b-VN(#EVVUteSb2u
z`tH;QUnpH%{G{*W%@2PSu8P|4G&h^JF?rL{^P+o~7SC_I^rput^G2c;k8SVzS!?nq
zKD$*>b!4i2Wwo;Z)0KU5tWHckZDnb+FV^71o#NkDF8D`I$~dk}wL0s_{Mb*g`L{m#
zx%29mmJ@4RC5vuu3Ol$uHq7M8WJ$^J`MR1b_rILn9g{vG$G14;Q)~DM?#zSikLnB$
zOtfa(dUfK`>BhcxC47?;HIu*Y=g+io+NNt8=Kn_H$4vDW0Vl8iU5k$PD&{Q-+|~GW
zt+kiD?Wx<(#mp`&e0DJMpvUJwe_n+DTCuuO>F`A9-aQ9jIIMVnX2tWfZ~n#IbrU@d
zTCe-Y4YXd@2Ud1|JmO|TcsxKNP9lz@Q@Z|7S>K*L%ev;h`SJPBDx(b_N@QbqtzE0O
zHGQe~)7e?xZg*oF&#yWmsp<dfg3;GSVMXyv7k`~^Z*g;?_-Cn?CyF{JuU}s=b4F3`
zzwg?wV)X0h-Zqkn<xp%95VF4F{Qs`U;=J5vZnM72zmnrK)z?3JL%qZ$PF48jao6O-
z_vKgq{xz}g|NGp9<pEY#uD`#z)ph?oDfeFSpEh%tSMIyE_wB6e)25Rj^HA2Y0rkV{
z|Gtj*mhit|30{3sxY*%dwSudEO3uvKST<ezsH$~#KJC-5aliJ?%2Qog`|9qmC5zHt
zmH!e;_Kpgh{4;d-ix;2LmMOm%51B0Y@^WcL<<(5Fx?|q!SDBxDd^y^1mw*$8;*u*n
z!hft-vFK4(mrnluyX#yU99LBcOZM`W@W5A9n?$~ZEUGj)n5{hP$=B}q+OMHAe}26l
zKYgV}(oa{Di+NgIerAi_=48FSwe@s}iQA&6^>KTRCQnFdeyG}csQcE9IL=Of>!*{&
zo6eV17S_#**}SMNX5W^zeR^}luU+0QVZG$}hYz~Po>}~Ta`B9aWc{Ip%B4Z8`1JqF
z&DRQ-t=h|{+i#uwRPXtn2gY%imYrr6_De5OY!PtsnEB97-2HByPpfFDwUKDlmA5DN
z2)g||D!tnIJ}5I`Zvf7k@)cZjgBFQbK)OyJkGO4_3mMCGThxW#`a9B|^t0-D;KR;N
z!(!<ZQ@iy!?kBA|_oif}=^vky7Eg+6oeCQ_d$v#Ol(jm&S<>|DeEYXgPQ1CSclBcG
z(#y{Ciu^9thdq4yE&E)zRP2@X`%^&ye?+X5+gLR1PV@}f7h)L~`|P2yjJn3gMDw*c
zwAm5(0n*+8_4}+L?fD)TQTQ;~?=P4AryI;$Pz;`?JvH<F|JtQ{_LxY-F7vVQ`=yhw
zw>RAM(!-@^A6ZQ*JUnsI%}0{8MuFx}y=%KSN~W7mi(WNn)#B5)-7Sjlr!Iea!RVyq
zx_rZHXHQ=Kzi-QPRdCJAp}4Q@;nFW3YFbs4K@A^}LxQe>2a-VJBo7|KTzat#+OqD{
z;?h_qG%3emkHH>+pu9hS-ZFo>H7lVc(rVArlIT$PU;3Hydyd{%<+IXg?JkoS0UA@a
z{;oTG+G+XjJ2y3A)zZFQzWDTOiTgc?s}AndPS36RIsLN3F6*7Y?x)Ya9UK4A36x_W
zrAx$l@t+U(Fls{X`S_mt1@04rmjBs6#uqD+ly9Ab41XyHy286XtFHG<d3ba=Xk31i
zeck_`+?B=7yZZF%rRJ*66_?dx7B8LnE_?Os4?*>1qLypEbF7Sge)>Rq@RHS=9k(sB
zEV}zK{KU7cOshRLH<oYuvfegOuV~td<$kd{56xV%`1B9q->+|18|(qc_<Pizd<A?K
z>Ee<#;0Xj!uk0XXXbu#jZ=u7ZLZKo7KB_99oeFEdXNk9o&p#wA88mNCdA+l2?7UNJ
zb!CswQT{t^zQ*40w3jc6q7rjg8t>@7VqZ4fu5-of8bw#XWj>aFO)8BxH}>iMw=i^7
zlQ)mhD-b(v?H9cBn&jHWr#sZbPrfsMcP-h4TmR1)VJ8knm3yE?mU6o%rl2gcBtGlZ
zembd+D7&5>IPMx-TJrODQ_0JFkAq(BT(*uU>WZiPbiM3V-fJ%|_gK8y+~esM`!AkH
zjFwMcW1$z!l|66q;?uKd_T^Z7w|V(OXNvnAKBId(-Q1^ZzwV#LKR;GRN>w86%JW?&
zLZEa2>h39GtvZQ!GPJKZg*|?cp}+g|x2>8+TWalhX1rWj^z~5O!{{E<Gpp5m_4OvW
zecg9mUEq~ia)0WwQy+eXpU8gM$EUCV)gnmGYo@C4oR3DQclx+bKb>`7!TMiKa>kU}
z!1=jfem+Z|2TBJY)oy*{fOHEGYj!4Y0(V0}y>(W8a5o*)8{M`U($nw=K^~k1wXG*a
zY)-!}CL24Ycvq~>^E)N8cw#Pv{aR<?=RW;1ceeLpIpL*OJFS`LyH8Y}6T9i^$q9z)
zB~#y)u42hIytc3F%gV~dF^}#jYR_F~wd;$=e6{my_o$hiirceYDtP|K6Xz}MKeq@t
zadetr>~p8IT>A02A5ktP9r3^aW=eQ?$mNSqA7-9i@^)$XtLUECtqYd->CL?!GF_Sd
z%k*n+<u9u9o!aWx_hh?Olw95w7x(Gg`TQpew)aoTd7ioR?#mU6PpfMF{Pf_;=_md7
zyPTEZrq-XY2%KLU4oV3VDsVPNNe|?Yzt2~v9RKBJwQJ9&eXHh7t6f|Y9T|P>jHJlE
zqc=7RZ+`ltd)n!XrS(_U`S#AaS`>a*%V_<<R;yim=Dj;{>)Lbg&pl_0b{B8#(>rDD
zUe<Wg^<evJQ0_YNo|4?fqtBz?GDTCXYfp84U!Pw7omooL=Nd&?oy${97T-F#?zI1v
zi%;hqs=Bn~v(3x$TlHT&ou_RMp67dd!t1_0A!S#*E<Swv(^%@%$*<2n8;zZ+EH0Op
z%)GsF{-0lm-(B!f?X1~h2dZ2<<U7meK=(v>qK=7sM#7V5Q#-uqx_uKe8mY1hGSdbc
z5O<fa-2xv}3OZKvA;MlZHZJ<{iA{ogpIliu{nUi1UsGS~(Jn1{xoO!flb5EEZl$%s
z^9?l2E#9U);#_`O+dcg9#izZ?B#X)}XD`{mIZUrK@6yH8Lxtg|&)<K?)hi_X^=FwX
zD7Aj%zS!rBlv=?>J?XjMbbh}6g^N$0F74a%@rB@|+IKE5w=e7JnPci=f8Vur`kRg7
zCmQVdJs+RB&ph98qN88qnyj#Jv+Wjo60up!Pc%%syTtXZd&&mmIQQv_Th&kX$G_v~
z-BkahM;w$mf6T?z!YAFKs`2mdELq(Bse6C;q`tMEmfl|EX0%elT`&GtnEP}-ZfS47
z?_Z5Jly0<(4A?GFy`ijh=IuR`>y~wM>3jR<X_xlJ>xH*2I{jda=clXH{jF}61{ddj
zTof?By6zh%s3okwNVXl?nm}Fjl|fvk4_fxSuw70IvOWitVj1uMub#bUkBPJU^z+-T
zjZ!}-Sib-DS+)G5pwX5GQnImnk-?#UZ>$5&zWfiGuMtw#^;P%Sy8KIl_i`qzT6{V_
zN=DCA|D~F9u-c0kMi*vkthX(Q&Y1FIiW<0fZ8;*0wZTnXo^O7*ROZm>e_=&?{8cKH
zoKG=*y)*UtHIoY$pEe&idh)W`Z_?ynJrg{&FQtAsBls}7XRB=S#yi%JY%^nio7XNX
z=$)*-^X#lseV4OdzW5Zix5TpKf#D@<TYvZIj^|i@UjA`(VW5}$mJf%{+ki6GBi}8L
z1+k9T65nbu+S3xCZ9eOx%Du;vyUxG2e?M{iyA#(gKD}EleZqYH)d`o+&s4g;c7+~u
zTFIn;t#3<9b|yTWXq5e7bJ0)D{VaCnO2@6NpKLqKH&NZ%gx_s?iMv%y)s8uF^}G7?
zUN!#ezU9N+TU1c-edAwGP$Nr-A2l(6du_zGO?Ov*ekOEGxWw;~)OGE+N4)+i9lG!D
z-uHB$F8f;g#Cv<|OW*$#7Tx@#BN=+ZW1Xgi<gTL;raMbs*!=w5@Sb`8&Ih{3^46Dh
z=~!=V-ZN9OX5HyEmtT8s4rY7(;?m)ndYg@ox4BtZ+|iD^k}h4l@8?WV_taxgXPFI!
zg;!+TbS@_Uln!2b@!qQ!pI#N+`gD1|)TvuHWtSd)p=VU=St>chKCo(i){?`ge@?bO
zX|GnV`*qd7RsI5QmXlM<E?<7SciO?{yKE+ghDw!Ldb^81y>-Rz)B5^<GEz?;e7Kk8
zQ+rI={yD6@<zg-82wP*6;dC3Z)&xAx_`_Ai=8qDn{rWaZxr<@hqU+lQdlQwT<}X|H
zAW`|(PhUv;Q0FkW!`h6LN}=~3Js)lOz12xJ_6qaY{ePbn-Tdk0oOw65rzd`{9`mR6
z6Agzxmz3<xD0IGQ__g+BSxw=C&uT@BHv3+aEo|9S?pZ1u8@F<zmhW$~X?iOUKHqic
zQH)Oi+TY<BQ<~H3&sRKzG-c`(k()A;r$RReOx|R+2sEiLS0@N+%xv4dGGq6R4yM+X
zFTUowB;V+`wjA87sjGud@mJrk{XP}ace$6<God-<{5oB$T^jo<lDqx{%~5-M`@Y8Y
zb*mH~r<YGjWL;cZ@^aU*S!?!x%-gy1JpZpF4tJAsVz&xEy}9q~)30A1=pNfuV(Fr{
z#>Urudg(dWO>1r^FAO~M;P6*asrIORk!(M_R6}%ZHM5na4(l%h^<h4I=+3a7-L<W|
zS0i9XSKNI!leXTBTJXMx7thV7LI!tv^yU6k9XcQ^S+wELm$QDArLN2R^ycp|P~4yr
zeJsv0U|m$fmNW4eeB91!OqiGb>eUCMno|Ll`H%jqZ2oi}?Bq9no6k?!-p}DSy(!rE
z$(~!nB_%VT#;A4o&rv^m^k5+<*EtD^!i%(%UcncatO4&~1l72pUc%cPty_9#iyRWQ
zs%oV#N|-JBnh9P`G5>yu(<I(D@iuWMucM$LW7*f6OG+yLFMQXi%KGWY-L*@Of0cc*
zEVcdAiaks_Yciu{W2<y60{>Y*-hXN1%l^jmGf%Aan|SA<O|;!Eg&#4~m%m<mYG-@w
z>cyv1o@-SatZjdDOH}4~TShpv+?7UhBy>BLXQVWEUWI>u2gtRzZ-#7%&2ecw6!PVl
z*&@Rnm)DoUEd~4ipjj3k{ih;v-_mV5zyDbB=)>{+qo2OreOppe`MU4Tf&Kdx|KE)b
zxB4cbcYV!Dy~m&OCn$dY&7N<#ZPm0r#>MSAFNAvZUWp}}U$+Uh+hz78X1a;{F4H$l
z7oXmGE~E0gWtiOFV*d;kYl%2efP!Klw2=9Fmr$J2`SKWFgTHo8T1FY0E<tizM$k1k
z&{{4~=stKT4(i9|ic1GBFc96luQ!87`r<DW&^*A!^Z8nkm0j=me!mAP*rM|IuYXsz
znW(t*Y4h=4FJCNLR}mP#uj7eF|GDcYKU_6cssHxWC@XDUNp!Gx`}Ez<^`@Gy{~u-)
z)Vy6{_6#GZxV_IZDvgaRx;J*Vr@ww-r17-s=M76AuHSjwd_inz{YO#FJyTe1;uTMF
z%v$pWvQS~xl&#=3PoOb^iXX0^v5ebC-9$8eT-WC-OC5IGVh0}F`0{x+bOqF_uX~L4
z{1Dk%$kBOhd314UNo8eb+n*m#`wmRHx=H1IlH~Ru9w%c9*7cl?R;mp%xqdPA(W1Of
z)xYFcPX8GE;IrtSqc`Gr%~Oz{Bh#Z-er4tI#oql_W<QDdW7f1;wZUip>q8$v%^iXL
zC<{_fdXbYpHd;*k8u#|l2O)>G8R6gWKkk*k$NbS8+W6TopTnnfQ#U`)+Hu{^hcc2o
z*R1S08?DkTR(AQ~((kLBpB~=Rukw0AJo9{Aa8GFE&CE|n8xl$?_j&F5QmW=P-Rm;*
z|9d{~R@toZnfDs(=AelkkhLJ%P8FJ`i+R914SmCAb*ZI-cNKyPVlKHl!QAxVi)9-H
zS-FBQzP{aYY=huhfAHF~zkfhu9!DNUlwDrttJ<UfV$PARcjVXY-*11MdESW$%pd>m
z-~MuSOi0PizflY0zIhk&zbt+!u(PN%cIE{8Xty)-G^VdPd*NZ~>pSjGr?l;7w+mM)
zoqM+^|Ln9+SHG<KR8?iLr>BtZ#p>93>q-{ADTz99`1`bnOZ5~hj&g%q7k~U9v%yh;
zhq|vbH}10wa+<_TQAwv1{^{R88U6|-=WoZ)-+wnLJUr#qi%qXzCN5k$JLSxR#-hKs
zZWTRVbJ57EzOf`aG~RvsRByMFtjF2bm#lpGW}@|Fzp3BV%prx{)0N9#zA*YeVe`@(
zCns2jzvJ1Pb`veFgk8~&^Eqz~KG1<m?iSepe~<gUI~SbXr+<I<UZwuevUNT;mA~h{
z(J-EFHeuuE>X*-VebKdXcAtK5^{kTz%KmfL%yZGQKDz1J$%*Fcay90cak)*`)ht~)
z+c;Lw_F2-`g_CFcnoW6G9IKRA0Sfev`_T16+NX@;_RN@94jv_=pkUea`5dHRk(!>q
zXU8#V`#tQB>_c(_FS;HT7flq2uQ3W*=AWq@uA8}d@o7_5<<gRu`?k&MdB4~8LuAl=
z^@*u|>NhPH^!>?CGT4<<eko;vanvpIoiSaPWg^2fBdDH1Qz6sj6@lADgD<+?P4l^%
zr{gg(raV-CXO^$C<n-+xpqlVp_Nvz(ivE7x{MB*4(RE3c|8Hg~?q65(r1zGl)t)n@
zR$jZ;tbWqkmbvL{ruz2v-4~bYf%<idqAf1``Y)av+rEDQnwq|lvUDZMCADOn7KhD5
z#fdA}U#xzyNN&Y?xi#`jBBnk6Hcj(&*6AC6YHGK7KVRxwQnIt?;LJ(07TLKgEuM37
z)9X10<lhxNkw4D5zHH}t%cxt|rFG4pq@0jb4_5oqhc(x&s|d7DUb5^)jlQC&DEaAk
z`~A9V$Xr-&WJPDp#&?|TM)QtT*F2K{_U6{2#oeD?&#jg}wdL(h&rMaSv67bQOZxQo
zK960q*n9ictxKbxi8r2~;Bo&H>!*L7eSfwYhT6TC?9n@KWxB-u>%^i%Q`=ue_w?rM
zuuMB&+S&536*MkYr+y2(mu&5d?NY|A&$U$~9m#Ido&FHy_@I*~@XwE@e6>@%yY%Mg
zo#oXP{rT>8_>*T%&x|Sp(`94V%`Kc9vu9ZspMK=qe@pnkonKQpdr$Qg<^B0S6W=dm
zTijjbvQ+!&g@e5*?#%AfmiJDbYwfHrl?<!P;p4t)+Iu0VzK~Qvg9fE1s64waUv|3h
zYoyh#nhz2CFI-YMtzJGM@$}}>lE8V}7ftKaTh6=JOa6uY+@Ik;e=qF&v(3)c{`%9W
zt6o~|(kVDM_3$rQ_1RHYK~J8XXyaWduES0x$AM~(!)?5<+T&1nx$EzTDoyG}C(p|7
z7d?HueEa2#u7897?mm4uyWRWWtzSk~`TP3xWS`Anu-N-}rLW4~TXHMs|17WES=RPv
zlgbClU8g5bmk#B(zrOV3&-Bwb{QBOMNu8LnZ@2S$4^Yd<Nd$SV?yNN?qQ0kYfp+w2
zfF?)~Jr6Ds<eo=G5^~R@yZ!IlCl&KOKDemu1hqD;cCFBktNUN+pRw}9{(E(C|Mt8S
zkhDzR)u(4$#O^hH>%r)rtJ(iz9xQm^UAZD|*&hq_4?lOFo)~`KO65<?^v!<z9E#X1
zgWjZ^nA+QY>gi6si66S)V<?D;9pA7j@Ho1ut?gbP*W?*pYV&hl4j$GQWB2Phed_M+
z^64uxVrO-2+YD|ygLWnJfNSZave3@Q=?{+*>$P|8@mIN05$9P1nl&#i0X69Vd^x+1
zKcO<~<-{W2g8{O!>t-3bTdg^B;o{RB^MX}$_eOB_+d1u<EAr#bou?;$hqEv4{?yeb
z?s08}$<5fq6FD<mpMHy(?g2{>$SVd+G+#p|)IbCNASd^LoxG?aDJ#W9ll$T>6Vcps
zX;2G#Z@lzH3GhG`WG3avqljq_j~<2%R7{>z^Yt4WxR3Y!%`KrQsVALBEsa)vu#t^*
z^N&|ruDfdSV(;IXxhiW<eJ?6qJ?+!k6$hR_({tY!9pyUDclFA}r~j;4yYv{Z<fgR~
zm1obX3yP4aI#|?(S{^|5mQLOT?c9MTYTH(39J=8KZU{k7U0CZKe6egBs0|8Qyxp4!
z?uj%cLEFjsJD*O2RA*7^&R2Yt%K!60&g18&x9YA|J63%71e&k&Zr=0hfXvM8t9JIB
zj9qj1^vvMml9HF3Hch)?^C$LYK~?<EB8LaY#nYAd=dMxV53zM~pU$o&tGLzu>NMV)
zeV}F#>+>m}{~lFJK&_A^<2XEPE-YDtSQOWrs5Q%A8@Ol6)U|Tr#U(l}uP=pcnG0^*
zfmXyN!`4`Od2ABwPKFKxOzNJ$|L)R!-DQhTA9qc5_HO?3;ivSbpU<j{wDQ;W>BS!N
zox(j`cG2O}hR^k<D*MN3SpR0R(=TU@Ej=;QYS*3@DIVSXc)uL}^4((QrDe~~9XJ^M
z!WGmdb@Jdulw_&kX1WPU;k)N>x0L;*OGO6p-QWex9zhG_=KX)xCT6v3Mf<Dwhu<IF
zlpM<}Y4_MoHuhJ4yh{3$trspnJ;N#c^nzIb>B+Uhhc2dGG!#GW?Kg{0|L*xLk(wEG
zQ4h|aOS`$p^2@Bkrf_gS?+D85RYuS?NS6sTOL5FV)K}s*xTnVmJ#wcsQ|s0_vqgq~
zlt4RS48h|?@JXyUyGmbsbvPtXfA)99ddnH#d(U~sB#Xr<-Cyv#Z6bI?>(j&j`(2>5
zE8uQm&foVF_iWv_wP$Ae%s#!t%XKDhQ{MgRMbX7czk>eXNKZN0aO%_XV+Yco>bd9T
z*3Ywfobu|0(c(hq%$nM+x*2ti#*u8iTzeB_(3)!0Nf}N$W_BOtL2IEwqgk83ygjD$
z^TkO`qx_eW58GdEe_!8Rx2${L(TL+ejozGMd-&qhkEQCTV^`{yl<ZvoOEKuPo4dWw
z{u3uBuF>I?VQGsq=|k)6f%lwI6x=yFhr54&XaBtKqGf0H&X|vn!nF=ntCZFJf1hi$
zYX$qO)%NZ|e?G=Pd06V3>XWySPhXs0W6I`yn_&0p!RvQNSbck~vHzaTkDfbEPgGx*
z)!H0n_436hr>xXWlU0-IX8hAr_vTx@$$i_~-<JxTsa|q~uaAp$Vv(}G^}F!@zl3|{
zR_cYi+oOAb3hv*hA7r*7x@+h8(w`;Sc^R1<^UF^E-hOSm;7(A%VVbj3=6Z5hpWbu*
z91ZoaGTQgo@zmJ8D|%udy!u4P6vc<nca^-Dsdspn#iwGgwk^fmJl8#63eJuv{z04a
zu=D{Qt0F0lYrIa5{946sB=mm2_64KqC-z%kxLyew+%w#Jbw<_Xi#_Te<>ns|`!1HO
zI`8$T>IVsLzdb9u+tX>ai)Yq`kUpE2A3x|G^OLY}u!|F`nenf`I4C9IRz#)Mmze3E
z%R?q!Ugvp{|FZm(oC#%boz}k;M(g-NmI14!LK{V(X?mXEi!B>_!F73nPcP`uj;QI&
z7QM{@4|;tEjeUiHsv`KDc-8u{K&1;e*sJU()|VT+m!3O$$F$SNaY-8|oo3p<Pe18=
zfBd@(+r@()y8gACbY6acP}-UOA@0*B22b}`yr%rg9!d3AFN!{0dHgiro9Vydp3@WK
zr&$<nJC?BXvZbH<bn92))7-8vf7Sjnzktott?k6RT6sy~a;n#qWlv8{Rdrdg@%#6>
zD@7@p4$oEA)g0sYw77Cr_m!5oXzZ0x{fRZdpK(<dT-CJNr4c>v)22BkM>S+)Z|Seq
z{I0WR@#53(U&}@YeYctUGG@u*)5R~Rs=VGM^4H*R_@!H(ZNWOyW-W17+-Z<u4|Pv(
zuDlPL&OTqE$k|>sue(d{)la>N+a-Sg?0&oH^E9`jyI+o4?b`DG&x|Yf4?SgLcdc2g
zbp7n9OBbK^_x9OTgcwV{=;5=!zBIesJ#2f~liyo>P8OAU?yp-Y{#BAkAJnb-XpPe3
zoAMPhk_1|KVCds2e9R0y8tPzZYBXy~E7m3yeC<i}tq+R=9=l7OY`k~7@!vUaEA_&s
z#&I4_f|cDH-~ZpMyT#;ye)W^rx1~<3c*$zD>x;w=&sA4`IqAh*322`_r^)&7#njh@
zrAwC^ht4Z__437}>2F`Ozht*FnDki0vzbk)`ddrfbWjhaMFzE*I>i(`94E1C5ofYA
zbbSo?%*QQyzOJ`#g2(dSUw1QU17~6Qs-D<wIgyaA708u`3Km57)Rvu}X=M1nN;bC5
zFG4xgsBT)>^-}J8Nja6Fn){4DMD92}@%wqN$@k9n7Hux>>eKsv)pw$Cd)OuZ%kyW2
z3H&(IAtKruccmTHLY0LNiF!sthrBA1l%)=bm?Y*XgT{_nMS_(Vdq#F#Te>pC8hUij
zt&U>`Apbz7`oS|&-kE{Y%9k!B?Gp6e-~VIs-@8d~4j*4Xxs==b^^2mrTT+9T->X-B
zy=Hc4$<7Sl)SG+sLfxmI-(_W_lDfpFwCcr+O{Qlj2DgVzik;bK6T7+k!Dl6-vYZ#L
zwOof$hKi_AVi@gVxYAWt`rlS)i{b?%{fX-<H_WJ$|0w@^_t6{A|5rH1#TD=A(>wJ{
z_rK!RS9L0}*F&y8TzdbKm({Ks%L5ZvYTACUT<6`Nrx`Rw^v7;1U6so>m2ch@vbey#
zcY{9->TPlUiP}3&>dQ3G9t(^Qp0(Z?<XG{E+a+#){j%Xg(8T$5Kjy#LwRL6BEUBs)
zb<3YK-Husu`p<*n*mDuH)T-YVJeeKXx5mfPcbDa5ki!$L-T$qaqo_M)?xcx|n~Kw4
zwAS)aJ&5%pH!XoorH8%v@aSLdmi4#gm)&>&bM*Ob?vj$58||{^NB8V4EYF`g%R1J5
zdY$w8iCebZUwZ!Q+tL!t`qEuCo6b$l&ntN{Z%f*xn=?0l->LUiM)H#GhnpOKDkGk$
zedX8G;n}MJ8qMO^s00}bfHnQWJrI(TFKGE9kG`Bzcv0rRH$u6Ocht+jef_Qd<#)mV
zA1<3)AKotU``hHb{(I{muYdhy;nCQp^QAL)IoV%-`pMhf<NDUnl8Tr6O<eayJ~;7M
zl6T2&<J|ercf}N?en|YqZ!RhQqo<?fl}Ob^E{64ZTKgNte3#7Xio1X8{l4Gp&ir}U
zE`KUSB>9ihtPFEe(71jE?<sc%hAsM@E{-7~2BxN_TYX&Ln+LkSz6{+F15VW6q&mOf
z7tNUI{p7yu?_JEET4tbil1Frp+1>ogUs->b(@3p+C7=HB^!>{2SL*$%)`5D6R}Oi7
zdZ^_dw<gqCuUzfL^IbXqoOairc6kSVJ^aPq-pt)-PEzIthm`N{E^vo4V6FK+f)`y@
zt^uu715KMi_aTC&s2mJI?!S0sFSuO0m%pg%+VYi<{VK6rG6ErGo9^}xLcCiP`L+})
z{XbWtd@Skl<WJ0Zlin<UH2>+=ug^~`$!xXS6?5nL#{*?zJF~7CTJ4&%S1I;g%+nLu
zUoWL}7#sbFnLcIj`BKfSS10nnN}UJ{Eqnd`_MXW7IjxP@nt8(W!5zfoQJ|d=>+?bT
z&p_L3prsvXq}=xoc-i8MziO*8YG=VZSK`bU+HTxG7I=Chd-uuj0`(jANB4;4?sB|#
zcBiBJbj9oICu>RCpKi6yzi@Hs>075Rr5ASXxpZbC=eg8hJN34=%zORGW0|tIHJcs3
zq@>Z7r`rysJEvS&aG^U)fr`PJTEVzN^sOnRLFbaW|6KRW?-PGKzL)eS-v7;7W&Ql$
ztM}~Lv$;=i@p9eCho#?6bC&)WyKC*bRfkWX<kmhp-^$YH^iB`==^xLfK778bXSa!K
ztwPG_*49_@mlm~pe7yJk3uswT%LF56#_J8O=&bR7b^iC}J)OsAv>cH(`S=pNymIe8
z(80!_-CWStoa^nQZYG8K;Qsda*(*SkfRMQ<ah-?&$eG5c|33URv10n_`hF9k_xs(8
z7VNhcSa#q2QFnFs#_QKiCfu*Tw|IK(&-wpP-M_wV;_K!5*B?IZ`8<E}l`lyzE&qGS
z#-59rsQ1|2YSXRE#f!b`pI22HF74vee{8+<Mf=P8%DDXp(w*YJDBa%na{Af=SQR)o
z1G4=p9K1=t(;r+7ChLMWCtlwU+Hr-n)D1K{to9Dv_5D&O9J<90KAVjgQ(E>&u(Erj
z?e1+aQuqJ;w%cawyP}zMxBIwH-*?#T74t{q_L!B6Pq$`Ydiv;3$jv7vmoK{7UVpmq
zigIa5WYrwy-F<~mzNhIf`5Ach*YBd@ucyPX56qC4=~62`ZrDFn=DjfIyb~cd_x74E
zKcag~uJ(agWzD}3*;qHfSgotse>8YE$zQtYT6wO?t0;Xd+ojVxJ=>>?o_qQ5`7WF4
zzz4<?W@-voy}!i%{_U;SxGha+vm%fkIpl@on>#y;y}|3qSLe_BD8vsMeR=uwQSVjN
zJi~9NUavpB`n2BU!kylw6_)$HmaPo?uKaS(qHTS8=^|m1RDH8vM0Z!te_eFC;lMZk
zKR;VP&c13NQa>?v_m|UQ5me1%D-}MtEPM3f^Y-0dcanT;uP@E0jNfTkKDAFzJg#ne
z-8%2znr}-=GWQ$Bx_v21X)2!Crx$LO{4{^1q}84??}cuB+seG&R4trc{A=Ki4UYSD
zz;#)tB9H#kcJOev@O<zhOVD&3Qx~}G1kFw}9tF=%3x5aKa#i~~wW8*OX7IoR#9R&4
z-5dGkUN8S2YkU3axvS=<bdB?`T}*wkWa*@dA17~lV;$!{-7ovn(#uCgBHzzhwAkA}
zy~}2XP|dp2w;s!C+No}MYh5~N&YXmjm*O=yx%grARbqvvEo2J!2Y3qC<{EfIel7TL
za?ly&pqbnSGeFA_G9KLn_s&qNrd8K_O1{0hxp03c|BE?COrJeuU7N5)NAAbd+6m0U
zmE9ZT>u;=`es}v%#-FeEILU%X1AU|O?mpkCXIUKl^q<z(FU?<+=fD0Gu}oQ>k2B}{
z8Xe6ucZGlObeLo)Y*MdyzKM$;wE+_)_b|2oy}sOYK8l=u;gKn5jr#Q&ZPS_8#@Nqs
z`QUQwzstR(H?QZ;{<7(<^}UI?t&dY{-f_vsoqMy>5z<Y#;o&}=uY2{0v%Hs6W)%1K
z=_Su$1mzlAtqo_h(@sRc-tOtv_KT-OMD*o!Uk=nHc|D>{{Oi6&k@t=y`#3$~okd=7
z8|`7yINR0xy_3a1<xl^2vE)}f_4svWsT6-Ud-dW{xzEBYYyNGWW4Ul~pPp=Z$V9_*
zi%l0S!`-LLoiq8F{Yuel&z;NG8}{}WKe=8iy~LG!>t9zj|7-fQ4bb|IANM>`m4i$j
zKDrDVVEYu&&C~^6HUmA|541F)Aql+jqq7=3kiGZ5i|AT^_+Yls0uybsd+I_q6F-{Y
zi|$$Ze3wrAKBsr{OXv6LUB45dq3c)YGpl^=!o{auv$ed>EjU#)zj#ZZUhJHj$k~_N
z+^4(GV%t`_zu|D(%KtB!zrM+t_~_Av1W<obaY+nxUi@bd=o~oGYozpZb0Q%%l4M+m
ze&E8VS4C(3zLWIk9s7T!+t2T=THO6<iyZh|vqR@gEpsmkewzN})usD8<JzZBJeMFF
z8|PL0#JaS5qw(!illev2x9xs$yXWnlmGNc^ABduKX|vZH@_tesRDW;&Q(v27Dk}FD
zkXLT%9QN*jr_%j<%Ino-WAD|k*!{2kYs~cL&(}_oF24Ta#if_8STAik8vo=_tNW`L
zlZ+p0?EHK|%joLMg^RuQ)4Ob9cP``8|Nlnv#<|F^Pfwedzh-~A`s}`{t#vnB(8kN2
zih>T9${;)H8>0?-*gu&5-SOMg)!WTqKo_B`c)lxV&gP)7C-)aG_Wu8|d+FhypAVle
z-C1yPVNvqxtWEbUUERf>>Q42)cJb-dw8q>!E4of>%e}O6@~vl!{?A;2mP8@rk>ojX
z_VnrDkOYt{7x#z9Kjn}66WwDW4T0xNBR9VBtE_pJY_+RrPMF5rbu(0StL`jW?EQSI
z>C)4z$4>|QE&EagTKJoDYU;rklir=scW2#Jn7DA?A=liz(xa!}-CcN4NT3ZmLDVG>
zcct28n)yeG$rCD?)$G7SVf|Y{yH?`vgU^FT+86RRNqN=|b5PIw;&0!Oi@!|ZYrI5R
z=AHP#56bQHxxU6szy8cd^YQ0tuRj!>YhbcmR~+|ar=D*0m#N}G+NV99uezXnEN{|s
z<4dI#KdrPk)c*c@`Fv5}`I5ld)t}S;ZUSc^6+h?}kf(xiO84jgeqJ%x9G2ZfKp6@;
zc~bS)<?HM9`@gN#NSX=iz}(uJJ$+@y?HRM?&5K*M$Pl_2MCWjKf1tEY&?EUh+a)fa
zFP-^!!I?9+7cBNZAK1O*`3b&<&v(tK`nvR!$5fxl7jvZd9lf#2dDe>;pB(=l*p_?I
z>ECzPjlbLzqPu%!rKL0)AEM=<J%_rL;H5omolzH8ogj2h3Upjg$OrI7Do~jE=eXQH
z2Ck=)L8mN%)_%Xdyd1jnKklPbP2+d5<X=1WHou?P`?LSm3!{>vg*^LLPTe9~y|7Qu
z@^bXaRaIwGH(&PHWx3O8S4{Zc`F(op!^@srkJ{=I`|8r(-M7vuy}i{bBHqsCoq$@z
zL(ZjyPqmAP!e<>oCt|F4AP(9=1!_3YyV0?&7hLZjGk`}j!`4NRNr%0A{s(;jpnL2}
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

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/nb.png b/M4MCode/M4M_MkI/ReadmeFigures/nb.png
new file mode 100644
index 0000000000000000000000000000000000000000..5586c717ad8f77037efab3c6422fb31ef3e7324e
GIT binary patch
literal 2654
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

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/rcs.pdf.png b/M4MCode/M4M_MkI/ReadmeFigures/rcs.pdf.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c6d5b463046dc9ec64a7f28225e38552e771a13
GIT binary patch
literal 89001
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
Q7#J8lUHx3vIVCg!0CTy28UO$Q

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/reconfigedTraces.png b/M4MCode/M4M_MkI/ReadmeFigures/reconfigedTraces.png
new file mode 100644
index 0000000000000000000000000000000000000000..84927a3da494bb992042535d00d9aefd65c0ad85
GIT binary patch
literal 60643
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%0AD_ImfJb+pk}Uh(z>`8O{ru*W3NIC|CgU
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

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/terminal.pdf.png b/M4MCode/M4M_MkI/ReadmeFigures/terminal.pdf.png
new file mode 100644
index 0000000000000000000000000000000000000000..327bfff9893b89ee6cf1253dd5a205f5814c6f8c
GIT binary patch
literal 12709
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

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/traceesSideBySide.png b/M4MCode/M4M_MkI/ReadmeFigures/traceesSideBySide.png
new file mode 100644
index 0000000000000000000000000000000000000000..bbe492938a4b9082ccfbeae33c7b51c81f9ae59e
GIT binary patch
literal 35071
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
Se({bPNWZ76pUXO@geCy};Y5J|

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/ReadmeFigures/traces.png b/M4MCode/M4M_MkI/ReadmeFigures/traces.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6e68191f2a317ab0c9cb209a5555b15522096a7
GIT binary patch
literal 53369
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
Tt!gr!0^)nR`njxgN@xNA6Wdm0

literal 0
HcmV?d00001

diff --git a/M4MCode/M4M_MkI/m4minit.ps1 b/M4MCode/M4M_MkI/m4minit.ps1
new file mode 100644
index 0000000..f8bf8bd
--- /dev/null
+++ b/M4MCode/M4M_MkI/m4minit.ps1
@@ -0,0 +1,173 @@
+param($m4mframework='netcoreapp2.1') # change this if you don't have net core 2.1 (e.g. netcoreapp3.1 or net5)
+
+# run netstateb and m4mb (alias defined below) if you need to build m4m
+
+# $PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition # PS2 compatibility
+$m4mdir = $PSScriptRoot
+$outerdir = ($m4mdir -replace "(.+)(\\|/)M4M_MkI", '$1')
+$rawdir = ($m4mdir -replace "(.+)(\\|/)Code(\\|/)Other(\\|/)M4M_MkI", '$1')
+Write-Output "RAW Directory: $rawdir"
+
+function m4m()
+{
+    dotnet "$outerdir/M4M_MkI/M4M.CoreRunner/bin/Release/$m4mframework/M4M.CoreRunner.dll" $args
+}
+
+function m4mdbg()
+{
+    dotnet "$outerdir/M4M_MkI/M4M.CoreRunner/bin/Debug/$m4mframework/M4M.CoreRunner.dll" $args
+}
+
+function m4m21()
+{
+    dotnet "$outerdir/M4M_MkI/M4M.CoreRunner/bin/Release/netcoreapp2.1/M4M.CoreRunner.dll" $args
+}
+
+function m4m31()
+{
+    dotnet "$outerdir/M4M_MkI/M4M.CoreRunner/bin/Release/netcoreapp3.1/M4M.CoreRunner.dll" $args
+}
+
+function m4m5()
+{
+    dotnet "$outerdir/M4M_MkI/M4M.CoreRunner/bin/Release/net5/M4M.CoreRunner.dll" $args
+}
+
+function m4mded()
+{
+    dotnet "$outerdir/M4M_Ded/M4M_Ded.CoreRunner/bin/Release/netcoreapp2.1/M4M_Ded.CoreRunner.dll" $args
+}
+
+function m4mexe()
+{
+    . "$outerdir/M4M_MkI/M4M/bin/x64/Release/M4M.exe" $args
+}
+
+function netstateb()
+{
+    dotnet build "$outerdir/NetState/NetState/NetState.csproj" --configuration Release
+    dotnet build "$outerdir/NetState/NetState/NetState.csproj" --configuration Debug
+}
+
+function m4mb()
+{
+    # remember to build netstate before you try to build M4M
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Release --framework $m4mframework /p:M4M_DUAL_TARGET=true
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Debug --framework $m4mframework /p:M4M_DUAL_TARGET=true
+}
+
+function m4mb21()
+{
+    # remember to build netstate before you try to build M4M
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Release --framework netcoreapp2.1 /p:M4M_DUAL_TARGET=true
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Debug --framework netcoreapp2.1 /p:M4M_DUAL_TARGET=true
+}
+
+function m4mb31()
+{
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Release --framework netcoreapp3.1 /p:M4M_DUAL_TARGET=true
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Debug --framework netcoreapp3.1 /p:M4M_DUAL_TARGET=true
+}
+
+function m4mb5()
+{
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Release --framework net5 /p:M4M_DUAL_TARGET=true
+    dotnet build "$outerdir/M4M_MkI/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Debug --framework net5 /p:M4M_DUAL_TARGET=true
+}
+
+function m4mclean()
+{
+    $dirs = (Get-ChildItem -r "genome_t.dat" | ForEach-Object{$_.Directory})
+
+    foreach ($d in $dirs) {
+        Write-Output $d
+        Get-ChildItem $d | Where-Object {$_.Name -match ".*[a-z]+[0-9]+\(best\).dat"} | Remove-Item
+        Get-ChildItem $d | Where-Object {$_.Name -match "sudokusamples[0-9]+.dat"} | Remove-Item
+        Get-ChildItem $d | Where-Object {$_.Name -match "ivmcswitches[0-9]+.dat"} | Remove-Item
+        Get-ChildItem $d | Where-Object {$_.Name -match ".*epoch[0-9]0*[1-9][0-9]*save.dat"} | Remove-Item
+    }
+}
+
+$m4mfilepattern = "(.*[a-z]+)([0-9]+)(\(best\)|[a-z]*).dat"
+$m4mbestpattern = "(sudokusamples)([0-9]+).dat|(ivmcswitches)([0-9]+).dat|(.*[a-z]+)([0-9]+)\(best\).dat"
+
+class M4MFileInfo {
+    M4MFileInfo([string] $prefix, [int] $epoch, [string] $filename) {
+        $this.Prefix = $prefix
+        $this.Epoch = $epoch
+        $this.Filename = $filename
+    }
+    [string] $Prefix = $null
+    [int] $Epoch = -1
+    [string] $Filename = $null
+}
+
+function m4minfo($filename)
+{
+    $match = $filename -match $m4mfilepattern;
+    if ($match) {
+        $prefix = $matches[1]
+        $epoch = [Int]$matches[2]
+        [M4MFileInfo]::new($prefix, $epoch, $filename)
+    } else {
+        throw "Unable to match file"
+    }
+}
+
+function m4mdeepclean()
+{
+    $dirs = (Get-ChildItem -r | Where-Object {$_.Name -match $m4mbestpattern} | ForEach-Object{$_.Directory.FullName} | Get-unique)
+
+    foreach ($d in $dirs) {
+        Write-Output $d
+
+        $groups = (Get-ChildItem $d | Where-Object {$_.Name -match $m4mbestpattern} | ForEach-Object {m4minfo $_} | Group-Object -property "Prefix")
+
+        foreach ($g in $groups) {
+            $g.Group | Sort-Object -property Epoch | Select-Object -skiplast 1 | ForEach-Object {"$d/$($_.Filename)"} | Remove-Item
+        }
+    }
+}
+
+function m4mbuild()
+{
+    # doesn't build M4M (Framework) at the moment becuase M4M.Old is a liability
+
+    netstateb
+    m4mb
+    m4mb31
+    m4mb5
+    dotnet build "$outerdir/M4M_Ded/M4M_Ded.CoreRunner/M4M_Ded.CoreRunner.csproj" --configuration Release
+    vs
+    $projs = ("$outerdir/M4MPlotting/M4MPlotting.sln", "$outerdir/M4MDenseDev/M4MDenseDev.sln", "$outerdir/M4MDenseMod/M4MDenseMod.sln")
+    foreach ($proj in $projs) {
+        msbuild $proj /p:Configuration=Debug
+        msbuild $proj /p:Configuration=Release
+        msbuild $proj /p:Configuration=Debug /p:platform=x64
+        msbuild $proj /p:Configuration=Release /p:platform=x64
+    }
+}
+
+function m4mplot()
+{
+    . "$outerdir/M4MPlotting/M4MPlotting/bin/Debug/M4MPlotting.exe" $args
+}
+
+function m4mdensedev()
+{
+    . "$outerdir/M4MDenseDev/M4MDenseDev/bin/Debug/M4MDenseDev.exe" $args
+}
+
+function m4mdensemod()
+{
+    . "$outerdir/M4MDenseMod/M4MDenseMod/bin/Debug/M4MDenseMod.exe" $args
+}
+
+Try
+{
+  # fails on linux remote systems
+  [System.Console]::Title = [System.Console]::Title + " - M4M"
+}
+Catch
+{
+}
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/m4minit.sh b/M4MCode/M4M_MkI/m4minit.sh
new file mode 100644
index 0000000..e5dbc8a
--- /dev/null
+++ b/M4MCode/M4M_MkI/m4minit.sh
@@ -0,0 +1,11 @@
+# run netstateb and m4mb (alias defined below) if you need to build m4m
+
+m4mframework='netcoreapp2.1' # change this if you don't have net core 2.1
+m4mdir=$(pwd)
+
+echo "M4M Dir is $m4mdir"
+
+# alias
+alias netstateb='dotnet build "$m4mdir/../NetState/NetState/NetState.csproj" --configuration Release'
+alias m4mb='dotnet build "$m4mdir/M4M.CoreRunner/M4M.CoreRunner.csproj" --configuration Release --framework $m4mframework /p:M4M_DUAL_TARGET=true'
+alias m4m='dotnet $m4mdir/M4M.CoreRunner/bin/Release/$m4mframework/M4M.CoreRunner.dll'
\ No newline at end of file
diff --git a/M4MCode/M4M_MkI/readme.md b/M4MCode/M4M_MkI/readme.md
new file mode 100644
index 0000000..b857faa
--- /dev/null
+++ b/M4MCode/M4M_MkI/readme.md
@@ -0,0 +1,1367 @@
+# What is this file
+
+This file is full of information about M4M. It includes information about building M4M, running M4M, using M4M, a little information about how M4M works internally, and a couple of examples.
+
+The other thing this file is, is totally incomplete: M4M has always been a rapidly developed program, with little features and additional modes bolted on all the time. This file provides some pointers into the code so that you can hopefully find the most relevant bits quickly.
+
+# M4M
+
+M4M provides a command-line interface for generating, running, plotting, and analysing stuff.
+
+It has 5 assemblies, though only only 4 matter, with different .NET targets.
+
+ - M4M.Model (.NET Standard 2.0): includes the underlying model (`DenseGenome`, `PopulationExperiment`, Targets, `ExperimentFeedback`, etc.), general purpose data structures (`PriorityQueue`, `DisjointSets`, etc.), `Misc` rubbish, and serialisation stuff. Linear Algebra and Random Numbers are handled through the MathNet.Numerics library. Serialisation (all binary) uses the nightmarish NetState. All this stuff is in the `M4M` namespace, not `M4M.Model`.
+
+ - M4M.Old (.NET Framework 4.6.1): old stuff which depends on the .NET Framework which I haven't looked at in years. Ignore it. Actually, I probably deleted it at some point, so don't be shocked if you can't find it.
+
+ - M4M.New (.NET Standard 2.0): new stuff which works. Much of this, too, should be abandoned. Includes the `Epistatics.Ivmc*` stuff, `Cli.*` stuff, experiment setup stuff, and plotting stuff (plotting provided by OxyPlot). Basically, anything which has any interface to the user.
+
+ - M4M.CoreRunner (.NET Core 2.1): the .NET Core runner. (Works with newer versions of .NET Core also, but you'll need to update the scripts accordingly; NOTE: .NET Core 2.1 is EOL in 2021/10; not switched to a new one yet by default due to lazyness and poor CI support)
+
+ - M4M (.NET Framework 4.6.1): the .NET Framework runner. And lots of stuff which consumes M4M.Old but we don't care about.
+
+## Getting up and running
+
+If you can stuff all this stuff in a directory and download a copy of the .NET Framework or [.NET Core](https://dotnet.microsoft.com/download), then you are off to a good start:
+
+Life will be easiest if:
+
+ - You are on windows, have Visual Studio installed, and can load up the developer command prompt
+ - You have the .NET Core SDK 2.1 installed
+ - You have a recent version of powershell (pwsh) installed
+
+Note that while M4M can be run on linux without powershell, the scripts that generate the experiments are powershell scripts.
+
+### M4M Stuff:
+
+For .NET Core (>= 2.1), .NET 5 (hereon lumped in with .NET Core)
+
+ - M4M.CoreRunner.dll
+ - M4M.Model.dll
+ - M4M.New.dll
+
+For .NET Framework (>= 4.5?)
+
+ - M4M.exe
+ - M4M.Old.dll
+ - M4M.Model.dll
+ - M4M.New.dll
+
+### Dependencies:
+
+Dependencies are listed for reference: you shouldn't need to take any action to obtain then.
+
+Excepting NetState.dll, which should have been supplied with M4M, these packages should be obtained automatically by nuget upon build (requires an internet connection).
+
+ - NetState.dll - provides serialisation (essential)
+ - [MathNet.Numerics.dll](https://numerics.mathdotnet.com/) - provides mathsy stuff (essential)
+ - [OxyPlot.dll](https://github.com/oxyplot/oxyplot/) 2.0 - provides Pdf plotting capabilities (essential): newer versions have replaced the old PDF provision, so don't use those
+ - System.Runtime.CompilerServices.Unsafe.dll - something important (essential)
+ - SixLabors.Core.dll - [SixLabors](https://sixlabors.com/) stuff is only used for bitmap rendering (e.g. the bestiary); there is some licencing weirdness going on, but using an old version (e.g. 1.0.0-beta0007) for non-commercial work should be fine
+ - SixLabors.Fonts.dll
+ - SixLabors.ImageSharp.dll
+ - SixLabors.ImageSharp.Drawing.dll
+ - SixLabors.Shapes.dll
+
+.NET Framework stuff also requires:
+
+ - [OxyPlot.Pdf.dll](https://github.com/oxyplot/oxyplot/) - provides the nice Unicode plotting (essential: there is no fall-back to the inferior Pdf exporter)
+ - [PdfSharp.dll](http://www.pdfsharp.net/) - provides the nice Unicode plotting (essential: there is no fall-back to the inferior Pdf exporter)
+
+You can use the MathNet.Numerics OpenBLAS provider if it is available.
+
+Contact the maintainer if you have any difficulties.
+
+The PDF plotting should be replaced with the shiny new Skia based PDF exporter at some point.
+
+#### .NET Core
+
+.NET Core can be compiled for various platforms (M4M has been employed in practise on Linux (RHEL7) and Windows (7, 10)), and supports 'download and unzip' deployment. If you want to do build from source or otherwise do development, then you need the SDK, otherwise you can get away with just the runtime. You can find the installers for 2.1 (which is what everything M4M targets) here: https://dotnet.microsoft.com/download/dotnet-core/2.1. Iridis unfortunately doesn't have 2.1 (at the time of writing), so you'll have get it manually if you are working there or re/dual-target everything to 3.0 (it has been tested against 3.0, 3.1, and .NET 5.0, so don't be afraid of doing that.)
+
+I can't remember what installing does, but if you are using a compute cluster or an otherwise locked down system, life is no harder than downloading and unpacking an archive and added the path to `dotnet` executable to your PATH or creating an alias. If you intend to do C# development, then it is strongly advisable that you add `dotnet` to your PATH.
+
+#### .NET Framework
+
+The .NET framework ships with Windows, so is probably already installed. If not, you will need to install it. If you can't install things or are using Linux, you'd better look at using .NET Core instead.
+
+#### Powershell
+
+If you are using Windows, you probably have powershell installed already.
+
+If you don't have powershell but do have a recent version of .NET Core, you can install it by running
+
+    dotnet tool install --global PowerShell
+
+Then you can use `pwsh` to run it (works on both linux and windows).
+
+### Building everything
+
+Building is easy with .NET Core; it's a bit more painful with .NET Framework because msbuild and friends usually isn't in PATH.
+
+If you have .NET Core and powershell, run `. m4minit.ps1` from powershell (pwsh) then you need only run this command:
+
+    m4mbuild
+
+If you run it from the Visual Studio Developer Powershell, then it should build all the windows specific utilities also (I'm too lazy to port them to .NET Core, so they need .NET Framework). You can pass `m4minit.ps1` an alternative .NET version to use, e.g. `. m4minit.ps1 net5` (one of `netcoreapp2.1` (the default for legacy reasons), `netcoreapp3.1`, `net5`).
+
+If you don't want all the utility stuff, you can run:
+
+    netstateb
+    m4mb
+
+If you are on linux, you can run `. m4minit.sh` and hopefully it will do the same sort of thing (again, you can specify a .NET version as a parameter). Again, run
+
+    netstateb
+    m4mb
+
+To build the things you need. These scripts will also add `m4m` as an alias. If running `m4m` after running the build scripts doesn't error, then you are good to go.
+
+If you don't want to use the build scripts, or they don't work, or you want to use the non-core utilites, then read on...
+
+For reasons not worth going into, M4M ships with its own serialisation framework (netstate), and you need to build that before anything else (e.g. with visual studio or msbuild or a `dotnet` command as described below). Netstate starts .NET Standard, so it doesn't matter how you build it, and you only need to build it once.
+
+the easiest thing to do is to open the solutions in (an up-to-date) Visual Studio and build everything from within the Batch Build menu, or call `msbuild /p:Configuration=Release` on the runner and it should work everything out. If you don't have MSBuild (e.g. you are on linux) or don't know what msbuild is, then you can just as well use `dotnet build --configuration Release`. If you want the Core-runner, for example, open a terminal in the directory `M4M.CoreRunner` (the one containing `M4M.CoreRunner.csproj`) and run `dotnet build`: it will build the M4M dependencies for you.
+
+Things that may go wrong include:
+ - Some warning about info files or `AutoVersion` or something: check that `AutoVersion.dll` and its runtime config are in the build tools directory. If they are not, it isn't a big problem, but you could always ask me about it.
+ - Some complaint about not being able to reference net standard 2.0: delete all (ALL) the bin and obj directories, and try again.
+ - Some complaint about netstate/state attributes: you need to build netstate before anything else
+
+The utilities are all written for .NET Framework for Windows, and need msbuild to be built, e.g. `msbuild M4MPlotting.sln /p:Configuration=Release`. This should work if you run it from a Visual Studio Developer Prompty (windows search for 'Developer Prompt' will find it if it is available). (If you want them to run on .NET Core under windows, shout at me, and I'll sort it out. If you want M4MPlot to run under .NET Core on linux, shout at me and maybe I'll sort it out; the others I will not be re-writing.)
+
+### Running it
+
+You run the .NET Core runner from the command line with the command `dotnet M4M.CoreRunner.dll`.
+
+    ~~ M4M Core Running ~~
+    OpenBLAS
+    Available providers:
+    // long list
+
+You run the .NET Framework runner from the command line as `m4m.exe`.
+
+    ~~ M4M Framework Running ~~
+    OpenBLAS
+    Available providers:
+    // long list
+
+The 'OpenBLAS' line only appears if open-BLAS is detected (whereafter it will be used).
+
+If you don't want the nice intro text, use the 'quiet' commandline parameter.
+
+#### Command-line aliases
+
+In order To make your life easier, it is suggested that you alias `m4m.exe` or `dotnet M4M.CoreRunner.dll` to just `m4m`. In any help documentation, `m4m` is used in this sense. Aliasing works differently in different shells, for example:
+
+    // cmd
+    doskey m4m = path\to\m4m.exe $*
+
+    // PowerShell
+    function m4m() { dotnet "path\to\M4M.CoreRunner.dll" $args }
+
+    // bash
+    alias m4m='~/path/to/dotnet/dotnet path/to/M4M.CoreRunner.dll'
+
+Recall that forward-slashes work as separates on both modern versions of Windows and Linux. There is no reason this software shouldn't work on OSX with .NET Core, but I've never tried. m4m.exe might also work under mono, but again, I haven't tried.
+
+You can make life easy by using powershell and loading the `m4minit.ps1` script, which provides a handful of useful aliases.
+
+#### VS Code and Windows Terminal
+
+If you happen to be using VS Code with powershell as the terminal, you can configure the terminal by adding the following to your settings.json (e.g. just for the workspace where you want it):
+
+    "terminal.integrated.shellArgs.windows": ["-NoExit", "-command" ,".", "\"./m4minit.ps1\""],
+
+This tells it to run m4minit.ps1 on startup, and then to keep that instance alive. You'll need to update the path to m4minit.ps1 (in my case, I have it enabled only in the M4M_MKI directory, so m4minit.ps1 is in the same directly).
+
+Much the same can be done for the windows terminal. Create a new config block in settings.json, and swap out the startup command:
+
+    "commandline": "powershell -NoExit -command \". C:/Users/Murdo/Documents/raw/Code/Other/M4M_MkI/m4minit.ps1\"",
+
+Change the font to something sensible as well, because the default font in Windows Terminal is not good.
+
+    "fontFace": "Consolas",
+
+Another convenience I use is to add an environment variable for the address of m4minit.ps1, so that I can run it from anywhere in a hurry. This is especially useful on the compute server, when I need to swap between pwsh and bash at short notice.
+
+### Cli Parameters
+
+The M4M command line is controlled by various command-line parameters. There are no positional parameters.
+
+There are 2 types of parameters:
+
+ - Flags, which are just case-insensitive keywords that are left on their own. Examples include `quiet` (less pointless output), `diag` (diagnostic output), `help` (print help text sometimes), and `green` (plots `rcs` files in green mode instead of rainbow mode).
+
+ - The other type, which are key-value pairs. You can set these either with "key=value" (no spaces), or "-key value" (one space). Examples include `plot=rcs_t(best).dat`, `legend=to`, `targetpackage=custom`, `dtminfo=true`, and many others. Keys are all case-insensitive, and most (non-filename) values are case-insensitive. In code, flags are just key-value pairs with a null value.
+
+Generally, if a parameter requires a list of things, then this list is semi-colon delimitated (`;`), because it turns out that commas (`,`) are a bad choice. Unfortunately, many places which accept semi-colon also recognise comma. If you have difficulties inputing numbers containing commas, then you may consider the `forceinvarientculture` (or `fic`) flag to use US-style numerals. One issue with semi-colons is that some shells (Powershell) recognise these as end-of-lines, so you have to wrap lists in quotes, or escape the semi-colons with a back-tick.
+
+You can append to already assigned parameters with "+key value" or "key+=value". If the parameter is unassigned or null, then it will be assigned the given value.
+
+Some parameters are keyed, such that they can be set for individual items, where the name has the format `param:key`. These occur regularly in plotting, where customising individual axes is useful (e.g. `title:x=Generations`). In Code, these are no different from other parameters, and there is no real support for them, but it's a nice convention.
+
+As a simple piece of assistance, you will be informed of any Cli Parameters which are not observed at the end of execution. If a parameter is not observed, it may warrant investigation: for example, it might indicate a typo. This utility is disabled by the `quiet` and `donotlistunobserved` flags.
+
+After spending any time doing electronics, numerical formats like `3k3` become irresistible, so there is basic support for this (and exponential) format on many integer parameters. E.g. all of these are equivalent:
+
+    epochs=1500
+    epochs=1K5
+    epochs=1.5
+    epochs=1.5E3
+
+#### Special Parameters
+
+There are two types of special parameters, which allow you to 'include' parameters from another source. This is useful when you need to share parameters between commands, or just want to logically group them.
+
+ - `params`: inline parameters
+ - `paramfile`: file parameters
+
+##### params
+
+All parameters with key `params` will be expanded immediately into more params: the following two commands are equivalent:
+
+    m4m -exp epoch0savestart.dat plotperiod=1000 saveperiod=1000 saveperiodepochs=1000
+    m4m -exp epoch0savestart.dat params="plotperiod=1000 saveperiod=1000 saveperiodepochs=1000"
+
+This is performed as the parameters are consumed (in code, by the `CliParams.Consume*` methods): it can be used anywhere, and the usual overwriting rules apply. Importantly, you can use as many `params` commands as you want, so you can include many sources.
+
+##### paramfile
+
+After consuming all parameters from some source, _some_ tools will look up the `paramfile` parameter and, if it exists, consume every line in the give file (or semi-colon delimited files; back-tick for escape). This operation is recursive, but will ignore any paramfile that is observed twice. Parameters from a paramfile are ignored if they are set by whatever source reference the paramfile. Because this is really just a normal parameter (unlike `params`) but with important behaviour, you cannot use it more than once in a command.
+
+Any line in a paramfile that starts with `//` is ignored. Lines later in a file will replace parameters set by lines earlier in the same paramfile.
+
+In code, this is provided by the `M4M.Cli.LoadParamFiles` static method.
+
+### Differences between .NET Core and .NET Framework Versions
+
+The only real different is that .NET Core one is used for grunt work, and the .NET Framework is used alongside M4MPlotting.exe, and the Pdf engine that has unicode support (which the .NET Core version does not). They should otherwise provide the same command-line experience.
+
+## Files
+
+Data files all end in `.dat`. Anything which is not `.dat` is not a data file. Most important of all, `config.txt` is not a datafile, so don't bother editing it ever (it provides a summary of what is in an experiment, so that you can check it is running what you expect it to be running: don't edit it). `config.dat` is a data file, but you can't edit it. Look at the `reconfig` provider if you want to know how to 'edit' things (hint: you can't).
+
+Let me repeat: you cannot edit data-files. The serialisation format is not designed to permit such editing, and any attempts to do so are liable to corrupt data files, or otherwise leave you with invalid state. Also, it might just be dishonest, but let's not worry about that. Do not edit data files.
+
+### File Prefixes
+
+In order to make life worse, I mean better, I mean weird, file-names all have a meaningful prefix. Do not change the prefix of your data-files, as the program won't know what to do with them. For example
+
+    // this is an experiment definition, as indicated by the `epoch` prefix
+    epoch1000save.dat
+
+    // this file contains Regulatory Connection Strength trajectories (prefix `rcs`)
+    rcs1000000(best).dat
+
+    // this file contains end-of-epoch Whole Samples (prefix `wholesamplese`)
+    wholesamplese2000000.dat
+
+    // this file contains a single Dense Genome (prefix `genome`)
+    genome5000.dat
+
+### File Postfixes
+
+Some files end in "_t": this means they are 'terminals', generated at the end of an experiment, which - in the case of trajectory files - contain all samples recorded through the whole experiment. Partial results will have an end-of-epoch number instead. Wholesamplese files are special, because they tend not to start from epoch 0 every time.
+
+### File Formats
+
+There are 2 main file formats, both exclusively Big-Endian with UTF-8 string encoding:
+
+ - NetState Graph: this is an automatically serialised version of some actual .NET Classes. Don't even think about editing these. In code, these are loaded with the static `M4M.State.GraphSerialisation.Read<T>(Stream)` method, whre `T` is the expected type of object being read. The exact nature of these files depends on the runtime, but some sickening code in `NetState.dll` performs some automatic mapping which means they should be completely interopable.
+
+    Though in theory these could be loaded from another software package, the effort would be considerable, and if that were necessary it would probably be easier to load them in .NET first, and then use some dodgy JSON serialiser or something to re-export them.
+    
+    The benefits of this format are in rapid prototyping and (reasonably) efficient storage. Take a look at the State directory under M4M.Model if you want to understand how it is used. This is the `M4M.State` namespace, and the layer that interfaces with the netstate library. Unless you are working with structures, the `StateClass` and `SimpleStateProperty` annotations are all you need to to know.
+
+ - Trajectories: this is just a number of long lists of double-precision floating point numbers. Unless you are very unlucky, the file will also tell you the sample-period with which the data was sampled. This is used as an efficient way to pack in lots of data samples that are taken during the course of an experiment. In theory you could edit these... but why would you want to mess with your output data? Examples include `rcs` files, `fitness` files, `ist` files, `pst` files, `ivmcswitches` files, and `ivmcproper` files. In code, these are loaded with the static `M4M.Analysis.LoadTrajectories` method, and give you a `double[][]`, with the structure `trajectories[line][time]`
+
+    The format is very simple, so it should be trivial to load them from another software package if necessary.
+
+Some files are just weird. `genome` files are one such example, as I recall. The number of weird files should not increase over time.
+
+## Key Structures
+
+### Experiment
+
+In code, an experiment is a `PopulationExperiment<TIndividual>`, and is serialised as such to an `epoch` file, such as `epoch0savestarter.dat`. An experiment contains all the information to run an experiment, except for the PRNG seed, and information concerning 'feedback'. Population Experiment information includes:
+
+ - the current epoch and generation counts
+ - the current target index
+ - the current population, a `Population<TIndividual>`
+ - the population experiment configuration, a `PopulationExperimentConfig<TIndividual>`
+ - some information concerning where the experiment was created/might be run, which makes no sense but is there anyway
+
+### Population
+
+A population (`Population<TIndividual>`) just contains a list of individuals of type `TIndividual`, and provides various methods to mess with them.
+
+Populations and Individuals share the strange idea of 'escaped' individuals: for not-very-good reasons, individuals remember whether they have escaped the control of the population so that they know they can't be 'reycled'. This 'recycling' in theory allows object reuse, but in practise is a nightmare you should never enable. Instead, consider writing/requesting a fast-path which uses mutable individuals where performance is a problem.
+
+### Population Experiment Configuration
+
+A `PopulationExperimentConfig<TIndividual>` contains information about an experiment on a population. This information includes:
+
+ - the experiment configuration 'config', an `ExperimentConfiguration` (note this isn't generic)
+ - the population spinner, which manages the cycling of the population each generation
+ - the "PopulationResetOperation", which is run at the start of each target exposure
+ - the "PopulationEndTargetOperation", which processes the population at the end of each target exposure
+
+Some population spinners (e.g. the default spinner and `DeltaRuleSpinner`) recognise the following information in the population experiment configuration:
+
+ - the elite count, the number of elites to keep each generation
+ - hill-climber mode flag, which enables a 'hill-climber' rather than weighted population replacement
+ - the selector preparer, an object which prepares a selector for fitness-dependent selection
+
+### Experiment Configuration
+
+An `ExperimentConfiguration` contains lots of information about an experiment. Crucially, it includes:
+
+ - the number of epochs to run
+ - the number of generations per target per epoch `K`
+ - the development rules `drules`
+ - the reproduction rules `rrules`
+ - the judgement rules `jrules`
+ - the list of targets
+ - the target-cycler
+
+It also includes:
+
+ - the 'InitialStateResetProbability', the probabiliy of reseting stuff at the start of each target exposure (not necessarily just initial states)
+ - the 'InitialStateResetRange', which determines the range in which initial states should be reset when applicable
+
+### Individuals
+
+These include a genome and a phenotype. The genome is generic; the phenotype is not. `DenseIndividual` is the only type of individual you should ever care about, and comprises a `DenseGenome`, the developed `Phenotype`, and some other stuff you probably don't want to (and shouldn't have to) think about.
+
+### Whole Sample
+
+A `WholeSample<TIndividual>` holds a substancial amount of information about a single generation or epoch:
+
+ - the epoch and generation it represents
+ - a list of individual judgements (i.e. individual and judgement pairs)
+ - the current target (this is not a clone, so it will become 'out of date' if the target is ever reset (e.g. noise is added, or it's a Variable IVMC Target))
+
+The `wholesamplese` and `tracee` files are literally a `List<WholeSample<TIndividual>>`, which is only somewhat regrettable. `wholesamplese` files usually represent end-of-epoch samples, while `tracee` files usually represent within-epoch samples. They are usually stuffed into a `TraceInfo` as soon as possible.
+
+## Providers
+
+Here is a run-down on the different provides. Most provides are also used as parameters, e.g. to use the `plot` provider you provide a `plot=something` parameter, where `something` is a data-file or the word 'misc'. The cli just uses whichever provider it happens spot has a parameter associated with it first.
+
+The most essential ones are:
+
+ - `gen`, which generates experiments
+ - `exp` and `run`, which run experiments
+ - `plot`, which plots data-files
+
+### `exp`
+
+Runs a `PopulationExperiment` packaged in an  `epoch` file. It will provide an indication of progress every `0.1%`, indicating the current epoch and an estimate of the remaining runtime. 
+
+ - `topdir=directory` specifies the directory to run in (note: topdir is a highly context-dependent parameter)
+ - `continue`: attempts to continue an 'unfinished' experiment, simply subtracts the observed epoch count from the total epoch count to run
+
+Basic Examples:
+
+    // runs the indicated experiment in the same directory as the experiment file, using the given random seed
+    m4m exp=ImvcExperiment1/epoch0savestarter.dat seed=42
+
+    // runs the indicated experiment in the indicated directory (note: topdir is a highly context-dependent parameter)
+    m4m exp=ImvcExperiment1/epoch0savestarter.dat topdir=ImvcExperiment1runOne
+
+    // continues the indicated experiment (not that random seeds are not serialised, so a new seed is generated/must be supplied when you 'continue'
+    m4m exp=ImvcExperiment1/epoch500save.dat continue
+
+#### Automatic Saving
+
+Because `m4m` is _really slow_, part-way experiment 'save' files are regularly written out. Note that these do not contain random seed information, so 'continuing' an experiment is a bit of a lie, because the seed will be different.
+
+ - `saveperiodepochs=epochs`: changes how often an `epoch` save file is produced (period in epochs)
+ - `saveperiodseconds=seconds`: changes how often an `epoch` save file is produced in seconds (e.g. even if it ask it to wait for 1billion epochs, it will still spit out a file wherever it has got to if the number of seconds since the last file has exceeded this parameter)
+
+#### Default Feedback
+
+Vitally important to running experiments is to get something out from them. By default, the default 'feedback' provider will produce an endless supply data files. Indeed, the amount of data-files is a linear function of the duration... and the data-files are mostly cumulative... so the amount of space consumed is quadratic in the duration. Basic controls of the amount of data files include:
+
+ - `plotPeriod=epochs`: controls how often preliminary/preview data files are produced (mostly trajectory files such as `rcs`, `fitness`, etc.)
+ - `savePeriod=epochs`: controls how often a `genome` file is produced (period in epochs)
+ - `sampleMax=count`: controls the maximum number of samples that will appear in the default trajectory files, default is 2000
+ - `tracePeriod=epochs`: controls the interval between whole-epoch traces
+ - `traceDuration=epochs`: controls how long each trace is in epochs
+ - `wholeSamplePeriod=epochs`: controls how often a 'WholeSample' is taken
+ - `wholeSampleWritePeriod=epochs`: controls how often wholesamples are written out (these file can be big, so do not accumulate)
+
+Other than the regular preliminary data-files (which has a numerical postfix indicating the epoch whence they come), there are terminal data-files produced when the experiment ends with postfix `_t`. Usually you will want to delete everything except the `_t` files, since they contain all of the same information, and disk-space is at a premium when you are running a hundred of these at a time.
+
+#### Feedback Configurators
+
+Other than the default feedback - from which you can't escape without messing with the code - there is support for independent 'feedback configurators'.
+
+You can specify configurators as a semi-colon (`;`) delimited list, for example:
+
+    m4m exp=epoch0savestarter.dat configurators="ivmcdensefeedback;satfitnessfeedback"
+
+Remember to put quotes around your semi-colon list if using PowerShell.
+
+##### IvmcDenseFeedback
+
+The `ivmcdensefeedback` configurator produces trajectory files counting ivmc-switch events, ivmc-solves, and (optionally) the actual ivmc-benefit environmental factors during each epoch.
+
+The `ivmcdensefeedback` configurator has the following parameters:
+
+ - `ivmcSwitchResolution=epochs`: controls the interval between IvmcSwitch samples (note: every switch in every is counted, but with `
+ - `ivmcSwitchResolution>1`, the counts accumulate resulting in a lower-resolution trajectory)
+ - `ivmcSwitchPlotperiod=epochs`: controls how often `ivmcswitches` files are written out
+
+If the `ivmcdensefeedback` configurator is successfully attached, you should see the message `IvmcProperFeedback attached`.
+
+##### SatFitnessFeedback
+
+The `satfitnessfeedback` configurator produces trajectory files that contain the fitness of a saturated phenotype. This is useful, for example, if you want to know whether the system is able to find high-fitness patterns, without all the noise of directional selection.
+
+The `satfitnessfeedback` configurator has the following parameters:
+
+ - `SatFitnessSampleRate`: the sample period (defualt is 1, which produces very large files)
+ - `SatFitnessPlotPeriod`: the plot period, defualts to whatever `plotperiod` is
+ - `SatThreshold`: the threshold that determines whether values are high or low (default 0)
+ - `SatMin`: the value to assign to low values (default -1)
+ - `SatMax`: the value to assign to high values (default +1)
+
+If the `ivmcdensefeedback` configurator is successfully attached, you should see the message `SatFitnessFeedback attached`.
+
+Internally, this uses a `ProjectedFitnessFeedback` which it passes a `SaturationTarget` that wraps the existing targets (except those that are already instances of `SaturationTarget`, in which case it does not wrap them; don't ask me why).
+
+### `run`
+
+Runs a directory full of directories containing experiments. Most stuff that works with `exp` also works with `run`, though possibility in different ways (e.g. `seed` has a different job, and `topdir` becomes meaningless). Important parameters are:
+
+ - `run=directory`: specifies the directory to run
+ - `repeats=count`: the number of repeats to run of each experiment (each is run in its own directory with an `r{repeat}` postfix), default is 1
+ - `seed=seed`: specifies the seed of the first experiment: it is incremented for each successive experiment (in file-name order), randomised by default
+ - `expfilename=epochfilename`: the exact `epoch` filename to look for in sub-sub-directories.
+ - `postfix=postfix`: the 'runs' postfix
+ 
+ #### Example
+
+A 'Real world' derived example, demonstrating many `run` and `exp` parameters.
+
+    m4m run=$1 postfix=$2 traceperiod=0 wholesampleperiod=100 wholesamplewriteperiod=2000000 plotperiod=500000 saveperiod=500000 saveperiodepochs=100000 saveperiodseconds=7200 seed=$3 expfilename=$4 configurators=ivmcdensefeedback ivmcswitchplotperiod=1000000 ivmcswitchresolution=1 ivmcrecordpropers=false > logs/$1runs$2log.txt
+
+It has the following features:
+ - takes the run-directory postfix, seed, and experiement file name as a command-line parameter
+ - sets a low-ish wholesampleperiod, high wholesamplewrite period (so you get one big data-file at the end instead of having to concat lots of small data files, but you have to wait for it)
+ - disables traces (traceperiod=0)
+ - sets a large but not too-large plotperiod and saveperiod, so that part-way results can be interrogated
+ - sets a smallish saveperiodepochs, so that there are many checkpoints (which are small and can always be put to good use)
+ - sets the saveperiodseconds to 7200seconds = 2hours (the whole run took ~30hours)
+ - engages the ivmcdensefeedback configurator, with the lowest possible ivmcswitchresolution (corresponding to no down-sample), but disabling ivmcrecordpropers
+ - pipes the output to a log file
+
+### `gen`
+
+Generates experiment files, based on a number of 'preparers', each of which has its own set of parameters, and may produce one or many experiment files. The prepare of choice is taken as the argument of the `gen` parameter. If no parameter is provided, a list of providers is printed. At some point I delete all the providers other than `composedense`, but in theory you could add more for e.g. non-dense stuff.
+
+Many providers have the following common parameters:
+
+ - `topdir=directory`: the 'top' directory; this default to some unhelpful place in `C:` under Windows or `~` under Linux, so it's usually worth qualifying
+ - `targetpackage=packagenameortype`: specifies the target package, or type of target package. The specifics depends on each preparer.
+
+ Most providers have a somewhat helpful `help` text outlining the parameters, but that's about it. Always check the `config.txt` file to verify everything is as you expect.
+
+ #### Generating Experiments
+
+ The `composedense` preparer produces a single 'Dense' experiment; it is intended to be a half-way extensible means, the backing for which should be the basis of any future providers. One day I might delete all the old provides, because they are just generally a nightmare. Unlike other providers - which are a monolithic nightmare - this provider is a semi-decoupled nightmare spread over multiple files (see the `ExperimentComposition` namespace). The functionality is provided by multiple 'composers', each of which deals with some part of the hierarchy of misery that is a `PopulationExperiment<DenseIndividual>` (they are generic where applicable). The error handling is not so great at the moment, because I made some bad decisions concerning where to put error handling logic.
+
+Sub-components and their parameters:
+
+##### `DefaultExperimentComposer`
+
+Composes an experiment, defering most of the work to other composers passed as a parameter.
+
+ - `seed=int`: the seed to use when generating stuff
+ - `name=string`: the name of the experiment; the inner-most directory wherein it will appear
+ - `appendTimestamp`: (flag) whether to append a timestamp to the experiment
+
+##### BasicTargetPackageComposer
+
+Redirects the work of building the target package (`ITargetPackage`) to one of many `IGenericPrefixedComposer<ITargetPackage>`.
+
+ - `targetpackage=prefixsuffix`: determines the composer to use (by prefix) passing it the suffix
+
+By default, the following prefxied target package composers are available:
+
+ - `file:`: (recursive) produces a single target package from multiple targets as indicated by each line of a file (each line must contain a `targetpackage=` clause, or you might end up with an infinite loop)
+ - `exp:`: uses targets from an existing experiment file
+ - `ivmc:`: prepares an Ivmc TargetPackage; see commentary in section _Ivmc Target Packages_
+ - `mc:`: MC-problem target packages, based on a correlation matrix
+
+##### BasicExperimentConfigurationComposer
+
+Puts together an `ExperimentConfiguration`, defering the generation of drules, rrules, and jrules to prefixed composers.
+
+ - `epochs`: the number of epochs to run
+ - `K`: the number of generations per epoch
+ - `gResetProb=[0,1]`: the probability of reseting the initial state vector at the start of each epoch
+ - `drules=prefixsuffix`: determines the composer to use for `DevelopmentRules`
+ - `rrules=prefixsuffix`: determines the composer to use for `ReproductionRules`
+ - `jrules=prefixsuffix`: determines the composer to use for `JudgementRules`
+
+##### DefaultDevelopmentRulesComposer
+
+The default drules composer (usually available to `BasicExperimentConfigurationComposer` as prefix "default").
+
+ - `T=int`: number of developmental time-steps to use, default 10
+ - `tau=[0.0, 1.0]`: the decay rate, default 0.2
+ - `squash=name`: the squash composer to use, defaults to tahnhalf
+
+##### DefaultReproductionRulesComposer
+
+The default rrules composer (usually available to `BasicExperimentConfigurationComposer` as prefix "default").
+
+ - `MG=magnitude`: G mutation magnitude, default is 2 (along with BGtype=binary and [-1, +1] clamping gives 'binary' G mutation)
+ - `MGtype=noisetype`: type of G mutation, default is binary
+ - `MB=magnitude`: B mutation magnitude, default is 2E-5
+ - `MBtype=noisetype`: type of B mutation, default is uniform
+ - `bprob=[0.0, 1.0]`: the probability of performing a mutation of B, default is 1.0
+ - `BEx=bool`: controls whether mutations on B should be performed at the exclusion of G, default is false (no)
+ - `CG=int`: the number of initial-state traits to update, default is 1
+
+##### DefaultJudgementRulesComposer
+
+The default jrules composer (usually available to `BasicExperimentConfigurationComposer` as prefix "default").
+
+ - `lambda`: the regularisation coeficient
+ - `kappa`: the noise-factor to apply to targets
+ - `regfunc=prefixsuffic`: the cost function composer and suffix (see `BasicRegularisationFunctionComposer`)
+
+##### BasicRegularisationFunctionComposer
+
+Composes typical regularisation function, and provides for custom prefixed composers. Supported regularisation functions include:
+
+ - `const`: a constant-equivalent (i.e. no regularisation)
+ - `l1`: an l1-equivalent (linear), as used in most experiments
+ - `rowl1`: an l1-equivalent (linear), which should be identical to `l1`, but is faster
+ - `coll1`: an l1-equivalent (inear), which is not identical to `l1`, but is faster still
+ - `l2`: an l2-equivalent (quadratic)
+ - `mmsoAB{a;b}`: michaelis-menton style regularisor given by `cost = b|x|/(|x|+a)`
+ - `mmsoABC{a;b;c}`: michaelis-menton style regularisor given by `cost = b|x|/(|x|+a) + c|x|`
+ - `mmsoAQ{a;q}`: michaelis-menton style regularisor given by `cost = b|x|/(|x|+a) + c|x|` with `a=a, b=a*(1-q), c=q`
+ - `l1And2ab{a;b}`: sum of linear and quadratic, `cost = a|x| + bx^2`
+
+##### BasicDensePopulationExperimentConfigurationComposer
+
+Puts together a `PopulationExperimentConfig<DenseIndividual>`, defering the generation of a custom population spinner to a prefix composer. Supported parameters include:
+
+ - `resetoperation`: the population reset operation to used; one of: uniform, binary; default depends on the G mutation type
+ - `spinner=prefixsuffix`: determines the prefixed composer and suffix of a custom population spinner (which may or may not observe other `PopulationExperimentConfig<T>` parameters), e.g. `dense:hebbian`, `dense:hopfield`, or `dense:deltarule`
+ - `hc=bool`: whether to use hill-climber mode where applicable
+ - `elitecount=int`: the number of elites to keep each generation where applicable
+ - `selectorpreparer`: the selector prepare to use, one of: `ranked`, `pareto`, `proportional` (default is `ranked`)
+
+Presently supported but ill-advised parameters include:
+
+ - `gResetBinary`: controls whether g resets should be binary (values sampled from {-1, 1}) or uniform (values sampled uniformally from [-1, +1]); prefer `resetoperation` instead which overrides this
+
+##### DefaultPopulationSpinnerComposer
+
+Puts together a population spinner (usually available to `BasicDensePopulationExperimentConfigurationComposer` as prefix "default").
+
+ - `default`: uses the `DefaultPopulationSpinner<TIndividual>`, which simply calls into `Population.SpinPopulation`. Provides a simple population-cycle based on the population-configuration hill-climber mode (on/off), elite-count, and selector-preparer. Implements a fast-path of hill-climbers; otherwise, pretty inefficient. No parallelism is employed for a variety of reasons.
+ - `paired`: uses the `PairedPopulationSpinner`, which simply calls into `Population.SpinPopulationPaired`. Ignores hill-climber mode, elite-count, and selector-preparer. Selects 2 individuals at random (no weighting), and replaces the less-fit individual with a mutant of the more-fit individual.
+ - `trios`: uses the `RecombinantTriosPopulationSpinner`, which simply calls into `Population.SpinPopulationRecombinantTrios`. Ignores hill-climber mode, elite-count, and selector-preparer. Selects 3 individuals at random (no weighting), and replaces the less-fit individual with a combination of the more-fit individuals. _I'm pretty sure this has never been run, let alone tested._
+
+##### DensePopulationSpinnerComposer
+
+Puts together a population spinner for a dense individuals (usually available to `BasicDensePopulationExperimentConfigurationComposer` as prefix "dense").
+
+ - `delta`: uses the `DeltaRuleSpinner`, which calls into `Population.SpinPopulation` before performing a delta-rule update step once the usual stuff has ended. Generally you want to set `bprob=0` so that the dtm is only modified by the delta-rule update step. An additional parameter is available:
+   - `deltarulefitnesspowerfactor=double`: a factor to which the fitness of the individual generated by the default spinner will be raised to create a factor by which the delta-nudges are multiplied after evalution (a crude form of fitness informed update)
+ - `hebbian`: wraps an 'inner spinner', running a hebbian update step at the end, updating `B` a little according to the auto-correlations in `G`. It has many parameters, including:
+   - `hebbiantype=hebbiantype`: controls the types of updates performed by the hebbian update. One of `Standard`, `IncreaseOnly`, and `DecreaseOnly` (default is `Standard`)
+   - `innerSpinner`: another `dense` type spinner (e.g. combine with `lotkavolterra` to get close to _What can ecosystems learn? Expanding evolutionary ecology with learning theory_ by Power et al.)
+ - `hopfield`: modifies `G` is a hopfield network... so it's a bit like splating the developmental process over many 'generations'. It has many parameters, including
+ - `lotkavolterra`: treat `G` as population densities with interacts according to `B` and the Lotka-Volterra competition dynamics. Additional parameter:
+   - `growthrate` the growth rate
+
+##### BasicDensePopulationComposer
+
+Puts together a `Population<DenseIndividual>`
+
+ - `g0string`: extreme value or semi-colon delimited double list of initial state expressions for the starting genome; default is all-zeros
+ - `b0string`: a matrix string; currently only supports the `col:c0;..` format, whereby the value of whole columns is provided in a semi-colon delimited double list; default is all-zeros
+ - `transmatmutator=prefixsuffix`: determines the developmental transformation matrix mutator, one of `default`, `singlecell`, `columns{moduleCount}`, `singlecolumn{moduleCount}`
+ - `popSize=int`: the number of clones in the population, default `1`
+
+#### Generating Ivmc Experiments (Legacy)
+
+ The `properivmc` preparer produces a single 'Ivmc' experiment. It has the following important parameters (some of which are congruent with other generators), many of which do not have defaults:
+
+ - `epochs`: the number of epochs to run
+ - `K`: the number of generations per epoch
+ - `lambda`: the regularisation coeficient
+ - `targetpackage`: the type of target package to use, either `4` (the default, a 4x4 ChangingProeprIvmc) or `custom`
+ - `regfunc`: the cost function, such as `L1`, `L2`, `MmsoLinAQ1,1` (alterntaively, specify `q` and `a` as parameters of an MMSOLin cost function without qualify `regfunc`)
+ - `MG=magnitude`: G mutation magnitude, default is 2 (along with BGtype=binary and [-1, +1] clamping gives 'binary' G mutation)
+ - `MGtype=noisetype`: type of G mutation, default is binary
+ - `MB=magnitude`: B mutation magnitude, default is 2E-5
+ - `MBtype=noisetype`: type of B mutation, default is uniform
+ - `bprob=[0.0, 1.0]`: the probability of performing a mutation of B, default is 1.0
+ - `BEx=bool`: controls whether mutations on B should be performed at the exclusion of G, default is false (no)
+ - `gResetProb=[0,1]`: the probability of reseting the initial state vector at the start of each epoch
+ - `gResetBinary`: controls whether g resets should be binary (values sampled from {-1, 1}) or uniform (values sampled uniformally from [-1, +1])
+
+##### Ivmc Target Packages
+
+The target package usually contains a single `IvmcProperChanging` Vector Target, which has the following parameters:
+
+ - `CH`: the 'high' benefit coefficient, default 1.0
+ - `CL`: the 'low' benefit coefficient, default 0.7
+ - `C0`: the 'zero' benefit coefficient, default 0.0 (recomment -1.0 for Split)
+ - `Z`: the multi-peak module condition probability
+ - `judgemode`: the judgement mode (aka. eta), either 0 (quadratic `Proper`, or piecewise-linear `Split`, or `Stacked` with a `modulebenefitfunction` (e.g. `split`)
+
+Custom target packages have the following parameters, with the equivalent of the `4` values indicated in brackets
+
+ - `targetstring`: the target string as a sequence of + and - symbols (`4`: `++++++++++++++++`)
+ - `modulesstring`: a string describing the modules in the environment, one of two formats (`4`: `4*4` or `aaaabbbbccccdddd`)
+ - `variationmodulesstring`: a string describing the modules of variation (in terms of modules; `4`: `null` or `4*1` or `abcd`)
+ - `variationModulePlusOverridesString (ivmcvmpos)`: a string describing 'positive' benefit coefficients
+ - `variationModuleMinusOverridesString (ivmcvmmos)`: a string describing 'negative' benefit coefficients
+
+You can produce multiple targets by using pipe (`|`) delimited variation override strings. If you do so, then you need to folly qualify both plus and minus overrides for every target. The format of these can be either a mix of `h`, `l`, `0`, and `_` (corresponding to CH, CL, C0, and unoverrided), or a semi-colon (`;`) delimited list of doubles and `_` (corresponds to `NaN`).
+
+### `plot`
+
+There isn't much point in having data if you can't visualise it. Because all file-names are prefixed with the type of file they are, plotting is mostly a case of `plot=filename`: the `CliPlot` provider maintains a list of `ICliPlotter`s which associate with a particular prefix, and directs input directly to them (though common post-plot processing is performed by `CliPlot` itself, e.g. legend placement).
+
+Another vital parameter is `out=filename`, which specifies the output filename (otherwise the input filename is used). The output file name has `.pdf` appended, so do not include it yourself.
+
+See the section on utility tools to find out how you can use this same functionality without touching a command line.
+ 
+The handling of some files is common, so the next section is broken down by type of file rather, with additional commentary per-prefix where necessary.
+
+#### Trajectory-type files
+
+In code, these are all handled by a `CliTrajectoryPlotter` configured with different colours and other boring chart information (default title, axes labels, etc.). Colours are not generally user-configurable, because that would be a nightmare.
+
+File-prefixes which employ a `CliTrajectoryPlotter` are:
+
+ - `rcs`, Regulatory Connection Strength trajectories (rainbow colours or `green`)
+ - `ist`, Initial State Trajectories (purple)
+ - `pst`, Phenotypic State Trajectories (blue)
+ - `fitness`, End-of-epoch fitness trajectories (f/b/c terms) (red/orange/yellow)
+ - `ivmcswitches`, Ivmc Modules Switchcounts (pink). The last trajectory is the sum of the others
+ - `ivmcproper`, Ivmc Proper-Module Configurations (brown)
+
+Common parameters include:
+
+ - `start=epoch`: the epoch from which to start processing & plotting
+ - `end=epoch`: the epoch from which to start processing & plotting
+ - `resolution=interval`: the post-processed plotting resolution (`resolution=1` is the default which plots everything)
+ - `maRadius=windowradius` computes a moving average over each trajectory
+ - `meanDownsample=width`: performs a downsampling of each trajectory, replacing many data-points with a single data-point that is the mean (performed after a moving average)
+ - `keep=intlist`: an intlist delimited list of indicies into the trajectory file to keep, discarding all others (by default, all are kept)
+ - `plottype=type`: chooses between `line` and `scatter` as the plot type
+ - `ymin`: the min value to show on the y-axis
+ - `ymax`: the max value to show on the y-axis
+
+You can also specify a `samplePeriod` if the data-files you are using happen to lack this information; there is no other good reason for doing this.
+
+##### `rcs`
+
+`rcs` files have some special features. Rather than plotting trajectories, you can plot the genome Developmental Transformation Matrix (a.k.a. `DTM` a.k.a. `transMat`) for a particular epoch by adding the `epoch=epoch` parameter.
+
+Also supported are 2 `rcs` plot annotators:
+
+ - `dtmInfo=bool`, which adds fall-lines onto the plot indicated the types of modules detected during periods of stability
+ - `huskyInfo=bool`, which plots the huskyness, and takes a `completeThreshold=[0,1]` parameter which it uses to provide a fall-line indicated when complete huskyness has been achieved (all modules have huskyness > completeThreshold; the exact epoch will also be printed to the console). This method will guess at the environmental module configuration, but it can otherwise be specified via `modulesize` or `modulesstring` parameters.
+
+For example:
+
+    // plots the dtm at epoch 1000
+    m4m plot=rcs_t(best).dat epoch=1000 forceaspect out=g1000
+
+    // plots lots of information on an rcs plot
+    m4m plot=rsc_t(best).dat dtminfo=true huskyinfo=true completethresdhold=0.9 modulesize=4 out=rscinfo
+
+#### `epoch`
+
+Population Experiments (`epoch` files) have 3 plot modes:
+
+ - `genome`: plots as a genome (see below)
+ - `ma`: performs a Mutant Analysis, plotting a histogram of the frequency of different changes in fitness.
+ - `target=targetname`: plots a target vector, qualified by name. `moduleCount` can be qualified to change the number of rows in the resulting plot.
+
+The following parameters apply to the Mutant Analysis `ma` mode:
+
+ - `mutantcount=count`: the sample size
+ - `target=targetname`: the target to judge the mutants by
+ - `seed=seed`: the seed
+ - `bincount=count`: the number of bins on the histogram
+ - `resetTarget=bool`: whether to reset the target (calling `NextExposure`) before performing the Mutant Analysis
+ - `min`: the minimum fitness change to bin
+ - `max`: the maximum fitness change to bin
+
+#### Genome
+
+Genomes can be plotted from trace-info files (if a `generation` is specified), `epoch` (if `genome` is passed), and `genome` files. By default, the dtm will be plotted (which can also be achieved with an `rcs` by specifying an `epoch`), but there are four genome render modes in total:
+
+ - `dtm`: plots the Developmental Transformation matrix
+ - `is`: plots the Initial State vector
+ - `ps`: plots the Phenotypic State vector (requires an experiment config)
+ - `dev`: plots developmental trajectories (requires an experiment config), can select which traits are drawn with `keep=intlist`
+
+`epoch` files contain an experiment configuration already, which is used by default if needed, but an alternative can be specified with the `expfile` or `configfile` parameter as it must be to use the `ds` or `dev` modes with files which do not provide an experiment configuration already.
+
+#### Trace Info
+
+_Not to be confused with traces_
+
+Such files includes `wholesamplese` and `tracee` prefix files, and are basically just a list of whole-samples. Such files may either operate on an `epoch`s timescale (e.g. `wholesamplese`) or a `generations` timescale (e.g. `tracee`).
+
+Because these files contain so much information, they allow you to easily plot multiple things on the same plot, each controlled by a `PlotOptions` parameter, which indicates the vertical axis (left/right) and line stype (solid/dashed).
+
+ - `fitness=plotoptions`: Fitness (red), default is right-axis (enabled)
+ - `regcoefs=plotoptions`: Regulatory Connection Strenghts (dtm matrix, green), default is off
+ - `gtraits=plotoptions`: Initial Trait expressions (purple), default is off
+ - `ptraits=plotoptions`: Phenotypic Trait expressions (blue), default is off
+ - `huskyness=plotoptions`: Huskyness (grey), default is off
+ - `devtimedominance`: Developmental-time Dominance, default is off
+
+Other parameters include:
+
+ - `start=epoch/generation`: the epoch/generation from which to start processing & plotting
+ - `end=epoch/generation`: the epoch/generation from which to start processing & plotting
+ - `retime=epoch/generation`: shifts the temporal dimensions so that the data starts at the given epoch/generation
+ - `resolution=width`: which performs mean-downsampling (what is normally `meandownsample`)
+ - `downsample=width`: which performs skip-downsample (what is normally `resolution`)
+ - `targets=list`: the seim-colon (';') delimitated list of targets to show on keep (by default it keeps only the 0th target)
+ - `targetlines=something`: adds fall-lines indicating target changes, one of `line`, `labels`, or null. Note that this does not show changes in exposure, only changes in the actual target object, so it's pretty useless with `IvmcProperChanging`, sadly.
+ - `diff`: enables plotting of the change in parameters from the start of the processed data rather than the true values
+ - `fitnessmin`: the minimum on the fitness axis (if any)
+ - `fitnessmax`: the maximum on the fitness axis (if any)
+ - `ymin`: the minimum on non-fitness y-axes (if any)
+ - `ymax`: the maximum on non-fitness y-axes (if any)
+
+If `huskyness` is enables, the 'true' modules will be guessed unless a `modulesize` or `modulesstring` are provided.
+
+If `devtimedominance` is enables, an experiment config must be provided through one of the `expfile` or `configfile` parameters, which may be either a `config` file containg suitable developmental rues, or an `epoch` file containing a suitable experimental configation. A `seed` can be provided as a parameter.
+
+You can specify a `generation` to plot the genome from that generation instead of the trajectories (see section `Genome` for further information). If an experiment config is required, it should be supplied as described above, via the `configfile` or `expfile` parameter.
+
+##### Tracee Plotting Providers
+
+As an alternative to the aforemented `PlotOption` based series, you can use various whole-sample providers. This is a more flexible system, which gives you greater control (useful for multi-plots) and is less of a total nightmare in Code: if you want to add more ploting options for wholesamples, then add more providers. This code doesn't support anything but an 'average' metric and downsampling resolution, though it wouldn't be hard to extend it to do so.
+
+The basic usage is to specify the `providers` parameter with a list of provider/name pairs, e.g.
+
+    providers=fitness:f;gtraits:g;gsum:gsum
+
+The providers can then be customised by name. In code, most go through the `Tracee1DProvider<T>.PlotStuffs` (even the 2D and 0D ones) method, and so support the following keyed parmeters:
+
+ - `plottype:name`: the plot type, either `line` or `scatter`, or `rectangles` for 1D-things
+ - `xaxiskey:name`: the x-axis key
+ - `yaxiskey:name`: the y-axis key
+ - `colour:name`: the base colour
+ - `markercolour:name`: the base marker colour
+ - `strokethickness:name`: the stroke thickness and marker size
+ - `markerType:name`: the type of marker, any of `cross`, `square`, `triangle`, `diamong`, `circle`, `none` (default)
+ - `resolution:name`: the stride between samples (default is 1)
+
+Some of these also work without a key, in which case they will apply to all providers that support this usage.
+
+The available providers are:
+
+ - `fitness`: b - lambda*c
+ - `benefit`: b
+ - `cost`: c
+ - `ptraits`: phenotypic trait expression
+ - `gtraits`: genotypic trait expression
+ - `regcoef`: regulation coefficients
+ - `gsum`: sum of genotypic trait expressions
+ - `huskyness`: degree of hierarhcy
+ - `sudoku`: sudoku score
+ - `moduleindependence`
+ - `deltab` (slow, should probably be renamed dbdb)
+
+##### Tracee Epoch Boundaries
+
+Note that mutli-epoch `tracee` files may have duplicated generations on 'epoch boundaries', where one sample is provided for each epoch, but they report a single generation. This means that they include information about reset and fitness changes before the system is able to respond (which other trajectory formats do not contain). This is not applicable to `wholesample` files, as they do not contian sub-epoch samples.
+
+These multi-sampled boundaries can create issues with plotting, for example when down-sampling, where the extra samples will mess up the sample period. Consider setting the `dropMode` to remove boundary samples; it may take any of 3 values:
+ 
+ - `none`: don't drop any samples (default)
+ - `dropFirst`: drop the first sample of the _next_ epoch on any boundaries
+ - `dropLast`: drop the last sample of the _previous_ epoch on any boundaries
+
+These boundaries can also produce misleading line-graphs. Consider setting `injectBreaks=true` to inject a break in any lines on epoch boundaries. You can further set the `brokenLineStyle` to change the style of the lines used for breaks, which detaults to `dot`. Note that broken lines may add a significant rendering: they can be commited completely by setting `brokenLineStyle=none`.
+
+#### `traces`
+
+_Not to be confused with Trace Info/tracee_
+
+These files (prefix `traces`) are metrics (fitness) collated from many tracees, and the `traces` parameter takes a semi-colon (`;`) delimitated list of them. There are two render modes:
+
+ - `areas`: plots areas representing percentiles for each traces
+ - not `areas`: plots individual traces for each traces
+
+Other parameters:
+
+ - `names`: a semi-colon (`;`) delimited list of names to give to each of the traces (one per file)
+ - `colors`: a semi-colon (`;`) delimited list of colors to give to each of the traces (one per file)
+ - `mean`: measures `mean` metrics rather than 'best' metrics within a population
+ - `start=generation`: sets the generation from which to start processing & plotting
+ - `end=generation`: sets the generation from which to start processing & plotting
+ - `retimeStart=generation`: offsets the generations (i.e. x-axis) so that they start at the given generation
+ - `resolution=interval`: the post-processed plotting resolution (`resolution=1` is the default which plots everything)
+ - `ymin`: the min value to show on the y-axis
+ - `ymax`: the max value to show on the y-axis
+
+#### `misc`
+
+If the word `misc` is supplied instead of a file-name as the `plot` parameter, then a table of miscellanous plot providers is consulted according to the `misc=provder`. These includes:
+
+ - `ivmcproperthing`: plots the 'proper' cost function
+ - `ivmcsplitthing`: the the 'split' cost function
+ - `transition`: plots transition plots of dubious utility
+ - `McPhenotypeTransition`: plots phenotype transitions (jelly molds) for mc problems (need to supply lots of parameters) or a given expfile (in which case it has sensible defaults)
+    example: `m4m -plot misc misc=McPhenotypeTransition n=32 k=1 a=0 b=32 -expfile epoch0savestart.dat terpmode=one`
+    - `expfile`, is specified, is the expfile to use for fitness computation and all that
+    - `n` is the number of traits
+    - `k` is the module size
+    - `a` is the initial number of 'set' modules (left of the plot)
+    - `b` is the terminal number of set modules (right of the plot)
+    - `terpMode` is the interpolation mode, one of `one` or `all`
+    - `x`, if specified, has it plot the phenotype with the given progress between a and b
+    - `target`, if specified, plots a target from the expfile if it is an `Epistatics.CorrelationMatrixTarget` (this is a tad out of place...)
+
+// TODO: need more info about things.
+
+#### Plotting Post-Processing
+
+There are a number 'post-postprocessing' parameters which control how the figure looks, having nothing to with the data itself. Most of these are implemented in `PreProcessPlot`. Those that only apply to exports are implemented in `PostProcessPlot`.
+
+ - `title=text`: sets the plot-title 
+ - `legenttitle=text`: sets the legent-title
+ - `legend=pos`: positions the legend (inside/outside, top/bottom, left/right, and none)
+ - `size=size`: sets the plot size relative to A4 landscape (note: this is 'paper' size, not the size of the axes)
+ - `width=width`: overrides the size, setting the plot width in 1/72" (note: this is 'paper' size, not the width of the axes)
+ - `height=height`: overrides the size, setting the plot height in 1/72" (note: this is 'paper' size, not the height of the axes)
+ - `border=thickness` sets the border thickness
+ - `padding=margins`: sets the plot margins manually, either `left;top;right;bottom` or `all`
+ - `intervalsize`: scales the interval size for all axes. A larger interval size means fewer ticks on each axis
+ - `intervalsizeh`: scales the interval size for horizontal axes
+ - `intervalsizez`: scales the interval size for vertical axes
+ - `forceaspect`: messes with the width and height to force the axes to be square
+ - `allgrey`: changes all line-plots to be a variety of greys, regardless of their original colours (note british spelling)
+ - `repeatlines=interval`: adds a vertical line at regular intervals
+ - `defaultcolors=colorlist`: takes a semi-colon seperated list of colors to use as the default series colors
+
+Some parameters operate on individual axes. You can't always know what the axes are called, but the names of every axis will be printed if `quiet` is not set. In most cases, omiting the `:key` part will mean it applies to all axes.
+
+ - `tier:axis=int`: sets the position tier of the axis 
+ - `label:axis=text`: sets the axis-label of the axis (e.g. `label:x=Epochs`)
+ - `format:axis=format`: sets the format mode of the axis (e.g. `0000.000` or `0E+4` or `auto`)
+ - `visible:axis=bool`: sets the visibility of the axis
+ - `minpad:axis=padding`: sets the minimum padding of the axis
+ - `maxpad:axis=padding`: sets the maximum padding of the axis
+ - `min:axis=value`: sets the minimum value of the axis (overrides axis padding)
+ - `max:axis=value`: sets the maximum value of the axis (overrides axis padding)
+ - `pos:axis=position`: sets the axis position (left/right/top/bottom)
+ - `zerocrossing:axis=bool`: sets whether the axis should be positioned at the zero-crossing
+ - `axislinestyle:axis=linestyle`: sets the line style of the axis
+ - `axislinecolour:axis=linestyle`: sets the line color of the axis
+ - `axislinethickness:axis=linestyle`: sets the line thickness of the axis
+ - `startposition:axis=value`: sets the start position of the axis
+ - `endposition:axis=value`: sets the end position of the axis
+ - `axislinestyle:axis=linestyle`: sets the line style of the axis
+ - `majorticksize:axis`
+ - `majorgridlinestyle:axis`
+ - `majorgridlinecolour:axis`
+ - `majorgridlinethickness:axis`
+ - `minorticksize:axis`
+ - `minorgridlinestyle:axis`
+ - `minorgridlinecolour:axis`
+ - `minorgridlinethickness:axis`
+
+Some parameters operate on individual series. In most cases, omiting the `:key` part will mean it applies to all series.
+
+ - `reduceGeometry:series=bool`: enables or disables geometry reduction, which may reduce the accuracy of line and area series in favour of smaller output (default false)
+
+### `spit`
+
+Writes out a `config.txt` for the given population experiment.
+
+ - `split=filename`: the filename for the population experiment
+ - `out=filename`: the output filename, defaults to `config.txt`
+
+### `tracee`
+
+Runs a trace, producing a `tracee` file. Parameters include:
+
+ - `postfix`: the postfix of the `tracee` file
+ - `topdir`: the directory wherein to run
+ - `seed=int`: the random seed to use
+ - `targets=intlist`: an int-list of target indexes to loop through; uses target0 by default
+ - `cycles=int`: the number of times to cycle through the targets per epoch
+ - `sampleperiod=generations`: the sampling period in generations, default is 1 (take all samples)
+ - `exposureEpoch=int`: the starting epoch
+ - `epochs=int`: the number of epochs to run
+
+Basic Examples:
+
+    // runs a tracee, looping through targets 0 and 1 ten times, producing the file tracees/tracee2M0.dat
+    m4m tracee=epoch2000000saveterminal targets="0;1" cycles=10 seed=1 topdir=tracees postfix=2M0
+
+### `traces`
+
+Runs many traces, producing a `traces` file.
+
+ - `traces=expfile`: the population experiment from which to trace
+ - `sedd=int`: the random seed
+ - `count=int`: the number of traces to run, default is 1000
+ - `postfix`: the `traces` file postfix, default is ""
+ - `targets=intlist`: the list of targets to cycle through
+ - `cycles=int`: the number of times to cycle through the targets per epoch
+ - `sampleperiod=generations`: the sampling period in generations, default is 1 (take all samples)
+ - `exposureEpoch=int`: the starting epoch
+ - `epochs=int`: the number of epochs to run
+ - `q`: the cli-prefix (no idea why this is configurable)
+
+### `concat`
+
+Concatenates data files together.
+
+ - `concat`: a semi-colon delimited list of data-files
+ - `out`: output filenamee
+ - `mode`: the concatenation mode, one of `traj` (trajectories) or `ws` (wholesamples)
+
+#### Trajectories mode
+
+The trajectories mode, `traj`, concatenates trajectory files together (e.g. `rcs`, `ist`, `ivmcswitchness`, etc.). If your files do not have a sample period, you will have to provide one.
+
+ - `trimends`: set to trim the last entry in all but the last trajectories (similar to `skipfirst`, but the other way round)
+ - `sampleperiod`: the trajectory sampleperiod
+
+#### Wholesamplse mode
+
+The trajectories mode, `ws` or `wholesamples`, concatenates `wholesample` files together.
+
+ - `skipfirst`: skip the first entry in successive files (similar to `trimends`, but the other way round)
+ - `resequence`: resequences wholesamples in the order supplied
+
+### `extract`
+
+Extracts a population experiment from a `wholsamples` or `tracee`.
+
+ - `extract`: the `wholesamples` or `tracee` source file to extract from
+ - `expfile`: the experiment save file to reconfigure
+ - `epoch`: the epoch to extract (`wholesamples` only)
+ - `generation`: the generation to extract (`tracee` only)
+ - `outdir`: the experiment output directory
+ - `postfix`: the experiment savefile postfix
+ - `zeroStart`: whether to zero the start epochs and generations (default is false: keep)
+ - `firstOfEpoch`: whether to take the first entry for the given epoch (default is false: takes the last)
+
+### `analyse`
+
+Defers to `ICliAnalysers` based on the prefix of the file parameter. Only an `rcs` analyser is currently provided.
+
+### `hebbian`
+
+Generates individuals from an experiment by running a number (usually 1) of epochs, and then generates a genotype with a DTM that is the average of the autocorrelations of the individual genotype or phenotype or something.
+
+ - `hebbian=expfile`: the experiment file
+ - `epochs=int`: the number of epochs to run (default 1)
+ - `startEpoch=int`: the start epoch (default 0)
+ - `generations=int`: overrides the per-target generation count if set
+ - `count=int`: the number of samples to use
+ - `targets=intlist`: the sequence of targets (by index): by default, the first target it used alone
+ - `initialreset`: if set, overrides the reset operation
+ - `randomvector`: if set, overrides the reset operation (ignored if `initialreset` if set), one of `binary` or `uniform`
+ - `extractor`: the vector extractor to use, either `g` (the initial state vector) or `p` (the phenotypic state vector)
+ - `out`: output file name, ish: a `genome` file and PDF plot will be generated into the working directory
+ - `title`: the title of the PDF output
+ - `seed=int`: the random seed (default 1)
+ - `weighted=bool`: whether the averaging should be weighted by fitness
+
+### `reconfig`
+
+Reconfigures a population experiment, enabling you to make specific adjustments to the configuration, modify the population, and change the targets. Parameters are grouped logically.
+
+#### Boring Parameters
+
+ - `outdir`: the directory wherein to place the reconfiged population experiment
+ - `postfix`: the postfix to append to the population experiment filename
+
+#### Population Experiment Config
+
+Available parameters include:
+
+ - `spinner`: the population spinner
+ - `hillclimberMode=bool`: hill-climber mode
+ - `elitecount=int`: the elite-count
+ - `selectorpreparer`: the selector-preparer
+
+#### Experiment Config
+
+Available parameters include:
+
+ - `epochs=int`: the number of epochs to run
+ - `K`: the number of generations per target per epoch
+ - `initialStateResetProbability=[0,1]`: the probability of reseting the population at the start of each target exposure
+ - `targets=epistaticTargetPackage`: parses targets (e.g. `exp:`, `file:`, `ivmc:`, `mc:`); behaviour likely to change at some point to match composers
+ - `targetCycler`: the target cycler
+
+#### Population
+
+You can change various things about the popuation, including swapping out completely. The following parameters are applied in order, and compound upon each other:
+
+ - `population=expfile`: pull in a population from another different population experiemnt
+ - `genome=genomefile`: pull in a new genome from a file
+ - `g0`: the initial state vector
+ - `b0`: the developmental transformation matrix
+ - `pop.size`: the new population size. The populatoin is shuffled, before looping over the population and taking as many individuals as requested
+
+#### `jrules`
+
+JRules refers to Judgement Rules. Available parameters include:
+
+ - `jrules.lambda=double`: the regularisation factor
+ - `jrules.kappa=double`: the vector-target noise size
+
+#### `drules`
+
+DRules refers to Developmental Rules. Available parameters include:
+
+ - `drules.T=int`: the number of developmental timesteps
+ - `drules.tau1=[0,1]`: the update rate
+ - `drules.tau2=[0,1]`: the decay rate
+
+#### `rrules`
+
+RRules refers to Reproduction Rules. Available parameters include:
+
+ - `rrules.RB=double`: the probability of mutating the dtm (usually known as `rprob`)
+ - `rrules.MG=double`: the magniute of initial state mutations
+ - `rrules.MB=double`: the magniute of dtm mutations
+ - `rrules.MGtype=double`: the type of mutations applied to the initial state vector
+ - `rrules.MBtype=double`: the type of mutations applied to the dtm
+ - `rrules.BEX=bool`: controls whether dtm mutations exclude initial state mutations
+ - `rrules.CG=int`: the number of updates applied to the initial state vector per mutation
+
+### `bestiary`
+
+The `bestiary` provider has two functions: tabulating dtms, and tabulating mutant phenotypes. These have some common parameters:
+
+ - `out=file.png`: controls the output file-name. If not extension is provided, '.png' will be appended
+ - `min=double`: the value that is mapped to the coldest colour
+ - `bhot`: sets black to be the hot colour
+ - `whot`: sets white to be the hot colour, which is the default and overrides `bhot` if provided
+
+#### Dtms
+
+If `dir=directory` is provided as a parameter (with not value provided to `bestiary`), then table (bitmap) is produced from `genome` files within the sub-sub-directories of the given directory. Parameters include:
+
+ - `gname=genomefilename`: specifies the file-name to look for in sub-sub-directories, default is `terminalgenome.dat` (for historical reasons, this file doesn't start with `genome`, but is otherwise equivalent to `genome_t.dat`)
+ - `labels=bool`: controls whether labels should be rendered on the table, default is true (if the system crashes complaining about fonts, you probably want to disable these)
+ - `font=fontname`: specifies the font to use if labels are to be added
+
+#### Mutants
+
+If `bestiary=epochfile` is provided as a parameter, a number of mutatnts are generated for every member of the population in the `epoch` file based on a given `gmode`, and their phenotypic state vectors drawn on a bitmpa image (one-row per module).
+
+ - `gmode=mutationmode`: controls how mutants are generated from the given genome, details below
+ - `seed=seed`: sets the random seed to be used
+ - `count`: the number of mutants per individual in the population, default is 100
+
+There are 3 mutation modes:
+
+ - `gmode=binary`: each mutant has a single element set to either -1 or +1 randomly
+ - `gmode=uniform`: each mutants has a single element set uniformally in the G-clamping range randomly
+ - `gmode=mutate`: uses reproduction rules based on the population experiment configuration supplies as a parameter to `bestiary`
+
+### `sparseswitchers`
+
+Given an experiment file and the indices of the traits in a module, finds sparse network configurations which allow switching by flipping the first trait in the module
+
+ - `sparseswitchers=filename`: the config to use
+ - `module=intlist`: the list of traits in the module, default is to use all traits
+ - `target=int`: the index of the target to use, default 0
+ - `epoch=int`: target reset exposure epoch, implies reset target if set
+ - `resettarget`: resets the target
+ - `seed=int`: random seed
+ - `proper`: enables/disables cplus and cminus
+ - `cplus=doublelist`: list of c_plus values for target modules
+ - `cminus=doublelist`: list of c_minus values for target modules
+ - `T=int`: alternative developmental step count
+
+## Simple Example
+
+Before doing anything else, run `m4m` on its own to check you have everything set up and working. It should provide a list of providers if it is working. We will use or mention the `gen`, `exp`, `run`, `spit`, `plot`, `bestiary`, and `hebbian` providers in this example.
+
+### Scenario
+
+This is a simple example of how you might use the `m4m` tool. Perhaps you want to replicate the results found in a paper called _"How evolution learns to generalise"_ by Kouvaris et al. with the L1 (linear) regularistaion function. In particular, we want to plot the trajectories of regulatory connection strenghts over time, and investigate the distribution of phenotypes generated by the terminal genotype.
+
+### Generating an experiment
+
+There just happens to be a target package to describe the environment we need: `classic:orig4x4`. We could describe it with a heavily customised Ivmc target package, but that would detract from the point of a simple example. Here is how we will generate the experiment:
+
+    m4m gen=composedense topdir=Experiments name=Test1 lambda=0.1 epochs=200 targetpackage=classic:orig4x4 regfunc=l1 epochs=200 K=20000 mg=0.1 mgtype=uniform mb=1e-4 mbtype=uniform bprob=1 bex=false decayrate=0.2 T=10 typo=true
+
+There is a _lot_ of information here, and it is all model parameters. If anyone claims to have a simple model, but it has this many parameters (there are probably a few more which are defaults which I've forgotten to mention) then they are lying. Let's go through everything to reassure you that nothing scary is happening, and to give you an idea of how you can start to change parameters to run new and exciting experiments that nobody else will care about:
+
+ - `gen=composedense`: tells m4m we need the `gen` provider, and tells the provider to compose something dense (never use anything else: they are all legacy and unmaintained)
+ - `topdir=Experiments`: create our experiment within a directory called `Experiments`
+ - `name=Test`: Create the experiment within a sub-directory called `Test`
+ - `regfunc=l1`: use the L1 regularisation function (cost of connections)
+ - `lambda=0.1`: the cost factor
+ - `targetpackage=classic:orig4x4`: use the classic `orig4x4` target package, which contains 3 targets
+ - `epochs=200`: 200 epochs please
+ - `K=20000`: 20000 generations per target per epoch
+ - `bex=false`: mutate B and G at the same time
+ - `bprob=1`: always mutate B
+ - `mgtype=uniform`: uniform mutation in G
+ - `mg=0.1`: small mutations in G
+ - `mbtype=uniform`: uniform mutation in B
+ - `mb=1e-3`: no idea what this number is: you'll need to read the paper to calculate it; I am too lazy to do so now
+ - `typo=true`: doesn't do anything apart from illustrate the unused parameter warning, which you should always check
+ - `T=10`: the number of developmental timesteps is 10, which happens to be the default, but default are liable to mean you default on your morgage payments, so specify it anyway
+ - `decayrate=0.2`: also a default (tau_2 in the paper)
+
+Running this `gen` command should create an `Experiments` directory containing another `Test` directory, which contains the following files:
+
+ - config.dat: a configuration data file (not every useful)
+ - config.txt: a text-summary of the experiment, which is very useful for checking the experiment is as expected: don't modify it, as it doesn't change the experiment, and will only create confusion. If you do modify or delete the file by accident, you can create a new one by running `m4m -spit epoch0savestarter.dat` (it works with any `epoch` experiment file)
+ - epoch0savestarter.dat: The experiment file itself, which is explained in detail somewhere in this document. This is the file we will 'run' in order to perform the experiment.
+ - initialpopulation.dat: The initial population. I don't think I've ever used one of these for anything, so feel free to delete it (the same information in contained in the experiment file)
+
+It should also inform you that the "typo" parameter was not consumed. If you'd typo'd `decayrate` (or some other important thing) this would be your first warning of this fact. Your second warning should be when you look at the `config.txt` file to check everything looks correct. Your third warning is when you get angry because nothing makes sense, so it's best to make the most of the first 2.
+
+### Running an experiment
+
+Now we can run the experiment. We can use either the `exp` or `run` providers. The `exp` provider is good for running just one experiment. The `run` provider is good for running a whole block at once. Both are easy to use, and are generally configurable in the same way.
+
+This is how to run an experiment 4 times with a given seed (the repeats will have incrementally increasing seeds):
+
+    m4m -exp Experiments/Test1/epoch0savestarter.dat seed=1 repeats=4
+
+Because we have specified a non-trivial number of repeats, the program will produce a separate output directory for each repeat, called `r0`, `r1`, `r2`, etc. Had we not specified a number of repeats, the outputs would be outputed to the same directory as experiment file.
+
+A similar result can be achieved with `run` as followed:
+
+    m4m -run Experiments seed=1 repeats=4 postfix=One
+
+This will create a directory called `TestrunsOne` containing the outputs.
+
+If we had any more experiments in directors in `Experiments` (e.g. created with `name=Test2` or `name=Test3`, etc.), then these would have also been run.
+
+We could run a second set of runs (e.g. with a different base seed) into a new directory by changing the postfix.
+
+The output from `exp` and `run` is very similar, and usually worth holding onto (e.g. by piping to a log file) so that you can look up seeds and run-time in the future. While the experiments are running themselves, it prints incremental reports on its progress, and basic population metrics (mean benefit, cost, and fitness).
+
+As the simulation runs, output files will start to appear. The last set to be produced will all end in `_t`, indicating the terminal copy. Usually we end up deleting all the numbered files which also have an `_t` varient, because they contain a subset of the same data (or are genome files we don't really care about). In our case, we asked for only 200 epochs, so there are not many 'partial' results.
+
+ - config.dat: as discussed
+ - config.txt: as discussed
+ - epoch0savestart.dat: a copy of epoch0savestarter.dat
+ - epoch200saveterminal.dat: the terminal experiment file, which contains all the information to run the experiment, but contains the terminal population and internally knows it has reached 200 epochs. These are extremely useful for analyses, and can be used to 'continue' an experiment if we need to
+ - fitness_t(best).dat: fitness trajectories
+ - genome0.dat: the starting genome
+ - genome200.dat: genome at end of epoch 200
+ - genome_t.dat: terminal genome
+ - initialpopulation.dat: as discussed
+ - ist_t(best).dat: initial state trajectories
+ - pst_t(best).dat: phenotypic state trajectorties
+ - rcs_t(best).dat: regulatory connection strength trajectories
+ - terminalgenome.dat: terminal genome again, but with a less-useful filename
+ - wholesamplese200.dat: wholesamplese from epoch 0 to 200
+
+The `m4mclean` powershell function in `m4minit.ps1` will recursively clean away any numbered files in directories that contain a `genome_t.dat` file.
+
+### Plotting results
+
+Drop into one of the output directories (e.g. `Experiments/Test1/r0` ir you used `exp` or `ExperimentsrunsOne/Test1r0` if you used `run`) to make life easy:
+
+    cd Experiments/Test1/r0
+    cd ExperimentsrunsOne/Test1r0
+
+First things first, we should look at the terminal genome. We can plot it with the following command.
+
+    m4m -plot genome_t.dat thing=dtm out=terminal forceaspect title="Terminal Developmental Matrix" min=-1 max=+1
+
+With a bit of luck, it should look like this:
+
+![Terminal Genome](ReadmeFigures/terminal.pdf.png)
+
+The parmeters we have given to `plot` (which is the provider we are using here) are as follows:
+
+ - `plot=genome_t.dat`: the file we want to plot, which for `genome` files plots the developmental transformation matrix by default
+ - `thing=dtm`: asks to plot the dtm (which is also the default). We could alternative have asked for `is` or `ps`, the initial and phenotypic states respectively
+ - `out=terminal`: asks it to output any file to `terminal.pdf` (the extension is implied: in future it might support more export types). If you don't specify `out`, it will be the same as the input file name
+ - `forceaspect`: a flag parameter which forces a genome plot like this to be square ish: you'll generally not use it for anything else
+ - `title=whatever`: specifies the title of the plot
+ - `min=-1`: specifies the minimum (black) value: by default it is the minimum value in the plot, which is great for comparing within one genome, but less so when comparing between plots
+  - `max=+1`: specifies the maximum (white) value: by default it is the maximum value in the plot
+
+Plotting an `epoch` experiment file will, by default, behave like plotting a `genome` file, though there are other things you can plot, and the `thing=dev` option shows the trait expression trajectories from initial state to phenotypic state.
+
+Since our genome looks about right, let's see what actually happened during the experiment:
+
+    m4m -plot rcs_t(best).dat
+
+This is a bit hideous, so add the `green` flag to make it tidier looking. It is also clear that not a whole lot happens after epoch `20`, so set `end=20` so we can focus on the detail. We also don't need the legend, so get rid of it by nulling it with `legend=`:
+
+    m4m -plot "rcs_t(best).dat" green end=20 legend= title="Regulatory Connections Trajectories" out=rcs
+
+Hopefully it looks like this:
+
+![Regulatory Connection Trajectories](ReadmeFigures/rcs.pdf.png)
+
+### Phenotype Distributions
+
+Though the DTM is extremly informative by itself, it can help to look at the sorts of phenotypes it produces when random initial state vectors are developed with it.
+
+    m4m -bestiary epoch200saveterminal.dat out=nb.png gmode=uniform seed=1 count=100
+
+This will give you something like this (one pixel per trait; row-major):
+
+![Sample Phenotypes](ReadmeFigures/nb.png)
+
+ - `bestiary=epoch200saveterminal.dat`: the experiment from which to sample
+ - `out=nb.png`: the output filename (this time it needs an extention... maybe .jpg would work, I don't know, it's delegated to ImageSharp)
+ - `seed=1`: the random seed
+ - `gmode=uniform`: the initial state randomisation mode, uniform random values
+ - `count=100`: the number of phenotype samples to generate
+
+You can add a `generations=int` parameter to cause each sample to go through that many generations before being rendered.
+
+If looking at phenotypes individually isn't your thing, the `hebbian` provider will average the correlations between them for you:
+
+    m4m -hebbian epoch200saveterminal.dat out=hebb generations=0 seed=1 initialreset=uniform extractor=p
+
+This should give you:
+
+![Sample Phenotypes](ReadmeFigures/hebb.pdf.png)
+
+ - `hebbian=epoch200saveterminal.dat`: you guessed, the experiment from which to sample
+ - `out=hebb`: the output name (you get a PDF and a genome file)
+ - `generations=0`: don't run any geneations (unlike with `bestiary`, this defaults to `K` from the experiment)
+ - `initialreset=binary`: the initial state randomisation mode, binary random values
+ - `extractor=p`: take the phenotype (default is the genotype, which with `generations=0` won't tell you much)
+ - `seed=1`: the random seed
+
+### Running a Tracee
+
+We can run a Tracee, to investiage the within- and between-epoch behaviour of the system. A tracee is run from an `epoch` experiment file: often we may want to compare the behaviour at the start or end of a simulation (note that you can configure `exp` and `run` to record `tracees` at intervals (see section _Default Feedback_), which is a better solution if you want to show the change in behaviour of a specific 'run' of the whole system).
+
+Let's get two post-hoc tracees, for the start and terminal `epoch` experiment files:
+
+    m4m -tracee .\epoch0savestart.dat epochs=5 targets="1;2;0" sampleperiod=10 postfix=0
+    m4m -tracee .\epoch200savestart.dat epochs=5 targets="1;2;0" sampleperiod=10 postfix=200
+
+Note the slightly weird ordering of the `targets` parameter, which relects the behaviour of the default target cycler (i.e. starts with the 'second' target, not the first). Note also that we have reduced the sample period to keep the file size small, and provided a different postfix for each file, that corresponds to the epoch number of the experiment.
+
+These tracees are of no use without being about to interrogate them. Let's plot part of both of them, so we can compare:
+
+    m4m -plot .\tracee0.dat start=100k end=140k providers=fitness:f markertype:f=diamond downsample=100
+    m4m -plot .\tracee200.dat start=100k end=140k providers=fitness:f markertype:f=diamond downsample=100
+
+Here, we use the traceplotting provider `fitness`, because it allows use to configure the marker type, and we only plot the values between 100k and 140k epochs.
+
+If you happen to have the `FigForm` utility handy, you can place the resulting pdfs side-by-side (or over-and-under, if you change the arrangment to `0&1`) as so:
+
+    figform -pages ".\tracee0.dat.pdf;.\tracee200.dat.pdf" -arrangement "0;1" -out both.pdf
+
+![Two Tracees, side by side.](ReadmeFigures/traceesSideBySide.png)
+
+### Running Traces
+
+Traces have very little in common with Tracees. Instead of running one simulation over potentially many epochs (recording alot of information along the way), traces runs many simulations, and only record basic metrics (e.g. the best fitness in the population).
+
+    m4m -traces epoch200saveterminal.dat count=100 postfix=200
+    m4m -plot traces200.dat areas wide=0,50
+
+The `areas` parameter means we want to plot the 'percentile bounds' over all simulations, and the `wide` parameter determines which percentile ranges are plotting: e.g. `0` refers to the range `0-100` (i.e. shows the whole range of values), `25` would refer to the inner quartiles, and `50` is special, refering to the mean, which is plotted as a solid line rather than an area.
+
+Although they record less information - and in a more efficient format - it is still advisible to reduce the `sampleperiod` when running `traces`. In this particular set of traces, we can clearly see two s-curves (corresponding to the changing of two dense modules).
+
+![Traces.](ReadmeFigures/traces.png)
+
+Intuitively, this makes no sense... and when I take a while to think about it, it still makes no sense, but there's probably some mathsy reason why this makes sense to do with the fact that we have two random variables which are sorting on a per-sample basis before (visually) assigning them to (now biased) distributions or something.
+
+### Reconfiguring an experiment
+
+Let's turn on exclusive `B` mutation, set the probability of `B` mutation to one half, and see if the traces change.
+
+    m4m -reconfig .\epoch200saveterminal.dat outdir=reconfigs rrules.bex=true rrules.rb=0.5 postfix=BEx
+    cd .\reconfigsBEx\
+    m4m -traces .\epoch0savestarter.dat count=100 targets=1
+
+![Reconfiged Traces.](ReadmeFigures/reconfigedTraces.png)
+
+Nope. That isn't it, and the fact that the curves are almost exactly twice the size suggests the `B` has no influence on `G` anyway. The reality is that this is just an artefact of the percitile ranging: by 1200 generations, most runs have changed one module, and by 1400, most have changed the other: there is no systematic pattern to the runs themselves (i.e. the distribution of swap times for modules is independent and not bi-modal).
+
+Maybe the area plotting mode is misleading us?
+
+That concludes the basic example. The best way to work out how to use the various tools is to look at the code, but hopefully this readme is of some utility.
+
+## M4M Utility Tools
+
+There are 3 additional (windows only) tools that may be of some great benefit. They are all (fairly simple) UI's built on top of the M4M.Model and M4M.New libraries, and are built with the m4mbuild powershell command from the Visual Studio Developer Powershell. Direct DLL referenes are used in-code (rather than project reference) due to issues with older versions of msbuild and Visual Studio. They are .NET Framework applications (though I intend to make them also compile to .NET Core 3.1 at some point) using the Windows Forms GUI framework.
+
+### M4MPlotting
+
+A small program that allows interaction with plots preduced by `m4m plot` commands. There was a simplified cross-platform version of this once, but it was slow. If you need it, give me a shout and I'll dig it out (or more likely re-write it).
+
+![Interrogating an rcs file with M4MPlotting](ReadmeFigures/m4mplotting.png)
+
+Though it mostly just shows plots and allows you modify the 'command-line' arguments for the m4m plot command, there is some additional functionality included for dealing with specific files that is mostly easy to discover:
+
+ - click on `rcs` trajectories to see the trans mat at that point in time
+ - click on `wholesmaples` or `tracee` trajectories to see the trans mat/initial states/whatever at the point
+ - the `CliQ` misc plotter happens to be interactive
+ - you can add draggable cursors with the `cursors` flat, which can be customised per axis (e.g. `cursors:x=blue`)
+
+I use this tool endlessly, and have .dat files open with it by default, so I can just double click on a data-file to see what is inside. You can slap Ctrl-C to copy a bitmap of the image, or export it as a PDF or PNG from the File > Export menu (the extension name determines the output format). The File > View Logs menu shows you the same output you would get from running the command at the command line.
+
+The `m4mplot` function in `m4minit.ps1` opens this and copies the other command-line parameters into the parameter text box. This is handy for opening e.g. large files where you have to specify a large downsample in order for it to render in good time.
+
+Don't overlook the `File` > `ViewLogs` option, which is handy for checking for errors, unused flags, and picking up axis keys.
+
+### M4MDenseDev
+
+A not-so small program that allows viewing of wholesamples or rcs/ist pairs, and messing about with dense genomes. This is used in the usual demo, where a hierarchy produing rcs is loaded, the switch genes are identified, and then G is set so that the switch genes are high and everything else is low. Sweeping through the rcs then shows up how hierarchy enables the switch genes to take over. To load stuff, you must drag the files onto the main window, which also allows you to quickly compose an experiment for testing/demonstration purposes (though usually you will want to load actual data files).
+
+![The classic evolution of hierarchy demo](ReadmeFigures/m4mdensedev.png)
+
+Where applicable, M4MDenseDev is a better tool than M4MPlotting, because it is optimised to work with what it expects, rather than just being a thin layer over the command-line plotter. It is very useful for demonstrating what different regulatory matricies do, and getting a feel for the signal felt by the B matrix.
+
+### M4MDensemod
+
+A small program that allows you to step one epoch at a time and modify the initial state vector. It is really only useful for investigaing static landscape problems, with a nice visualiasation for 4-neighbour constraint matricies (e.g. the concentric squares problem).
+
+![The concentric squares problem](ReadmeFigures/m4mdensemod.png)
+
+## Design Notes 
+
+There are many bad things about M4M. Here are some of the more important mistakes in its design:
+
+ - There is no strong seperation between things in a population, and things outside of a population: this makes it hard (impossible) to provide low-overhead and 'ideal' reset operations, because they have to perform a development step themselves, because they can't assume nothing has escaped the population
+    - This basically means the epistatic mode can't support reset operations properly
+    - It also means that there is a hideous system for tracking 'escaped' individuals: we tried to provide a seperation with the `Process' method, but isn't good enough and I can't assume it works
+ - There is no clarity on when individuals are reported in feedback
+ - The developmental interface is too restrictive
+ - A number of key components use caching to reduce overhead, which makes parallelism impossible
+ - The parallism mechanism that exists (but should not) makes a mockery of deterministic execution: don't use it
+ - The individuals that are reported by the `SpinPopulation` methods are not those that are in the population after each generation: they are those in the population before each generation. This is because not all (population) modules actually require the offspring to be evaluated, but what it means is that all the feedback stuff is slightly misleading.
+
+Some other things that could be better:
+
+ - Expfiles should not have a directory associated with them
+ - Experiment duration should be easier to change
+ - Experiment configs should be eaiser to edit, or should know how to 're-generate' themselves: they should have a known seed
+ - Experiment seeds are not persisted: you can't continue an experiment with the same seed
+ - There shouldn't be a hard dependency on MathNET: it would be inefficient to inject AD stuff, because conversions are be necessary; it would be nicer if (at run-time) any math-backing could be used
+ - `savestart.dat` autocompletes before `savestarter.dat`, so it's very easy to run the wrong thing when you are reusing directories
+ - Lots of components do not depend on the type of individual; however, there isn't the infrastructure to support such genericism, so they mostly fail if used with anything else
\ No newline at end of file
diff --git a/M4MCode/NetState/NetState.sln b/M4MCode/NetState/NetState.sln
new file mode 100644
index 0000000..a18ea1a
--- /dev/null
+++ b/M4MCode/NetState/NetState.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29025.244
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetState", "NetState\NetState.csproj", "{401F0D7B-2FC0-40CC-9DFE-A390352093C2}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{401F0D7B-2FC0-40CC-9DFE-A390352093C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{401F0D7B-2FC0-40CC-9DFE-A390352093C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{401F0D7B-2FC0-40CC-9DFE-A390352093C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{401F0D7B-2FC0-40CC-9DFE-A390352093C2}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {9628C54B-3DB5-4E02-B97E-1DC6378D518F}
+	EndGlobalSection
+EndGlobal
diff --git a/M4MCode/NetState/NetState/AutoState/AutoGraphProviders.cs b/M4MCode/NetState/NetState/AutoState/AutoGraphProviders.cs
new file mode 100644
index 0000000..b96d44e
--- /dev/null
+++ b/M4MCode/NetState/NetState/AutoState/AutoGraphProviders.cs
@@ -0,0 +1,177 @@
+using NetState.Serialisation;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace NetState.AutoState
+{
+    /// <summary>
+    /// Provides StateProviders for common primitive types
+    ///  - Bool
+    ///  - Byte
+    ///  - Int
+    ///  - Long
+    ///  - String
+    /// Class
+    /// </summary>
+    public class AutoGraphStateProviderTable : IStateProviderPreparer<IWriteGraphStreamContext<Object>, IReadGraphStreamContext<Object>>
+    {
+        public readonly static AutoGraphStateProviderTable Instance = new AutoGraphStateProviderTable();
+
+        private readonly Dictionary<Type, Type> PrimitiveLookup = new Dictionary<Type, Type>();
+
+        public AutoGraphStateProviderTable(bool includeDefaults = true)
+        {
+            if (includeDefaults)
+                AddDefaults();
+        }
+
+        private void AddDefaults()
+        {
+            AddProvider(typeof(bool), typeof(BoolStateProvider));
+            AddProvider(typeof(byte), typeof(ByteStateProvider));
+            AddProvider(typeof(int), typeof(IntStateProvider));
+            AddProvider(typeof(long), typeof(LongStateProvider));
+            AddProvider(typeof(float), typeof(FloatStateProvider));
+            AddProvider(typeof(double), typeof(DoubleStateProvider));
+            AddProvider(typeof(string), typeof(StringStateProvider)); // TODO: make these graph objects?
+        }
+
+        public void AddProvider(Type targetType, Type providerType)
+        {
+            PrimitiveLookup.Add(targetType, providerType);
+        }
+
+        public IStateProvider<T, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>> TryResolveInstance<T>()
+        {
+            Type provisional = TryResolve(typeof(T));
+
+            if (provisional == null)
+                return null;
+
+            return AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>>>(provisional);
+        }
+
+        public Type TryResolve(Type t)
+        {
+            if (PrimitiveLookup.TryGetValue(t, out var found))
+            {
+                return found;
+            }
+            else if (t.IsValueType == false)
+            {
+                Type gspt = typeof(GraphStateProvider<,>);
+                Type gspgt = gspt.MakeGenericType(new[] { t, typeof(object) });
+                return gspgt;
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        public IStateProvider<T, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>> Prepare<T>()
+        {
+            return TryResolveInstance<T>();
+        }
+    }
+
+    /// <summary>
+    /// Provides ClassStateProviders for common types over the given AutoGraphStateProviderTable
+    ///  - String
+    ///  - List of T
+    ///  - Dictionary of TKey, TValue
+    /// </summary>
+    public class AutoGraphClassStateProviderTable : ICustomStateProviderProvider<object, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>>
+    {
+        public readonly static AutoGraphClassStateProviderTable Instance = new AutoGraphClassStateProviderTable(AutoGraphStateProviderTable.Instance);
+        
+        private readonly Dictionary<Type, Type> PrimitiveLookup = new Dictionary<Type, Type>();
+        public AutoGraphStateProviderTable AutoGraphStateProviderTable { get; }
+
+        public AutoGraphClassStateProviderTable(AutoGraphStateProviderTable autoGraphStateProviderTable)
+        {
+            PrimitiveLookup.Add(typeof(string), typeof(StringStateProvider));
+            AutoGraphStateProviderTable = autoGraphStateProviderTable;
+        }
+
+        public IClassStateProviderFactory<T, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>> TryResolve<T>(ref int version) where T : class
+        {
+            Type provisional = TryResolve(typeof(T));
+
+            if (provisional == null)
+                return null;
+
+            return AutoSerialisationHelpers.AcquireInstance<IClassStateProviderFactory<T, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>, IWriteGraphStreamContext<object>, IReadGraphStreamContext<object>>>(provisional);
+        }
+
+        public Type TryResolve(Type t)
+        {
+            // TODO: need to support Multi-Dim arrays
+            // TODO: need to support BOXED Primitives
+
+            if (PrimitiveLookup.TryGetValue(t, out var provisional))
+            {
+                return provisional;
+            }
+
+            if (t.IsArray)
+            {
+                if (t.GetArrayRank() != 1)
+                    throw new NotImplementedException("Mutli-dimensional arrays are not presently supported for automatic serialisation; shout at the maintainer");
+                
+                var at = typeof(AutoArrayStateProvider<,,,>);
+                var elementType = t.GetElementType();
+
+                var agt = at.MakeGenericType(new[] {
+                    elementType,
+                    AutoGraphStateProviderTable.TryResolve(elementType),
+                    typeof(IWriteGraphStreamContext<object>),
+                    typeof(IReadGraphStreamContext<object>)
+                    });
+
+                return agt;
+            }
+            else if (t.IsGenericType)
+            {
+                var gtd = t.GetGenericTypeDefinition();
+                var gta = t.GetGenericArguments();
+                
+                // TODO: ought to do arrays somehow
+                if (gtd == typeof(List<>))
+                { // List<ElementType>
+                    var at = typeof(AutoListStateProvider<,,,>);
+                    var elementType = gta[0];
+
+                    var agt = at.MakeGenericType(new[] {
+                        elementType,
+                        AutoGraphStateProviderTable.TryResolve(elementType),
+                        typeof(IWriteGraphStreamContext<object>),
+                        typeof(IReadGraphStreamContext<object>)
+                        });
+
+                    return agt;
+                }
+                else if (gtd == typeof(Dictionary<,>))
+                { // Dictionary<KeyType, ValueType>
+                    var at = typeof(AutoDictionaryStateProvider<,,,,,>);
+                    var keyType = gta[0];
+                    var valueType = gta[1];
+
+                    var agt = at.MakeGenericType(new[] {
+                        keyType,
+                        valueType,
+                        AutoGraphStateProviderTable.TryResolve(keyType),
+                        AutoGraphStateProviderTable.TryResolve(valueType),
+                        typeof(IWriteGraphStreamContext<object>),
+                        typeof(IReadGraphStreamContext<object>)
+                        });
+
+                    return agt;
+                }
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/AutoState/AutoGraphSerialisation.cs b/M4MCode/NetState/NetState/AutoState/AutoGraphSerialisation.cs
new file mode 100644
index 0000000..cfc9de9
--- /dev/null
+++ b/M4MCode/NetState/NetState/AutoState/AutoGraphSerialisation.cs
@@ -0,0 +1,624 @@
+using NetState.Serialisation;
+using NetState.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace NetState.AutoState
+{
+    public interface ICustomClassStateProviderAttribute
+    {
+        IClassStateProviderFactory<T, TSourceContext, TSinkContext, object, object> Prepare<T, TSourceContext, TSinkContext>() where T : class;
+        int Version { get; }
+    }
+    
+    public class CustomClassStateProviderAttribute : Attribute, ICustomClassStateProviderAttribute
+    {
+        public CustomClassStateProviderAttribute(Type stateProviderType, int version = 0)
+        {
+            StateProvider = AutoSerialisationHelpers.AcquireInstance<object>(stateProviderType);
+            Version = version;
+        }
+        
+        private object StateProvider { get; }
+        public int Version { get; }
+
+        public virtual IClassStateProviderFactory<T, TSourceContext, TSinkContext, object, object> Prepare<T, TSourceContext, TSinkContext>() where T : class
+        {
+            if (StateProvider is IClassStateProviderFactory<T, TSourceContext, TSinkContext, object, object> yep)
+            {
+                return yep;
+            }
+            else
+            {
+                throw new Exception($"Custom State Provider type {StateProvider.GetType().FullName} is not compatible with {typeof(IClassStateProviderFactory<T, TSourceContext, TSinkContext, object, object>)}");
+            }
+        }
+    }
+
+    // this stuff assumes a Type-based factory lookup
+    public interface IWriteGraphStreamContext<TBase> : IStreamContext
+    {
+        void WriteRef<T>(T t) where T : class, TBase;
+    }
+    
+    public interface IReadGraphStreamContext<TBase> : IStreamContext
+    {
+        T ReadRef<T>() where T : class, TBase;
+    }
+
+    public class AutoGraphStateProviderPreparer<TBase> : IStateProviderPreparer<IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>>
+    {
+        public IStateProvider<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> Prepare<T>()
+        {
+            var at = typeof(GraphStateProvider<,>);
+            var agt = at.MakeGenericType(
+                typeof(T),
+                typeof(TBase)
+                );
+
+            // type gap to satisfy constraints
+            return AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>>>(agt);
+        }
+    }
+
+    public class GraphStateProvider<T, TBase> : IStateProvider<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> where T : class, TBase
+    {
+        public static readonly GraphStateProvider<T, TBase> Instance = new GraphStateProvider<T, TBase>();
+
+        public GraphStateProvider()
+        {
+        }
+        
+        public T Read(IReadGraphStreamContext<TBase> context)
+        {
+            return context.ReadRef<T>();
+        }
+
+        public void Write(IWriteGraphStreamContext<TBase> context, T state)
+        {
+            context.WriteRef<T>(state);
+        }
+    }
+
+    public interface ICustomStateProviderProvider<TBase, in TWriteContext, in TReadContext, in TProviderWriteContext, in TProviderReadContext> where TBase : class
+    {
+        /// <summary>
+        /// Returns null if unable to resolve a state provider for the given generic type
+        /// </summary>
+        IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> TryResolve<T>(ref int version) where T : class, TBase;
+    }
+
+    public class CustomStateProviderProviders<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where TBase : class
+    {
+        private List<ICustomStateProviderProvider<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>> Providers { get; }
+
+        public CustomStateProviderProviders(IEnumerable<ICustomStateProviderProvider<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>> providers = null)
+        {
+            Providers = providers?.ToList() ?? new List<ICustomStateProviderProvider<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>>();
+        }
+
+        public void AddProvider(ICustomStateProviderProvider<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> provider)
+        {
+            Providers.Add(provider);
+        }
+
+        public IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> TryResolve<T>(ref int version) where T : class, TBase
+        {
+            foreach (var provider in Providers)
+            {
+                IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> provisonal = provider.TryResolve<T>(ref version);
+                if (provisonal != null)
+                    return provisonal;
+            }
+
+            return null;
+        }
+    }
+    
+    internal class Versioned<T>
+    {
+        public Versioned(T entry, int version)
+        {
+            Entry = entry;
+            Version = version;
+        }
+
+        public T Entry { get; }
+        public int Version { get; }
+    }
+    
+    // TODO: need one of these which supports versions better, so that it can lookup by type and version, and uses the one with the highest version by default
+    // (e.g. store a list of versioned providers, if version == -1, use the highest version, otherwise look up the specific version)
+    public class CustomClassStateProviderTable<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : ICustomStateProviderProvider<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where TBase : class
+    {
+        // csps
+        private readonly Dictionary<Type, Dictionary<int, Versioned<object>>> Table = new Dictionary<Type, Dictionary<int, Versioned<object>>>();
+
+        public IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> TryResolve<T>(ref int version) where T : class, TBase
+        {
+            if (!Table.TryGetValue(typeof(T), out var subTable))
+            {
+                return null;
+            }
+
+            if (subTable.TryGetValue(version, out var provisional))
+            {
+                if (provisional.Entry is IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> yep)
+                {
+                    return yep;
+                }
+                else
+                {
+                    throw new Exception($"A provider was found for type {typeof(T)} and version {version}, but was not compatible; please report this to the maintainer");
+                }
+            }
+            else
+            {
+                var maxVersion = subTable.Max(vk => vk.Key);
+                version = maxVersion;
+                provisional = subTable[maxVersion];
+
+                if (provisional.Entry is IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> yepMax)
+                {
+                    return yepMax;
+                }
+                else
+                {
+                    throw new Exception($"A default provider was found for type {typeof(T)}, but was not compatible; please report this to the maintainer");
+                }
+            }
+        }
+
+        public void Assign<T>(IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> classStateProvider, int version) where T : class, TBase
+        {
+            if (version < 0)
+                throw new ArgumentException("Version must be non-negative", nameof(version));
+
+            if (!Table.TryGetValue(typeof(T), out var subTable))
+            {
+                subTable = new Dictionary<int, Versioned<object>>();
+                Table.Add(typeof(T), subTable);
+            }
+
+            if (subTable.ContainsKey(version))
+                throw new ArgumentException("An entry is already provided for the given type and version");
+
+            subTable.Add(version, new Versioned<object>(classStateProvider, version));
+        }
+    }
+
+    public class SerialisationInfo<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where TBase : class
+    {
+        public SerialisationInfo(CustomClassStateProviderTable<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> customClassStateProviderTable, CustomStateProviderProviders<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> customProviders)
+        {
+            CustomClassStateProviderTable = customClassStateProviderTable;
+            CustomClassStateProviderProviders = customProviders;
+        }
+
+        public CustomClassStateProviderTable<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> CustomClassStateProviderTable { get; }
+        public CustomStateProviderProviders<TBase, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> CustomClassStateProviderProviders { get; }
+    }
+
+    public class GraphStreamSerialisationInfo<TBase> : SerialisationInfo<TBase, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> where TBase : class
+    {
+        public GraphStreamSerialisationInfo(CustomClassStateProviderTable<TBase, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> customClassStateProviderTable, CustomStateProviderProviders<TBase, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> customProviders, bool fallbackToAutoClassStateProvider) : base(customClassStateProviderTable, customProviders)
+        {
+            //CustomClassStateProviderTable = customClassStateProviderTable;
+            //CustomClassStateProviderProviders = customProviders;
+
+            FallbackToAutoClassStateProvider = fallbackToAutoClassStateProvider;
+        }
+
+        //public CustomClassStateProviderTable<TBase, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> CustomClassStateProviderTable { get; }
+        //public CustomStateProviderProviders<TBase, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> CustomClassStateProviderProviders { get; }
+        
+        public bool FallbackToAutoClassStateProvider { get; }
+    }
+
+    public interface IGraphStreamWriter<TBase> where TBase : class
+    {
+        void WriteRoot<T>(T root) where T : class, TBase;
+        void WriteRoots<T>(IReadOnlyList<T> roots) where T : class, TBase;
+        void Reset(bool resetTypeAssignments);
+    }
+
+    public interface IGraphStreamReader<TBase> where TBase : class
+    {
+        T ReadRoot<T>() where T : class, TBase;
+        IReadOnlyList<T> ReadRoots<T>() where T : class, TBase;
+        void Reset(bool resetTypeAssignments);
+    }
+
+    public class AutoGraphSerialisation
+    {
+        private class BasicWriteContext<TBase> : IGraphStreamWriter<TBase>, IWriteGraphStreamContext<TBase> where TBase : class
+        {
+            public BasicWriteContext(StreamReaderWriter streamRw, GraphStreamSerialisationInfo<TBase> gsi)
+            {
+                StreamRw = streamRw;
+                Gsi = gsi;
+            }
+            
+            public Dictionary<Type, int> TypeAssignments { get; }  = new Dictionary<Type, int>();
+            public Dictionary<int, IUntypedClassStateProvider<TBase>> ClassStateProviders { get; } = new Dictionary<int, IUntypedClassStateProvider<TBase>>();
+            
+            public Dictionary<TBase, long> Table { get; } = new Dictionary<TBase, long>();
+            public Queue<KeyValuePair<long, TBase>> Queue { get; } = new Queue<KeyValuePair<long, TBase>>();
+
+            public StreamReaderWriter StreamRw { get; }
+            public GraphStreamSerialisationInfo<TBase> Gsi { get; }
+
+            private int AssignType(Type t)
+            {
+                if (!TypeAssignments.TryGetValue(t, out int typeId))
+                {
+                    typeId = TypeAssignments.Count + 1;
+                    TypeAssignments.Add(t, typeId);
+                    
+                    var stateProvider = AcquireStateProvider<TBase>(t, -1, Gsi); // version = -1 means we take the default
+                    ClassStateProviders.Add(typeId, stateProvider);
+
+                    return ~typeId;
+                }
+                else
+                {
+                    return typeId;
+                }
+            }
+
+            public int LookupTypeId(Type t)
+            {
+                return TypeAssignments[t];
+            }
+
+            public void WriteRef<T>(T state) where T : class, TBase
+            {
+                if (state == null)
+                {
+                    StreamRw.WriteInt64(-1);
+                    return;
+                }
+
+                if (!Table.TryGetValue(state, out long id))
+                {
+                    id = Table.Count + 1;
+                    Table.Add(state, id);
+
+                    // write id first
+                    StreamRw.WriteInt64(~id);
+
+                    var t = state.GetType();
+                    var typeId = AssignType(t);
+
+                    // then type info
+                    StreamRw.WriteInt32(typeId);
+                    if (typeId < 0)
+                    {
+                        typeId = ~typeId;
+
+                        var csp = ClassStateProviders[typeId];
+                        
+                        StreamRw.WriteLongPascalString("#" + AutoSerialisationHelpers.AssembleSimpleTypeName(t));
+                        StreamRw.WriteInt32(csp.Version);
+                        csp.WriteConfig(this);
+                    }
+                    
+                    ClassStateProviders[typeId].WriteCreate(this, state);
+                    
+                    Queue.Enqueue(new KeyValuePair<long, TBase>(id, state));
+                }
+                else
+                {
+                    StreamRw.WriteInt64(id);
+                }
+            }
+
+            public void WriteRoot<T>(T root) where T : class, TBase
+            {
+                WriteRoots(new[] { root });
+            }
+
+            public void WriteRoots<T>(IReadOnlyList<T> roots) where T : class, TBase
+            {
+                StreamRw.WriteInt32(roots.Count);
+
+                // write roots
+                foreach (var root in roots)
+                {
+                    WriteRef<T>(root);
+                }
+
+                while (Queue.Count > 0)
+                {
+                    // these have already had their refs written out, so we know the type is good
+                    var next = Queue.Dequeue();
+                
+                    var id = next.Key;
+                    var state = next.Value;
+
+                    var t = state.GetType();
+                    var typeId = LookupTypeId(t);
+
+                    StreamRw.WriteInt64(id);
+                    StreamRw.WriteInt32(typeId);
+                    ClassStateProviders[typeId].WriteState(this, next.Value);
+                }
+
+                // signal end
+                StreamRw.WriteInt64(-1);
+            }
+
+            public void Reset(bool resetTypeAssignments)
+            {
+                Table.Clear();
+                if (resetTypeAssignments)
+                    TypeAssignments.Clear();
+            }
+        }
+
+        private class BasicReadContext<TBase> : IGraphStreamReader<TBase>, IReadGraphStreamContext<TBase> where TBase : class
+        {
+            public BasicReadContext(StreamReaderWriter streamRw, GraphStreamSerialisationInfo<TBase> gsi)
+            {
+                StreamRw = streamRw;
+                Gsi = gsi;
+            }
+
+            public Dictionary<long, TBase> Table { get; } = new Dictionary<long, TBase>();
+            public Dictionary<int, IUntypedClassStateProvider<TBase>> ClassStateProviders { get; } = new Dictionary<int, IUntypedClassStateProvider<TBase>>();
+
+            public StreamReaderWriter StreamRw { get; }
+            public GraphStreamSerialisationInfo<TBase> Gsi { get; }
+
+            public T ReadRef<T>() where T : class, TBase
+            {
+                long id = StreamRw.ReadInt64();
+
+                if (id == 0)
+                {
+                    throw new Exception("Read invalid ID of 0");
+                }
+
+                if (id == -1)
+                {
+                    return null;
+                }
+
+                if (id < 0)
+                {
+                    id = ~id;
+
+                    int typeId = StreamRw.ReadInt32();
+
+                    if (typeId < 0)
+                    {
+                        typeId = ~typeId;
+                        
+                        string ftStr = StreamRw.ReadLongPascalString();
+                        int typeVersion = StreamRw.ReadInt32();
+
+                        char typeNameType = ftStr[0];
+                        string typeName = ftStr.Substring(1);
+
+                        switch (typeNameType)
+                        {
+                            case '#':
+                                {
+                                    ClassStateProviders.Add(typeId, AcquireStateProvider<TBase>(AutoSerialisationHelpers.ResolveTypeFromSimpleName(typeName), typeVersion, Gsi));
+                                    break;
+                                }
+                            default:
+                                throw new Exception("Unsupported type name type: '" + typeNameType + "'");
+                        }
+                    
+                        ClassStateProviders[typeId].ReadConfig(this);
+                    }
+
+                    var csp = ClassStateProviders[typeId];
+
+                    TBase state = csp.ReadCreate(this);
+                    Table.Add(id, state);
+                }
+
+                return (T)Table[id];
+            }
+
+            public T ReadRoot<T>() where T : class, TBase
+            {
+                return ReadRoots<T>()[0];
+            }
+
+            public IReadOnlyList<T> ReadRoots<T>() where T : class, TBase
+            {
+                int rootCount = StreamRw.ReadInt32();
+            
+                T[] roots = new T[rootCount];
+
+                // init roots
+                for (int ri = 0; ri < rootCount; ri++)
+                {
+                    roots[ri] = ReadRef<T>();
+                }
+
+                while (true)
+                {
+                    // these have already had their refs read, so we know the typeIds are good, and that the state exists
+                    long id = StreamRw.ReadInt64();
+
+                    // end signal
+                    if (id == -1)
+                        break;
+
+                    TBase state = Table[id];
+                    int typeId = StreamRw.ReadInt32();
+                
+                    var csp = ClassStateProviders[typeId];
+                    csp.ReadState(this, state);
+                }
+
+                return roots;
+            }
+
+            public void Reset(bool resetTypeAssignments)
+            {
+                Table.Clear();
+                if (resetTypeAssignments)
+                    ClassStateProviders.Clear();
+            }
+        }
+
+        private interface IUntypedClassStateProvider<TBase>
+        {
+            void WriteConfig(IWriteGraphStreamContext<TBase> context);
+            void ReadConfig(IReadGraphStreamContext<TBase> context);
+            void WriteState(IWriteGraphStreamContext<TBase> context, TBase state);
+            void ReadState(IReadGraphStreamContext<TBase> context, TBase state);
+            TBase ReadCreate(IReadGraphStreamContext<TBase> context);
+            void WriteCreate(IWriteGraphStreamContext<TBase> context, TBase state);
+            int Version { get; }
+        }
+
+        private class TypedStateProvider<T, TBase> : IUntypedClassStateProvider<TBase> where T : class, TBase
+        {
+            public int Version { get; }
+
+            private readonly IClassStateProvider<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> ClassStateProvider;
+            
+            public TypedStateProvider(IClassStateProviderFactory<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> classStateProviderFactory, int version)
+                : this(classStateProviderFactory.CreateNew(), version)
+            {
+            }
+
+            private TypedStateProvider(IClassStateProvider<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> classStateProvider, int version)
+            {
+                ClassStateProvider = classStateProvider;
+                Version = version;
+            }
+
+            public void ReadConfig(IReadGraphStreamContext<TBase> context)
+            {
+                ClassStateProvider.ReadProviderConfig(context);
+            }
+
+            public void WriteConfig(IWriteGraphStreamContext<TBase> context)
+            {
+                ClassStateProvider.WriteProviderConfig(context);
+            }
+
+            public TBase ReadCreate(IReadGraphStreamContext<TBase> context)
+            {
+                return ClassStateProvider.ReadCreate(context);
+            }
+
+            public void WriteCreate(IWriteGraphStreamContext<TBase> context, TBase state)
+            {
+                ClassStateProvider.WriteCreate(context, (T)state);
+            }
+
+            public void ReadState(IReadGraphStreamContext<TBase> context, TBase state)
+            {
+                ClassStateProvider.ReadState(context, (T)state);
+            }
+
+            public void WriteState(IWriteGraphStreamContext<TBase> context, TBase state)
+            {
+                ClassStateProvider.WriteState(context, (T)state);
+            }
+        }
+        
+        private static IUntypedClassStateProvider<TBase> AcquireStateProvider<TBase>(Type targetType, int version, GraphStreamSerialisationInfo<TBase> gsi) where TBase : class
+        {
+            return AutoSerialisationHelpers.CallNamedStatic<IUntypedClassStateProvider<TBase>>(
+                typeof(AutoGraphSerialisation),
+                null,
+                nameof(InternalAcquireStateProvider),
+                new[] { targetType, typeof(TBase) },
+                version, gsi);
+        }
+
+        private static IUntypedClassStateProvider<TBase> InternalAcquireStateProvider<T, TBase>(int version, GraphStreamSerialisationInfo<TBase> gsi) where TBase : class where T : class, TBase
+        {
+            // check the gsi custom table
+            if (gsi != null && gsi.CustomClassStateProviderTable != null)
+            {
+                if (gsi.CustomClassStateProviderTable.TryResolve<T>(ref version) is IClassStateProviderFactory<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> found)
+                {
+                    var csp = found;
+                    return new TypedStateProvider<T, TBase>(csp, version);
+                }
+            }
+            
+            // check the gsi custom providers
+            if (gsi != null && gsi.CustomClassStateProviderProviders != null)
+            {
+                if (gsi.CustomClassStateProviderProviders.TryResolve<T>(ref version) is IClassStateProviderFactory<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>> found)
+                {
+                    var csp = found;
+                    return new TypedStateProvider<T, TBase>(csp, version);
+                }
+            }
+
+            // try a custom attribute
+            var uccspa = typeof(T).GetCustomAttributes().FirstOrDefault(ca => ca is ICustomClassStateProviderAttribute);
+            if (uccspa is ICustomClassStateProviderAttribute ccspa)
+            {
+                var csp = ccspa.Prepare<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>>();
+                return new TypedStateProvider<T, TBase>(csp, ccspa.Version);
+            }
+
+            if (gsi == null || gsi.FallbackToAutoClassStateProvider)
+            {
+                // fall back to an Automatically constructed one   
+                return AutoSerialisationHelpers.CallNamedStatic<IUntypedClassStateProvider<TBase>>(
+                    typeof(AutoGraphSerialisation),
+                    null,
+                    nameof(InternalAcquireAutoClassStateProvider),
+                    new[] { typeof(T), typeof(TBase) },
+                    version);
+            }
+
+            throw new Exception("Unable to acquire a state provider for type " + typeof(T).FullName);
+        }
+        
+        private static IUntypedClassStateProvider<TBase> InternalAcquireAutoClassStateProvider<T, TBase>(int version) where T : class, TBase
+        {
+            var csp = new AutoSerialisingClassStateProvider<T, IWriteGraphStreamContext<TBase>, IReadGraphStreamContext<TBase>>(version, null);
+            return new TypedStateProvider<T, TBase>(csp, csp.Version);
+        }
+
+        public static IGraphStreamWriter<TBase> CreateWriter<TBase>(StreamReaderWriter streamRw, GraphStreamSerialisationInfo<TBase> gsi = null) where TBase : class
+        {
+           return new BasicWriteContext<TBase>(streamRw, gsi);
+        }
+
+        public static void WriteGraph<T, TBase>(StreamReaderWriter streamRw, T root, GraphStreamSerialisationInfo<TBase> gsi = null) where T : class, TBase where TBase : class
+        {
+            WriteGraphs<T, TBase>(streamRw, new T[] { root }, gsi);
+        }
+        
+        public static void WriteGraphs<T, TBase>(StreamReaderWriter streamRw, T[] roots, GraphStreamSerialisationInfo<TBase> gsi = null) where T : class, TBase where TBase : class
+        {   
+            BasicWriteContext<TBase> context = new BasicWriteContext<TBase>(streamRw, gsi);
+            context.WriteRoots<T>(roots);
+        }
+        
+        public static IGraphStreamReader<TBase> CreateReader<TBase>(StreamReaderWriter streamRw, GraphStreamSerialisationInfo<TBase> gsi = null) where TBase : class
+        {
+           return new BasicReadContext<TBase>(streamRw, gsi);
+        }
+
+        public static T ReadGraph<T, TBase>(StreamReaderWriter streamRw, GraphStreamSerialisationInfo<TBase> gsi = null) where T : class, TBase where TBase : class
+        {
+            return ReadGraphs<T, TBase>(streamRw, gsi)[0];
+        }
+        
+        public static IReadOnlyList<T> ReadGraphs<T, TBase>(StreamReaderWriter streamRw, GraphStreamSerialisationInfo<TBase> gsi = null) where T : class, TBase where TBase : class
+        {
+            BasicReadContext<TBase> context = new BasicReadContext<TBase>(streamRw, gsi);
+            return context.ReadRoots<T>();
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/AutoState/AutoSerialisation.cs b/M4MCode/NetState/NetState/AutoState/AutoSerialisation.cs
new file mode 100644
index 0000000..f102067
--- /dev/null
+++ b/M4MCode/NetState/NetState/AutoState/AutoSerialisation.cs
@@ -0,0 +1,489 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using NetState.Serialisation;
+using System.Collections.Concurrent;
+
+namespace NetState.AutoState
+{
+    public delegate PT Getter<OT, PT>(OT owner);
+    public delegate void Setter<OT, PT>(OT owner, PT value);
+    
+    public static class AutoSerialisationHelpers
+    {
+        private static readonly ConcurrentDictionary<Type, object> StateProviderCache = new ConcurrentDictionary<Type, object>();
+
+        public static IEnumerable<PropertyInfo> EnumerateAllProperties<T>()
+        {
+            return EnumerateAllProperties(typeof(T));
+        }
+
+        public static IEnumerable<PropertyInfo> EnumerateAllProperties(Type type)
+        {
+            if (type == null)
+                return Enumerable.Empty<PropertyInfo>();
+
+            var mine = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
+            return mine.Concat(EnumerateAllProperties(type.BaseType));
+        }
+
+        public static IEnumerable<MethodInfo> EnumerateAllMethods<T>()
+        {
+            return EnumerateAllMethods(typeof(T));
+        }
+
+        public static IEnumerable<MethodInfo> EnumerateAllMethods(Type type)
+        {
+            if (type == null)
+                return Enumerable.Empty<MethodInfo>();
+
+            var mine = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
+            return mine.Concat(EnumerateAllMethods(type.BaseType));
+        }
+
+        public static TCast AcquireInstance<TCast>(Type stateProviderType, bool autoCache = true)
+        {
+            if (!StateProviderCache.TryGetValue(stateProviderType, out var stateProvider))
+            {
+                stateProvider = CreateInstance(stateProviderType);
+
+                if (autoCache)
+                {
+                    StateProviderCache.TryAdd(stateProviderType, stateProvider);
+                }
+            }
+
+            if (stateProvider is TCast yep)
+            {
+                return yep;
+            }
+            else
+            {
+                throw new Exception("Instance acquired from type \"" + stateProviderType.FullName + "\" was not of type \"" + typeof(TCast).FullName + "\"");
+            }
+        }
+
+        private static object CreateInstance(Type stateProviderType)
+        {
+            var instanceField = stateProviderType.GetField("Instance", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
+            if (instanceField != null)
+                return instanceField.GetValue(null);
+
+            var instanceProperty = stateProviderType.GetProperty("Instance", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
+            if (instanceProperty != null)
+                return instanceProperty.GetValue(null);
+
+            return stateProviderType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0].Invoke(new object[0]);
+        }
+        
+        public static RT CallNamedStatic<RT>(Type type, Type[] typeArguments, string methodName, Type[] methodTypeArguments, params object[] methodArguments)
+        {
+            Type typedType = typeArguments != null ? type.MakeGenericType(typeArguments) : type;
+            MethodInfo[] mis = typedType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+            MethodInfo imi = mis.First(mi => mi.Name == methodName);
+            MethodInfo gmi = methodTypeArguments != null ? imi.MakeGenericMethod(methodTypeArguments) : imi;
+            return (RT)gmi.Invoke(null, methodArguments);
+        }
+        
+        public static string AssembleSimpleTypeName(Type t)
+        {
+            // need FullName, because it qualifies things like mscorlib:List<mydll:MyType>
+            return t.Assembly.GetName().Name + "%" + t.FullName;
+        }
+        
+        private static AssemblyMappings ProvisionalMappings = AssemblyMappings.PrepareDefaults();
+        private static Dictionary<string, Assembly> FoundAssemblies = new Dictionary<string, Assembly>();
+
+        /// <summary>
+        /// When set to true, prevents the loading of assemblies.
+        /// Any attempt to load an assembly which is not already loaded into the current AppDomain will fail with an exception.
+        /// </summary>
+        public static bool DisallowAssemblyLoading { get; set; } = true;
+
+        /// <summary>
+        /// When set to true, prevents the loading of assemblies that do not appear in AllowLoadWhiteList when DisallowAssemblyLoading is false.
+        /// Has no effect if DisallowAssemblyLoading is true.
+        /// </summary>
+        public static bool EnableAllowLoadWhiteList { get; set; } = true;
+
+        /// <summary>
+        /// A set of assembly names that are allowed to be loaded when EnableAllowLoadWhiteList is set to true and DisallowAssemblyLoading is set to false.
+        /// </summary>
+        public static HashSet<string> AllowLoadWhiteList { get; } = new HashSet<string>();
+
+        public static Assembly TryLoadAssembly(string name)
+        {
+            lock (FoundAssemblies)
+            {
+                try
+                {
+                    if (FoundAssemblies.TryGetValue(name, out var found))
+                        return found;
+                    else
+                    {
+                        Assembly a;
+                        if (DisallowAssemblyLoading)
+                        {
+                            a = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(_a => _a.GetName().Name == name);
+                            if (a == null)
+                                throw new Exception("Assembly not already loaded.");
+                        }
+                        else if (!EnableAllowLoadWhiteList || AllowLoadWhiteList.Contains(name))
+                        {
+                            a = Assembly.Load(name);
+                        }
+                        else
+                        {
+                            throw new Exception("Assemly not in whitelist.");
+                        }
+
+                        FoundAssemblies.Add(name, a);
+                        return a;
+                    }
+                }
+                catch (Exception lex)
+                {
+                    foreach (var provisional in ProvisionalMappings[name])
+                    {
+                        try
+                        {
+                            Assembly a;
+                            if (DisallowAssemblyLoading)
+                            {
+                                a = AppDomain.CurrentDomain.GetAssemblies().First(_a => _a.GetName().Name == provisional);
+                                if (a == null)
+                                    throw new Exception("Assembly not already loaded.");
+                            }
+                            else if (!EnableAllowLoadWhiteList || AllowLoadWhiteList.Contains(provisional))
+                            {
+                                a = Assembly.Load(provisional);
+                            }
+                            else
+                            {
+                                throw new Exception("Assemly not in whitelist.");
+                            }
+
+                            FoundAssemblies.Add(name, a);
+                            return a;
+                        }
+                        catch
+                        {
+                        }
+                    }
+
+                    throw new Exception("Unable to load assembly '" + name + "', or any of it's provisional mappings: add more mappings to ProvisionalMappings if necessary. " + lex.Message, lex);
+                }
+            }
+        }
+
+        public static Type ResolveTypeFromSimpleName(string name)
+        {
+            var seperatorIdx = name.IndexOf('%');
+            var assemblyName = name.Substring(0, seperatorIdx);
+            var typeName = name.Substring(seperatorIdx + 1);
+            return ResolveType(assemblyName, typeName);
+        }
+
+        private static Type ResolveType(string assemblyName, string typeName)
+        {
+            var a = TryLoadAssembly(assemblyName);
+            return GetType(a, typeName);
+        }
+
+        private static Type GetType(string fullName)
+        {
+            // type names look like:
+            // TypeName`3[[T1],[T2],[T3]][,,,], assemblyName, other stuff
+            // We deal with finding the assemblyName, before passing to GetType(,)
+
+            // name ends with the last bracket, else just before the first comma
+            var nameEnd = fullName.LastIndexOf(']');
+            if (nameEnd == -1)
+                nameEnd = fullName.IndexOf(',') - 1;
+
+            var start = fullName.IndexOf(',', nameEnd + 1) + 1;
+            var end = fullName.IndexOf(',', start);
+
+            var assemblyName = fullName.Substring(start, end - start).Trim();
+            var assembly = TryLoadAssembly(assemblyName);
+            var typeName = fullName.Substring(0, nameEnd + 1);
+            return GetType(assembly, typeName);
+        }
+
+        private static Type GetType(Assembly assembly, string typeName)
+        {
+            // type names look like:
+            // TypeName`3[[T1],[T2],[T3]][,,,], assemblyName, other stuff
+            // We deal with everything before ", assemblyname"
+
+            // first, check if it is an array
+            int i = typeName.Length - 1;
+            if (typeName[i] == ']')
+            {
+                i--;
+                while (typeName[i] == ',')
+                    i--;
+                if (typeName[i] == '[')
+                {
+                    // we are an array
+                    var rank = typeName.Length - i - 1;
+
+                    var nonArrayTypeName = typeName.Substring(0, i);
+                    var nonArrayType = GetType(assembly, nonArrayTypeName);
+
+                    if (rank == 1)
+                    {
+                        return nonArrayType.MakeArrayType();
+                    }
+                    else
+                    {
+                        return nonArrayType.MakeArrayType(rank);
+                    }
+                }
+            }
+
+            // next, check if it generic
+            var openTypeEnd = typeName.IndexOf('[');
+
+            if (openTypeEnd >= 0)
+            {
+                // generic or array
+                var openTypeName = typeName.Substring(0, openTypeEnd);
+                var openType = assembly.GetType(openTypeName);
+
+                var typeArguments = new List<Type>();
+
+                i = openTypeEnd;
+
+                int depth = 0;
+                int lastStart = -1;
+                while (i < typeName.Length)
+                {
+                    var c = typeName[i];
+                    if (c == '[')
+                    {
+                        if (depth == 1)
+                        {
+                            lastStart = i + 1;
+                        }
+
+                        depth++;
+                    }
+                    else if (c == ']')
+                    {
+                        depth--;
+
+                        if (depth == 1)
+                        {
+                            var subTypeName = typeName.Substring(lastStart, i - lastStart);
+                            typeArguments.Add(GetType(subTypeName));
+                        }
+                    }
+                    i++;
+                }
+
+                return openType.MakeGenericType(typeArguments.ToArray());
+            }
+            else
+            {
+                // standard
+                return assembly.GetType(typeName);
+            }
+        }
+    }
+
+    public class AssemblyMappings
+    {
+        private Dictionary<string, HashSet<string>> Mappings = new Dictionary<string, HashSet<string>>();
+
+        public static AssemblyMappings PrepareDefaults()
+        {
+            AssemblyMappings amaps = new AssemblyMappings();
+            
+            // HACK: this is inherently a bad idea, because stuff might appear in different assemblies, but hopefully it will allow me to focus on real work and not compatibility issues
+            //  -> we should provide some way for CSPs to do something smarter... I don't konw what
+            // NOTE: this only solves the problem of serialising these types themselves... they will still cause problems when used as generic parameters
+            //  -> that issue is addressed by manually parsing the Full typename in the GetType methods
+            amaps.Map("mscorlib", "System.Private.CoreLib"); // framework -> core
+            amaps.Map("System.Private.CoreLib", "mscorlib"); // core -> framework
+
+            return amaps;
+        }
+
+        public void Map(string from, string to)
+        {
+            if (Mappings.TryGetValue(from, out var list))
+            {
+                list.Add(to);
+            }
+            else
+            {
+                Mappings.Add(from, new HashSet<string>() { to });
+            }
+        }
+
+        public bool TryGetMappings(string name, out IEnumerable<string> mappings)
+        {
+            if (Mappings.TryGetValue(name, out var list))
+            {
+                mappings = list;
+                return true;
+            }
+            else
+            {
+                mappings = null;
+                return false;
+            }
+        }
+
+        public IEnumerable<string> this[string name]
+        {
+            get
+            {
+                if (TryGetMappings(name, out var mappings))
+                    return mappings;
+                else
+                    return Enumerable.Empty<string>();
+            }
+        }
+    }
+
+    public interface IStateProviderAttribute
+    {
+        IStateProvider<PT, TSourceContext, TSinkContext> Prepare<PT, TSourceContext, TSinkContext>();
+    }
+    
+    public interface IAutoSerialiseAttribute : IStateProviderAttribute
+    {
+        int Index { get; }
+        int MinVersion { get; }
+        int MaxVersion { get; }
+        object Label { get; }
+    }
+    
+    public class AutoSerialiseAttribute : Attribute, IAutoSerialiseAttribute
+    {
+        public AutoSerialiseAttribute(int index, object label, int minVersion, int maxVersion, params Type[] stateProviderTypes)
+        {
+            Index = index;
+
+            Label = label;
+
+            MinVersion = minVersion;
+            MaxVersion = maxVersion;
+
+            StateProviders = stateProviderTypes.Select(spt => AutoSerialisationHelpers.AcquireInstance<object>(spt)).ToArray();
+        }
+        
+        public AutoSerialiseAttribute(int index, params Type[] stateProviderTypes)
+        {
+            Index = index;
+
+            MinVersion = 0;
+            MaxVersion = 0;
+
+            Label = null;
+
+            StateProviders = stateProviderTypes.Select(spt => AutoSerialisationHelpers.AcquireInstance<object>(spt)).ToArray();
+        }
+        
+        public int Index { get; }
+        private object[] StateProviders { get; }
+
+        public int MinVersion { get; }
+        public int MaxVersion { get; }
+
+        public object Label { get; }
+
+        public virtual IStateProvider<PT, TSourceContext, TSinkContext> Prepare<PT, TSourceContext, TSinkContext>()
+        {
+            foreach (var stateProvider in StateProviders)
+            {
+                if (stateProvider is IStateProvider<PT, TSourceContext, TSinkContext> yep)
+                {
+                    return yep;
+                }
+            }
+            
+            // nothing here is compatible; just return null
+            return null;
+        }
+    }
+    
+    public interface IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>
+    {
+        void Write(OT owner, TWriteContext context);
+        void Read(OT owner, TReadContext context);
+    }
+
+    public class SimplePropertyStateProvider<OT, TWriteContext, TReadContext, PT> : IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>
+    {
+        public SimplePropertyStateProvider(IStateProvider<PT, TWriteContext, TReadContext> provider, Getter<OT, PT> getter, Setter<OT, PT> setter)
+        {
+            Provider = provider;
+            Getter = getter;
+            Setter = setter;
+        }
+
+        public readonly IStateProvider<PT, TWriteContext, TReadContext> Provider;
+        public readonly Setter<OT, PT> Setter;
+        public readonly Getter<OT, PT> Getter;
+
+        public void Read(OT owner, TReadContext context)
+        {
+            Setter(owner, Provider.Read(context));
+        }
+
+        public void Write(OT owner, TWriteContext context)
+        {
+            if (Provider == null)
+            {
+                Console.WriteLine("NP");
+            }
+            if (owner == null)
+            {
+                Console.WriteLine("NO");
+            }
+            if (Getter == null)
+            {
+                Console.WriteLine("NG");
+            }
+
+            Provider.Write(context, Getter(owner));
+        }
+    }
+
+    public static class PropertyStateProvisioning<OT, TWriteContext, TReadContext>
+    {
+        public static IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext> Prepare<PT>(PropertyInfo propertyInfo, IStateProviderAttribute spa, bool throwOnMissingSetter = true)
+        {
+            var setter = (Setter<OT, PT>)propertyInfo.SetMethod?.CreateDelegate(typeof(Setter<OT, PT>));
+            var getter = (Getter<OT, PT>)propertyInfo.GetMethod?.CreateDelegate(typeof(Getter<OT, PT>));
+            var provider = spa.Prepare<PT, TWriteContext, TReadContext>();
+
+            if (throwOnMissingSetter && setter == null)
+            {
+                var eMsg = "No setter for property " + propertyInfo.Name + " on type " + typeof(OT).Name;
+                throw new Exception(eMsg);
+            }
+
+            if (getter == null)
+            {
+                var eMsg = "No getter for property " + propertyInfo.Name + " on type " + typeof(OT).Name;
+                throw new Exception(eMsg);
+            }
+
+            if (provider == null)
+            {
+                var eMsg = "No compatible StateProvider found for property " + propertyInfo.Name + " on type " + typeof(OT).FullName;
+                throw new Exception(eMsg);
+            }
+
+            var spsp = new SimplePropertyStateProvider<OT, TWriteContext, TReadContext, PT>(provider, getter, setter);
+
+            return spsp;
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/AutoState/AutoSerialisingStateProvider.cs b/M4MCode/NetState/NetState/AutoState/AutoSerialisingStateProvider.cs
new file mode 100644
index 0000000..4e0cde5
--- /dev/null
+++ b/M4MCode/NetState/NetState/AutoState/AutoSerialisingStateProvider.cs
@@ -0,0 +1,210 @@
+using NetState.Serialisation;
+using NetState.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace NetState.AutoState
+{
+    public class AutoSerialisationStateProvider<OT, TWriteContext, TReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        private static IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext> Prepare<PT>(System.Reflection.PropertyInfo propertyInfo, IAutoSerialiseAttribute asa)
+        {
+            return PropertyStateProvisioning<OT, TWriteContext, TReadContext>.Prepare<PT>(propertyInfo, asa);
+        }
+
+        private static readonly Lazy<AutoSerialisationStateProvider<OT, TWriteContext, TReadContext>> _instance = new Lazy<AutoSerialisationStateProvider<OT, TWriteContext, TReadContext>>(() => new AutoSerialisationStateProvider<OT, TWriteContext, TReadContext>());
+        public static AutoSerialisationStateProvider<OT, TWriteContext, TReadContext> Instance => _instance.Value;
+
+        private readonly IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>[] UntypedStateProviderProviders;
+        private readonly IAutoSerialiseAttribute[] AutoSerialiseAttributes;
+
+        public int Version { get; }
+        public int MaxVersion => AutoSerialiseAttributes.Max(asa => asa.MaxVersion);
+
+        public AutoSerialisationStateProvider()
+        {
+            Type type = typeof(OT);
+
+            var properties = type.GetProperties();
+            var untypedStateProviderProviders = new List<KeyValuePair<IAutoSerialiseAttribute, IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>>>();
+
+            foreach (var property in properties)
+            {
+                var attributes = property.GetCustomAttributes(true);
+                bool hasAsa = false;
+                bool hasValidAsa = false;
+
+                foreach (var attribute in attributes)
+                {
+                    if (attribute is IAutoSerialiseAttribute asa)
+                    {
+                        hasAsa = true;
+
+                        var idx = asa.Index;
+                        var ussp = AutoSerialisationHelpers.CallNamedStatic<IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>>(
+                            typeof(AutoSerialisationStateProvider<OT, TWriteContext, TReadContext>),
+                            null,
+                            nameof(Prepare),
+                            new[] { property.PropertyType },
+                            property,
+                            asa);
+
+                        if (ussp != null)
+                        {
+                            untypedStateProviderProviders.Add(new KeyValuePair<IAutoSerialiseAttribute, IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>>(asa, ussp));
+                            hasValidAsa = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (hasAsa && !hasValidAsa)
+                {
+                    throw new Exception($"Property {property.Name} on type {type.FullName} has one or more AutoSerialisationAttributes, but none are compatible with WriteContext {typeof(TWriteContext).FullName} and ReadContext {typeof(TReadContext).FullName}");
+                }
+            }
+
+            UntypedStateProviderProviders = untypedStateProviderProviders.Any() ? new IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>[untypedStateProviderProviders.Max(ussp => ussp.Key.Index) + 1] : new IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>[0];
+            AutoSerialiseAttributes = untypedStateProviderProviders.Any() ? new IAutoSerialiseAttribute[untypedStateProviderProviders.Max(ussp => ussp.Key.Index) + 1] : new IAutoSerialiseAttribute[0];
+
+            foreach (var kv in untypedStateProviderProviders)
+            {
+                UntypedStateProviderProviders[kv.Key.Index] = kv.Value;
+                AutoSerialiseAttributes[kv.Key.Index] = kv.Key;
+            }
+
+            Version = -1;
+        }
+
+        public AutoSerialisationStateProvider(int version, Predicate<object> labelPredicate)
+        {
+            AutoSerialiseAttributes = Instance.AutoSerialiseAttributes.Where(
+                x => (version == -1 || (x.MinVersion <= version && x.MaxVersion >= version))
+                && (labelPredicate == null || labelPredicate(x.Label))
+                ).ToArray();
+            UntypedStateProviderProviders = new IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext>[AutoSerialiseAttributes.Length];
+
+            for (int i = 0; i < AutoSerialiseAttributes.Length; i++)
+                UntypedStateProviderProviders[i] = Instance.UntypedStateProviderProviders[AutoSerialiseAttributes[i].Index];
+        }
+
+        public void Write(OT owner, TWriteContext context)
+        {
+            for (int i = 0; i < UntypedStateProviderProviders.Length; i++)
+            {
+                UntypedStateProviderProviders[i].Write(owner, context);
+            }
+        }
+
+        public void Read(OT owner, TReadContext context)
+        {
+            for (int i = 0; i < UntypedStateProviderProviders.Length; i++)
+            {
+                UntypedStateProviderProviders[i].Read(owner, context);
+            }
+        }
+    }
+
+    public class DefaultConstructor<T>
+    {
+        public readonly static Func<T> Constructor = PrepareDefaultConstructor();
+
+        private static Func<T> PrepareDefaultConstructor()
+        {
+            var ctor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
+
+            if (ctor == null)
+                throw new Exception("No default constructor for type " + typeof(T).Name);
+
+            var lambda = System.Linq.Expressions.Expression.Lambda<Func<T>>(
+                System.Linq.Expressions.Expression.New(ctor));
+            var func = lambda.Compile();
+
+            return func;
+        }
+    }
+
+    public class AutoSerialisingClassStateProvider<OT, TWriteContext, TReadContext> : IClassStateProvider<OT, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<OT, TWriteContext, TReadContext, object, object> where TWriteContext : IStreamContext where TReadContext : IStreamContext where OT : class
+    {
+        public static AutoSerialisingClassStateProvider<OT, TWriteContext, TReadContext> Instance => new AutoSerialisingClassStateProvider<OT, TWriteContext, TReadContext>(-1, null);
+        private AutoSerialisationStateProvider<OT, TWriteContext, TReadContext> SerialisationInstance { get; }
+
+        public string UniqueName { get; }
+
+        public Type Type => typeof(OT);
+
+        public int Version { get; }
+        public object Label { get; }
+
+        // force this as soon as we instanciate the class
+        private static Func<OT> Constructor = DefaultConstructor<OT>.Constructor;
+
+        public AutoSerialisingClassStateProvider(int version, object label)
+        {
+            Version = version;
+            Label = label;
+
+            SerialisationInstance = new AutoSerialisationStateProvider<OT, TWriteContext, TReadContext>(version, l => label == null || l == label);
+
+            if (version == -1)
+            {
+                Version = SerialisationInstance.MaxVersion;
+            }
+
+            UniqueName = $"AutoSerialisingStateProvider<{typeof(OT).FullName}, {typeof(TWriteContext).FullName}, {typeof(TReadContext).FullName}>({Version}, {label}>";
+        }
+
+        public IClassStateProvider<OT, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this;
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+
+        // class state provider methods
+        public OT ReadCreate(TReadContext context)
+        {
+            return Constructor();
+        }
+
+        public void ReadState(TReadContext context, OT state)
+        {
+            SerialisationInstance.Read(state, context);
+        }
+
+        public void WriteState(TWriteContext context, OT state)
+        {
+            SerialisationInstance.Write(state, context);
+        }
+
+        public void WriteCreate(TWriteContext context, OT state)
+        {
+            // nix
+        }
+
+        // state provider methods
+        public void Write(TWriteContext context, OT state)
+        {
+            WriteCreate(context, state);
+            WriteState(context, state);
+        }
+
+        public OT Read(TReadContext context)
+        {
+            OT state = ReadCreate(context);
+            ReadState(context, state);
+            return state;
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/AutoState/AutoStateProviders.cs b/M4MCode/NetState/NetState/AutoState/AutoStateProviders.cs
new file mode 100644
index 0000000..558af61
--- /dev/null
+++ b/M4MCode/NetState/NetState/AutoState/AutoStateProviders.cs
@@ -0,0 +1,71 @@
+using NetState.Serialisation;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace NetState.AutoState
+{
+    // NOTE: should use unmanged/enum if I can ever get 7.3 on my work machine
+    #region enum
+    public class AutoEnumStateProvider<T, TStorage, TStorageProvider> where TStorageProvider : IStateProvider<TStorage, IStreamContext, IStreamContext> where T : struct where TStorage : struct
+    {
+        public static readonly EnumStateProvider<T, TStorage> Instance = new EnumStateProvider<T, TStorage>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<TStorage, IStreamContext, IStreamContext>>(typeof(TStorageProvider)));
+    }
+    
+    public class AutoEnumStringTreeStateProvider<T, TStorage, TStorageProvider> where TStorageProvider : IStateProvider<TStorage, IStringTreeContext, IStringTreeContext> where T : struct where TStorage : struct
+    {
+        public static readonly EnumStringTreeStateProvider<T, TStorage> Instance = new EnumStringTreeStateProvider<T, TStorage>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<TStorage, IStringTreeContext, IStringTreeContext>>(typeof(TStorageProvider)));
+    }
+    #endregion enum
+
+    #region array
+    public class AutoArrayStateProvider<T, TElementProvider> where TElementProvider : IStateProvider<T, IStreamContext, IStreamContext>
+    {
+        public static readonly ArrayStateProvider<T, IStreamContext, IStreamContext> Instance = new ArrayStateProvider<T, IStreamContext, IStreamContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, IStreamContext, IStreamContext>>(typeof(TElementProvider)));
+    }
+
+    public class AutoArrayStateProvider<T, TElementProvider, TWriteContext, TReadContext> where TElementProvider : IStateProvider<T, TWriteContext, TReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        public static readonly ArrayStateProvider<T, TWriteContext, TReadContext> Instance = new ArrayStateProvider<T, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, TWriteContext, TReadContext>>(typeof(TElementProvider)));
+    }
+
+    public class AutoArrayStringTreeStateProvider<T, TElementProvider, TWriteContext, TReadContext> where TElementProvider : IStateProvider<T, TWriteContext, TReadContext> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext>
+    {
+        public static readonly ArrayStringTreeStateProvider<T, TWriteContext, TReadContext> Instance = new ArrayStringTreeStateProvider<T, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, TWriteContext, TReadContext>>(typeof(TElementProvider)));
+    }
+    #endregion array
+
+    #region list
+    public class AutoListStateProvider<T, TElementProvider> where TElementProvider : IStateProvider<T, IStreamContext, IStreamContext>
+    {
+        public static readonly ListStateProvider<T, IStreamContext, IStreamContext> Instance = new ListStateProvider<T, IStreamContext, IStreamContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, IStreamContext, IStreamContext>>(typeof(TElementProvider)));
+    }
+
+    public class AutoListStateProvider<T, TElementProvider, TWriteContext, TReadContext> where TElementProvider : IStateProvider<T, TWriteContext, TReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        public static readonly ListStateProvider<T, TWriteContext, TReadContext> Instance = new ListStateProvider<T, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, TWriteContext, TReadContext>>(typeof(TElementProvider)));
+    }
+
+    public class AutoListStringTreeStateProvider<T, TElementProvider, TWriteContext, TReadContext> where TElementProvider : IStateProvider<T, TWriteContext, TReadContext> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext>
+    {
+        public static readonly ListStringTreeStateProvider<T, TWriteContext, TReadContext> Instance = new ListStringTreeStateProvider<T, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<T, TWriteContext, TReadContext>>(typeof(TElementProvider)));
+    }
+    #endregion list
+    
+    #region dictionary
+    public class AutoDictionaryStateProvider<TKey, TValue, TKeyElementProvider, TValueElementProvider> where TKeyElementProvider : IStateProvider<TKey, IStreamContext, IStreamContext> where TValueElementProvider : IStateProvider<TValue, IStreamContext, IStreamContext>
+    {
+        public static readonly DictionaryStateProvider<TKey, TValue, IStreamContext, IStreamContext> Instance = new DictionaryStateProvider<TKey, TValue, IStreamContext, IStreamContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<TKey, IStreamContext, IStreamContext>>(typeof(TKeyElementProvider)), AutoSerialisationHelpers.AcquireInstance<IStateProvider<TValue, IStreamContext, IStreamContext>>(typeof(TValueElementProvider)));
+    }
+
+    public class AutoDictionaryStateProvider<TKey, TValue, TKeyElementProvider, TValueElementProvider, TWriteContext, TReadContext> where TKeyElementProvider : IStateProvider<TKey, TWriteContext, TReadContext> where TValueElementProvider : IStateProvider<TValue, TWriteContext, TReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        public static readonly DictionaryStateProvider<TKey, TValue, TWriteContext, TReadContext> Instance = new DictionaryStateProvider<TKey, TValue, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<TKey, TWriteContext, TReadContext>>(typeof(TKeyElementProvider)), AutoSerialisationHelpers.AcquireInstance<IStateProvider<TValue, TWriteContext, TReadContext>>(typeof(TValueElementProvider)));
+    }
+    
+    public class AutoDictionaryStringTreeStateProvider<TKey, TValue, TKeyElementProvider, TValueElementProvider, TWriteContext, TReadContext> where TKeyElementProvider : IStateProvider<TKey, TWriteContext, TReadContext> where TValueElementProvider : IStateProvider<TValue, TWriteContext, TReadContext> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext>
+    {
+        public static readonly DictionaryStringTreeStateProvider<TKey, TValue, TWriteContext, TReadContext> Instance = new DictionaryStringTreeStateProvider<TKey, TValue, TWriteContext, TReadContext>(AutoSerialisationHelpers.AcquireInstance<IStateProvider<TKey, TWriteContext, TReadContext>>(typeof(TKeyElementProvider)), AutoSerialisationHelpers.AcquireInstance<IStateProvider<TValue, TWriteContext, TReadContext>>(typeof(TValueElementProvider)));
+    }
+    #endregion dictionary
+}
diff --git a/M4MCode/NetState/NetState/Memory/Buffer.cs b/M4MCode/NetState/NetState/Memory/Buffer.cs
new file mode 100644
index 0000000..23e096f
--- /dev/null
+++ b/M4MCode/NetState/NetState/Memory/Buffer.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NetState.Memory
+{
+    public interface IBufferView
+    {
+        Task WriteToAsync(Stream targetStream);
+        int Length { get; }
+    }
+
+    public class CompositeBufferView : IBufferView
+    {
+        public CompositeBufferView(IBufferView[] bufferViews)
+        {
+            BufferViews = bufferViews;
+        }
+
+        public IBufferView[] BufferViews { get; }
+
+        public int Length => BufferViews.Sum(bv => bv.Length);
+
+        public async Task WriteToAsync(Stream targetStream)
+        {
+            for (int i = 0; i < BufferViews.Length; i++)
+                await BufferViews[i].WriteToAsync(targetStream);
+        }
+    }
+
+    public class ByteArrayBufferView : IBufferView
+    {
+        public ByteArrayBufferView(int length, int offset, byte[] buffer)
+        {
+            Length = length;
+            Offset = offset;
+            Buffer = buffer;
+        }
+
+        public int Length { get; }
+        private int Offset { get; }
+        private byte[] Buffer { get; }
+
+        public async Task WriteToAsync(Stream targetStream)
+        {
+            await targetStream.WriteAsync(Buffer, Offset, Length).ConfigureAwait(false);
+        }
+    }
+
+    public class SoloMemoryStreamBufferView : IBufferView
+    {
+        public SoloMemoryStreamBufferView(MemoryStream memoryStream, int offset)
+        {
+            MemoryStream = memoryStream;
+            Offset = offset;
+        }
+        
+        public SoloMemoryStreamBufferView(MemoryStream memoryStream)
+        {
+            MemoryStream = memoryStream;
+            Offset = (int)memoryStream.Position;
+        }
+
+        private MemoryStream MemoryStream { get; }
+        public int Length => (int)MemoryStream.Length - Offset;
+        private int Offset { get; }
+
+        public async Task WriteToAsync(Stream targetStream)
+        {
+            MemoryStream.Position = Offset;
+            await MemoryStream.CopyToAsync(targetStream).ConfigureAwait(false);
+        }
+    }
+
+    /// <summary>
+    /// A simple buffer
+    /// </summary>
+    public class LightBuffer
+    {
+        private static readonly byte[] EmptyBuffer = new byte[0];
+
+        private class View : IBufferView
+        {
+            public View(LightBuffer buffer, int offset, int length)
+            {
+                Buffer = buffer;
+                Offset = offset;
+                Length = length;
+            }
+
+            private LightBuffer Buffer { get; }
+            private int Offset { get; }
+            public int Length { get; }
+
+            public async Task WriteToAsync(Stream targetStream)
+            {
+                await targetStream.WriteAsync(Buffer.Barr, Offset, Length).ConfigureAwait(false);
+            }
+        }
+
+        private byte[] Barr;
+        private int _Position;
+        
+        public LightBuffer()
+        {
+            Barr = EmptyBuffer;
+            Length = 0;
+            Position = 0;
+        }
+
+        public int Length { get; private set; }
+
+        public int Position
+        {
+            get => _Position;
+            set
+            {
+                if (value < 0 || value > Length)
+                    throw new ArgumentException("Value must be non-negeative and no more than the length of the buffer", nameof(value));
+
+                _Position = value;
+            }
+        }
+
+        private void Allocate(int capacity)
+        {
+            if (Barr.Length < capacity)
+            {
+                var newBuffer = new byte[Math.Max(Barr.Length * 2, capacity)];
+                System.Buffer.BlockCopy(Barr, 0, newBuffer, 0, Length);
+                Barr = newBuffer;
+
+                Length = capacity;
+            }
+        }
+
+        public async Task WriteAsync(Stream sourceStream, int count)
+        {
+            Allocate(Position + count);
+            await sourceStream.ReadAsync(Barr, Position, count).ConfigureAwait(false);
+        }
+
+        public void Write(byte[] sourceBarr, int offset, int count)
+        {
+            Allocate(Position + count);
+            System.Buffer.BlockCopy(sourceBarr, offset, Barr, Position, count);
+        }
+
+        public void Clear()
+        {
+            Length = 0;
+            Position = 0;
+        }
+
+        public int Capacity => Barr.Length;
+        
+        public IBufferView PrepareFixedView(int offset, int length)
+        {
+            if (offset < 0 || offset > Length)
+                throw new ArgumentException("offset must be non-negeative and no more than the length of the buffer", nameof(offset));
+            if (length < 0 || offset + length > Length)
+                throw new ArgumentException("length must be non-negeative and not extend beyond the length of the buffer", nameof(length));
+
+            return new View(this, offset, length);
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/Memory/MemoryPool.cs b/M4MCode/NetState/NetState/Memory/MemoryPool.cs
new file mode 100644
index 0000000..4eee8a3
--- /dev/null
+++ b/M4MCode/NetState/NetState/Memory/MemoryPool.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NetState.Memory
+{
+    public interface IPooledLightBuffer
+    {
+        LightBuffer LightBuffer { get; }
+        void Free();
+    }
+
+    public class LightBufferPool
+    {
+        private ConcurrentStack<PooledLightbuffer> PooledBuffers = new ConcurrentStack<PooledLightbuffer>();
+
+        private class PooledLightbuffer : IPooledLightBuffer
+        {
+            public PooledLightbuffer(LightBufferPool pool)
+            {
+                Pool = pool;
+            }
+
+            public bool Taken { get; private set; }
+            private LightBufferPool Pool { get; }
+            public LightBuffer LightBuffer { get; } = new LightBuffer();
+
+            public void Take()
+            {
+                System.Diagnostics.Debug.Assert(!Taken);
+
+                Taken = true;
+            }
+
+            public void Free()
+            {
+                System.Diagnostics.Debug.Assert(Taken);
+
+                Pool.Free(this);
+                Taken = false;
+
+                LightBuffer.Clear();
+            }
+        }
+
+        public IPooledLightBuffer Take()
+        {
+            if (PooledBuffers.TryPop(out var plb))
+            {
+                plb.Take();
+                return plb;
+            }
+            else
+            {
+                plb = new PooledLightbuffer(this);
+                plb.Take();
+                return plb;
+            }
+        }
+
+        private void Free(PooledLightbuffer plb)
+        {
+            PooledBuffers.Push(plb);
+        }
+    }
+
+    public interface IPooledMemoryStream
+    {
+        MemoryStream MemoryStream { get; }
+        void Free();
+        IBufferView PrepareFixedView();
+        IBufferView PrepareFixedView(int offset, int length);
+    }
+
+    public class MemoryStreamPool
+    {
+        private ConcurrentStack<PooledMemoryStream> PooledStreams = new ConcurrentStack<PooledMemoryStream>();
+
+        private class PooledMemoryStream : IPooledMemoryStream
+        {
+            private struct FixedView : IBufferView
+            {
+                public FixedView(PooledMemoryStream pooledMemoryStream, int offset, int length) : this()
+                {
+                    PooledMemoryStream = pooledMemoryStream;
+                    Offset = offset;
+                    Length = length;
+
+                    // we know this is a nice (0-offset) stream, because PooledMemoryStream made it
+                    
+                    Buffer = PooledMemoryStream.MemoryStream.GetBuffer();
+                }
+
+                private PooledMemoryStream PooledMemoryStream { get; }
+                public int Offset { get; }
+                public int Length { get; }
+                
+                private byte[] Buffer { get; }
+                
+                public async Task WriteToAsync(Stream targetStream)
+                {
+                    await targetStream.WriteAsync(Buffer, Offset, Length).ConfigureAwait(false);
+                }
+            }
+
+            public PooledMemoryStream(MemoryStreamPool pool)
+            {
+                Pool = pool;
+            }
+
+            public bool Taken { get; private set; }
+            private MemoryStreamPool Pool { get; }
+            public MemoryStream MemoryStream { get; } = new MemoryStream();
+
+            public void Take()
+            {
+                System.Diagnostics.Debug.Assert(!Taken);
+
+                Taken = true;
+            }
+
+            public void Free()
+            {
+                System.Diagnostics.Debug.Assert(Taken);
+
+                Pool.Free(this);
+                Taken = false;
+
+                MemoryStream.SetLength(0);
+            }
+
+            public IBufferView PrepareFixedView()
+            {
+                return PrepareFixedView(0, (int)MemoryStream.Length);
+            }
+
+            public IBufferView PrepareFixedView(int offset, int length)
+            {
+                if (offset < 0 || offset > MemoryStream.Length)
+                    throw new ArgumentException("offset must be non-negative and no more than the length of the buffer", nameof(offset));
+                if (length < 0 || offset + length > MemoryStream.Length)
+                    throw new ArgumentException("length must be non-negative and must not extend beyond the length of the buffer", nameof(length));
+
+                return new FixedView(this, offset, length);
+            }
+        }
+
+        public IPooledMemoryStream Take()
+        {
+            if (PooledStreams.TryPop(out var pms))
+            {
+                pms.Take();
+                return pms;
+            }
+            else
+            {
+                pms = new PooledMemoryStream(this);
+                pms.Take();
+                return pms;
+            }
+        }
+
+        private void Free(PooledMemoryStream pms)
+        {
+            PooledStreams.Push(pms);
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/NetState.csproj b/M4MCode/NetState/NetState/NetState.csproj
new file mode 100644
index 0000000..61b5f43
--- /dev/null
+++ b/M4MCode/NetState/NetState/NetState.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+    <Description />
+    <PackageReleaseNotes>Do not use this software.</PackageReleaseNotes>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
+    <LangVersion>7.1</LangVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
+    <LangVersion>7.1</LangVersion>
+  </PropertyGroup>
+
+</Project>
diff --git a/M4MCode/NetState/NetState/Serialisation/SerialisationUtils.cs b/M4MCode/NetState/NetState/Serialisation/SerialisationUtils.cs
new file mode 100644
index 0000000..b6286ef
--- /dev/null
+++ b/M4MCode/NetState/NetState/Serialisation/SerialisationUtils.cs
@@ -0,0 +1,834 @@
+using System;
+using System.IO;
+
+namespace NetState.Serialisation
+{
+    public enum SizeClass
+    {
+        /// <summary>
+        /// 32bit
+        /// </summary>
+        Normal = 0,
+        /// <summary>
+        /// 16bit
+        /// </summary>
+        Short = 1,
+        /// <summary>
+        /// 8bit
+        /// </summary>
+        VeryShort = 2
+    }
+
+    /// <summary>
+    /// Utility methods to write and read various things to and from streams in BigEndian
+    /// No complexity here
+    /// </summary>
+    public class Utils
+    {
+        public static readonly byte[] EmptyBarr = new byte[0];
+
+        public static void EndianFlip(byte[] barr)
+        {
+            if (System.BitConverter.IsLittleEndian)
+            {
+                Array.Reverse(barr); // this has some horrid fast-path, I understand, which should be faster than anything in readable C#
+            }
+        }
+
+        //
+        // Read
+        //
+
+        public static byte ReadByte(Stream stream)
+        {
+            return (byte)stream.ReadByte();
+        }
+
+        public static bool ReadBool(Stream stream)
+        {
+            return stream.ReadByte() > 0 ? true : false;
+        }
+
+        // TODO: check this works, and write the 3 other associates
+        // note that you probably never want to use this, unless you expect lots of small (but with the odd long) ints
+        public static int ReadCroppedUInt(Stream stream)
+        {
+            int b1 = stream.ReadByte();
+            if ((b1 & 0b10000000) == 0) // first bit indicates 7bit or not
+            {
+                // 8
+                return b1;
+            }
+            else if ((b1 & 0b01000000) == 0) // second bit indicates 30bit or 14bit
+            {
+                // 16
+                b1 = b1 & 0b00111111;
+                int b2 = stream.ReadByte();
+                return (int)b1 << 8 + b2;
+            }
+            else
+            {
+                // 32
+                b1 = b1 & 0b00111111;
+                int b2 = stream.ReadByte();
+                int b3 = stream.ReadByte();
+                int b4 = stream.ReadByte();
+                return (int)b1 << 24 + b2 << 16 + b3 << 8 + b4;
+            }
+        }
+
+        public static int ReadSizeClass(Stream stream, SizeClass sizeClass)
+        {
+            switch (sizeClass)
+            {
+                case SizeClass.Normal:
+                    return ReadInt32(stream);
+                case SizeClass.Short:
+                    return ReadInt16(stream);
+                case SizeClass.VeryShort:
+                    return ReadByte(stream);
+                default:
+                    throw new Exception("Unsupported SizeClass: " + sizeClass.ToString());
+            }
+        }
+
+        public static short ReadInt16(Stream stream)
+        {
+            return System.BitConverter.ToInt16(ReadEndianFlipBytes(stream, 2), 0);
+        }
+
+        public static ushort ReadUInt16(Stream stream)
+        {
+            return System.BitConverter.ToUInt16(ReadEndianFlipBytes(stream, 2), 0);
+        }
+
+        public static int ReadInt32(Stream stream)
+        {
+            return System.BitConverter.ToInt32(ReadEndianFlipBytes(stream, 4), 0);
+        }
+
+        public static uint ReadUInt32(Stream stream)
+        {
+            return System.BitConverter.ToUInt32(ReadEndianFlipBytes(stream, 4), 0);
+        }
+
+        public static long ReadInt64(Stream stream)
+        {
+            return System.BitConverter.ToInt64(ReadEndianFlipBytes(stream, 8), 0);
+        }
+
+        public static ulong ReadUInt64(Stream stream)
+        {
+            return System.BitConverter.ToUInt64(ReadEndianFlipBytes(stream, 8), 0);
+        }
+
+        public static float ReadFloat32(Stream stream)
+        {
+            return System.BitConverter.ToSingle(ReadEndianFlipBytes(stream, 4), 0);
+        }
+
+        public static double ReadFloat64(Stream stream)
+        {
+            return System.BitConverter.ToDouble(ReadEndianFlipBytes(stream, 8), 0);
+        }
+
+        public static DateTime ReadDateTime(Stream stream)
+        {
+            return DateTime.FromBinary(ReadInt64(stream));
+        }
+
+        public static string ReadString(Stream stream, int length)
+        {
+            return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+        }
+
+        public static string ReadPascalString(Stream stream)
+        {
+            int length = Utils.ReadInt32(stream);
+            if (length < 0)
+                return null;
+            else
+                return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+        }
+
+        public static string ReadShortPascalString(Stream stream)
+        {
+            int length = Utils.ReadInt16(stream);
+            if (length < 0)
+                return null;
+            else
+                return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+        }
+
+        public static string ReadVeryShortPascalString(Stream stream)
+        {
+            int length = Utils.ReadByte(stream);
+            if (length == 255)
+                return null;
+            else
+                return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+        }
+
+        public static byte[] ReadBytes(Stream stream, int count)
+        {
+            byte[] barr = new byte[count];
+            stream.Read(barr, 0, count);
+            return barr;
+        }
+
+        public static byte[] ReadPascalBytes(Stream stream)
+        {
+            int length = Utils.ReadInt32(stream);
+            if (length < 0)
+                return null;
+            else
+                return ReadBytes(stream, length);
+        }
+
+        public static byte[] ReadShortPascalBytes(Stream stream)
+        {
+            int length = Utils.ReadInt16(stream);
+            if (length < 0)
+                return null;
+            else
+                return ReadBytes(stream, length);
+        }
+
+        public static byte[] ReadVeryShortPascalBytes(Stream stream)
+        {
+            int length = Utils.ReadByte(stream);
+            if (length == 255)
+                return null;
+            else
+                return ReadBytes(stream, length);
+        }
+
+        public static byte[] ReadEndianFlipBytes(Stream stream, int count)
+        {
+            byte[] barr = new byte[count];
+            stream.Read(barr, 0, count);
+            EndianFlip(barr);
+            return barr;
+        }
+
+        //
+        // Read (with position indicator)
+        //
+
+        public static byte ReadByte(Stream stream, ref int p)
+        {
+            p += 1;
+            return (byte)stream.ReadByte();
+        }
+
+        public static bool ReadBool(Stream stream, ref int p)
+        {
+            p += 1;
+            return stream.ReadByte() > 0 ? true : false;
+        }
+
+        public static int ReadSizeClass(Stream stream, SizeClass sizeClass, ref int p)
+        {
+            switch (sizeClass)
+            {
+                case SizeClass.Normal:
+                    return ReadInt32(stream, ref p);
+                case SizeClass.Short:
+                    return ReadInt16(stream, ref p);
+                case SizeClass.VeryShort:
+                    return ReadByte(stream, ref p);
+                default:
+                    throw new Exception("Unsupported SizeClass: " + sizeClass.ToString());
+            }
+        }
+
+        public static short ReadInt16(Stream stream, ref int p)
+        {
+            p += 2;
+            return System.BitConverter.ToInt16(ReadEndianFlipBytes(stream, 2), 0);
+        }
+
+        public static uint ReadUInt16(Stream stream, ref int p)
+        {
+            p += 2;
+            return System.BitConverter.ToUInt16(ReadEndianFlipBytes(stream, 2), 0);
+        }
+
+        public static int ReadInt32(Stream stream, ref int p)
+        {
+            p += 4;
+            return System.BitConverter.ToInt32(ReadEndianFlipBytes(stream, 4), 0);
+        }
+
+        public static uint ReadUInt32(Stream stream, ref int p)
+        {
+            p += 4;
+            return System.BitConverter.ToUInt32(ReadEndianFlipBytes(stream, 4), 0);
+        }
+
+        public static long ReadInt64(Stream stream, ref int p)
+        {
+            p += 8;
+            return System.BitConverter.ToInt64(ReadEndianFlipBytes(stream, 8), 0);
+        }
+
+        public static ulong ReadUInt64(Stream stream, ref int p)
+        {
+            p += 8;
+            return System.BitConverter.ToUInt64(ReadEndianFlipBytes(stream, 8), 0);
+        }
+
+        public static float ReadFloat32(Stream stream, ref int p)
+        {
+            p += 4;
+            return System.BitConverter.ToSingle(ReadEndianFlipBytes(stream, 4), 0);
+        }
+
+        public static double ReadFloat64(Stream stream, ref int p)
+        {
+            p += 8;
+            return System.BitConverter.ToDouble(ReadEndianFlipBytes(stream, 8), 0);
+        }
+
+        public static DateTime ReadDateTime(Stream stream, ref int p)
+        {
+            p += 8;
+            return DateTime.FromBinary(ReadInt64(stream));
+        }
+
+        public static string ReadString(Stream stream, int length, ref int p)
+        {
+            p += length;
+            return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+        }
+
+        public static string ReadPascalString(Stream stream, ref int p)
+        {
+            int length = Utils.ReadInt32(stream);
+            if (length < 0)
+            {
+                p += 4;
+                return null;
+            }
+            else
+            {
+                p += 4 + length;
+                return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+            }
+        }
+
+        public static string ReadShortPascalString(Stream stream, ref int p)
+        {
+            int length = Utils.ReadInt16(stream);
+            if (length < 0)
+            {
+                p += 2;
+                return null;
+            }
+            else
+            {
+                p += 2 + length;
+                return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+            }
+        }
+
+        public static string ReadVeryShortPascalString(Stream stream, ref int p)
+        {
+            int length = Utils.ReadByte(stream);
+            if (length == 255)
+            {
+                p += 1;
+                return null;
+            }
+            else
+            {
+                p += 1 + length;
+                return System.Text.Encoding.UTF8.GetString(ReadBytes(stream, length));
+            }
+        }
+
+        public static byte[] ReadBytes(Stream stream, int count, ref int p)
+        {
+            p += count;
+            byte[] barr = new byte[count];
+            stream.Read(barr, 0, count);
+            return barr;
+        }
+
+        public static byte[] ReadPascalBytes(Stream stream, ref int p)
+        {
+            int length = Utils.ReadInt32(stream);
+            if (length < 0)
+            {
+                p += 4;
+                return null;
+            }
+            else
+            {
+                p += 4 + length;
+                return ReadBytes(stream, length);
+            }
+        }
+
+        public static byte[] ReadShortPascalBytes(Stream stream, ref int p)
+        {
+            int length = Utils.ReadInt16(stream);
+            if (length < 0)
+            {
+                p += 2;
+                return null;
+            }
+            else
+            {
+                p += 2 + length;
+                return ReadBytes(stream, length);
+            }
+        }
+
+        public static byte[] ReadVeryShortPascalBytes(Stream stream, ref int p)
+        {
+            int length = Utils.ReadByte(stream);
+            if (length == 255)
+            {
+                p += 1;
+                return null;
+            }
+            else
+            {
+                p += 1 + length;
+                return ReadBytes(stream, length);
+            }
+        }
+
+        public static byte[] ReadEndianFlipBytes(Stream stream, int count, ref int p)
+        {
+            p += count;
+            byte[] barr = new byte[count];
+            stream.Read(barr, 0, count);
+            EndianFlip(barr);
+            return barr;
+        }
+
+        //
+        // Write
+        //
+
+        public static void WriteByte(Stream stream, byte v)
+        {
+            stream.WriteByte(v);
+        }
+
+        public static void WriteBool(Stream stream, bool v)
+        {
+            stream.WriteByte(v ? (byte)1 : (byte)0);
+        }
+        
+        public static void WriteSizeClass(Stream stream, SizeClass sizeClass, int size)
+        {
+            switch (sizeClass)
+            {
+                case SizeClass.Normal:
+                    WriteInt32(stream, size);
+                    break;
+                case SizeClass.Short:
+                    WriteInt16(stream, (short)size);
+                    break;
+                case SizeClass.VeryShort:
+                    WriteByte(stream, (byte)size);
+                    break;
+                default:
+                    throw new Exception("Unsupported SizeClass: " + sizeClass.ToString());
+            }
+        }
+
+        public static void WriteInt16(Stream stream, short v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteUInt16(Stream stream, ushort v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteInt32(Stream stream, int v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteUInt32(Stream stream, uint v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteInt64(Stream stream, long v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteUInt64(Stream stream, ulong v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteFloat32(Stream stream, float v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteFloat64(Stream stream, double v)
+        {
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+        
+        public static void WriteDateTime(Stream stream, DateTime v)
+        {
+            WriteInt64(stream, v.ToBinary());
+        }
+        
+        public static void WriteString(Stream stream, string str)
+        {
+            WriteBytes(stream, System.Text.Encoding.UTF8.GetBytes(str));
+        }
+        
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WritePascalString(Stream stream, string str)
+        {
+            if (str == null)
+            {
+                WriteInt32(stream, -1);
+            }
+            else
+            {
+                byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+                WriteInt32(stream, barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WriteShortPascalString(Stream stream, string str)
+        {
+            if (str == null)
+            {
+                WriteInt16(stream, -1);
+            }
+            else
+            {
+                byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+                WriteInt16(stream, (short)barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        /// Supports null (represented as 255 (-1) length string)
+        /// </summary>
+        public static void WriteVeryShortPascalString(Stream stream, string str)
+        {
+            if (str == null)
+            {
+                WriteByte(stream, 255);
+            }
+            else
+            {
+                byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+                WriteByte(stream, (byte)barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+        
+        /// <summary>
+        /// Returns the length of the SizeClass in bytes
+        /// </summary>
+        public static int MeasureSizeClass(SizeClass sizeClass)
+        {
+            switch (sizeClass)
+            {
+                case SizeClass.Normal:
+                    return 4;
+                case SizeClass.Short:
+                    return 2;
+                case SizeClass.VeryShort:
+                    return 1;
+                default:
+                    throw new Exception("Unsupported SizeClass: " + sizeClass.ToString());
+            }
+        }
+
+        /// <summary>
+        /// Includes the 4 bytes for the int
+        /// </summary>
+        public static int MeasurePascalString(string str)
+        {
+            if (str == null)
+                return 4;
+            else
+                return System.Text.Encoding.UTF8.GetByteCount(str) + 4;
+        }
+
+        public static void WriteBytes(Stream stream, byte[] barr)
+        {
+            stream.Write(barr, 0, barr.Length);
+        }
+
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WritePascalBytes(Stream stream, byte[] barr)
+        {
+            if (barr == null)
+            {
+                WriteInt32(stream, -1);
+            }
+            else
+            {
+                WriteInt32(stream, barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WriteShortPascalBytes(Stream stream, byte[] barr)
+        {
+            if (barr == null)
+            {
+                WriteInt16(stream, -1);
+            }
+            else
+            {
+                WriteInt16(stream, (short)barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        /// Supports null (represented as 255 (-1) length string)
+        /// </summary>
+        public static void WriteVeryShortPascalBytes(Stream stream, byte[] barr)
+        {
+            if (barr == null)
+            {
+                WriteByte(stream, 255);
+            }
+            else
+            {
+                WriteByte(stream, (byte)barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        /// Includes the 4 bytes for the int
+        /// </summary>
+        public static int MeasurePascalBytes(byte[] barr)
+        {
+            if (barr == null)
+                return 4;
+            else
+                return barr.Length + 4;
+        }
+
+        /// <summary>
+        ///  modifies your byte[]
+        /// </summary>
+        public static void WriteEndianFlipBytes(Stream stream, byte[] barr)
+        {
+            EndianFlip(barr);
+            stream.Write(barr, 0, barr.Length);
+        }
+
+        //
+        // Write
+        //
+
+        public static void WriteByte(Stream stream, byte v, ref int p)
+        {
+            p += 1;
+            stream.WriteByte(v);
+        }
+
+        public static void WriteBool(Stream stream, bool v, ref int p)
+        {
+            p += 1;
+            stream.WriteByte(v ? (byte)1 : (byte)0);
+        }
+        
+        public static void WriteSizeClass(Stream stream, SizeClass sizeClass, int size, ref int p)
+        {
+            switch (sizeClass)
+            {
+                case SizeClass.Normal:
+                    WriteInt32(stream, size, ref p);
+                    break;
+                case SizeClass.Short:
+                    WriteInt16(stream, (short)size, ref p);
+                    break;
+                case SizeClass.VeryShort:
+                    WriteByte(stream, (byte)size, ref p);
+                    break;
+                default:
+                    throw new Exception("Unsupported SizeClass: " + sizeClass.ToString());
+            }
+        }
+
+        public static void WriteInt16(Stream stream, short v, ref int p)
+        {
+            p += 2;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteUInt16(Stream stream, ushort v, ref int p)
+        {
+            p += 2;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteInt32(Stream stream, int v, ref int p)
+        {
+            p += 4;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteUInt32(Stream stream, uint v, ref int p)
+        {
+            p += 4;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteInt64(Stream stream, long v, ref int p)
+        {
+            p += 8;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteUInt64(Stream stream, ulong v, ref int p)
+        {
+            p += 8;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteFloat32(Stream stream, float v, ref int p)
+        {
+            p += 4;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteFloat64(Stream stream, double v, ref int p)
+        {
+            p += 8;
+            WriteEndianFlipBytes(stream, System.BitConverter.GetBytes(v));
+        }
+
+        public static void WriteDateTime(Stream stream, DateTime v, ref int p)
+        {
+            p += 8;
+            WriteInt64(stream, v.ToBinary());
+        }
+
+        public static void WriteString(Stream stream, string str, ref int p)
+        {
+            byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+            p += barr.Length;
+            WriteBytes(stream, barr);
+        }
+
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WritePascalString(Stream stream, string str, ref int p)
+        {
+            if (str == null)
+            {
+                p += 4;
+                WriteInt32(stream, -1);
+            }
+            else
+            {
+                byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+                WriteInt32(stream, barr.Length);
+                p += barr.Length + 4;
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+        
+
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WriteShortPascalString(Stream stream, string str, ref int p)
+        {
+            if (str == null)
+            {
+                p += 2;
+                WriteInt16(stream, -1);
+            }
+            else
+            {
+                byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+                WriteInt16(stream, (short)barr.Length);
+                p += barr.Length + 2;
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        /// Supports null (represented as 255 (-1) length string)
+        /// </summary>
+        public static void WriteVeryShortPascalString(Stream stream, string str, ref int p)
+        {
+            if (str == null)
+            {
+                p += 1;
+                WriteByte(stream, 255);
+            }
+            else
+            {
+                byte[] barr = System.Text.Encoding.UTF8.GetBytes(str);
+                WriteByte(stream, (byte)barr.Length);
+                p += barr.Length + 1;
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        public static void WriteBytes(Stream stream, byte[] barr, ref int p)
+        {
+            p += barr.Length;
+            stream.Write(barr, 0, barr.Length);
+        }
+
+        /// <summary>
+        /// Supports null (represented as -1 length string)
+        /// </summary>
+        public static void WritePascalBytes(Stream stream, byte[] barr, ref int p)
+        {
+            if (barr == null)
+            {
+                p += 4;
+                WriteInt32(stream, -1);
+            }
+            else
+            {
+                p += 4 + barr.Length;
+                WriteInt32(stream, barr.Length);
+                stream.Write(barr, 0, barr.Length);
+            }
+        }
+
+        /// <summary>
+        ///  modifies your byte[]
+        /// </summary>
+        public static void WriteEndianFlipBytes(Stream stream, byte[] barr, ref int p)
+        {
+            p += barr.Length;
+            EndianFlip(barr);
+            stream.Write(barr, 0, barr.Length);
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/Serialisation/StateProviders.cs b/M4MCode/NetState/NetState/Serialisation/StateProviders.cs
new file mode 100644
index 0000000..fcf922c
--- /dev/null
+++ b/M4MCode/NetState/NetState/Serialisation/StateProviders.cs
@@ -0,0 +1,1217 @@
+using NetState.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NetState.Serialisation
+{
+    // TODO: these should not be here
+    public interface ISimpleContext<TBase>
+    {
+        IStateLookup<TBase> StateLookup { get; }
+    }
+
+    public interface ISimpleStreamContext<TBase> : ISimpleContext<TBase>, IStreamContext
+    {
+    }
+
+    public class SimpleContext<TBase> : ISimpleContext<TBase>
+    {
+        public SimpleContext(IStateLookup<TBase> stateLookup)
+        {
+            StateLookup = stateLookup;
+        }
+
+        public IStateLookup<TBase> StateLookup { get; }
+    }
+
+    public interface IEntity
+    {
+        long Id { get; }
+    }
+
+    public abstract class AEntity : IEntity
+    {
+        public long Id { get; }
+
+        protected AEntity(long id)
+        {
+            Id = id;
+        }
+    }
+
+    public class SimpleSteamContext<TBase> : SimpleContext<TBase>, ISimpleStreamContext<TBase>
+    {
+        public SimpleSteamContext(StreamReaderWriter streamRw, IStateLookup<TBase> stateLookup) : base(stateLookup)
+        {
+            StreamRw = streamRw;
+        }
+
+        public StreamReaderWriter StreamRw { get; }
+    }
+    
+    // (relatively) low-overhead and (hopefully) high(ish) performance context
+    public interface IStreamContext
+    {
+        StreamReaderWriter StreamRw { get; }
+    }
+
+    // more of an interchange context
+    public interface IStringTreeContext
+    {
+        string Name { get; }
+        IStringTreeContext AddChildContext(string name);
+        void SetAttribute(string key, string value);
+        string GetAttribute(string key);
+        IEnumerable<KeyValuePair<string, string>> EnumerateAttributes();
+        IEnumerable<IStringTreeContext> EnumerateChildContexts();
+        IEnumerable<IStringTreeContext> EnumerateChildContexts(string name);
+        /// <summary>
+        /// Throws if no such context exists
+        /// </summary>
+        IStringTreeContext GetChildContext(string name);
+        /// <summary>
+        /// Returns null if no such context exists
+        /// </summary>
+        IStringTreeContext TryGetChildContext(string name);
+    }
+    
+    // more of an interchange context
+    public interface IStringTreeContext<out STC> : IStringTreeContext where STC : IStringTreeContext<STC>
+    {
+        new STC AddChildContext(string name);
+        new IEnumerable<STC> EnumerateChildContexts();
+        new IEnumerable<STC> EnumerateChildContexts(string name);
+        /// <summary>
+        /// Throws if no such context exists
+        /// </summary>
+        new STC GetChildContext(string name);
+        /// <summary>
+        /// Returns null if no such context exists
+        /// </summary>
+        new STC TryGetChildContext(string name);
+    }
+
+    public interface IStreamContext<CX> : IStreamContext
+    {
+        CX Context { get; }
+    }
+
+    // basic providers
+
+    public class StreamContext<CX> : IStreamContext<CX>
+    {
+        public StreamContext(StreamReaderWriter streamRw, CX context)
+        {
+            StreamRw = streamRw;
+            Context = context;
+        }
+
+        public StreamReaderWriter StreamRw { get; }
+        public CX Context { get; }
+    }
+
+    public interface IStateProviderPreparer<in TWriteContext, in TReadContext>
+    {
+        IStateProvider<T, TWriteContext, TReadContext> Prepare<T>();
+    }
+
+    public interface IStateProvider<T, in TWriteContext, in TReadContext> : IStateWriter<T, TWriteContext>, IStateReader<T, TReadContext>
+    {
+        // nix (combination interface)
+    }
+
+    public interface IClassStateProviderFactory<T, in TWriteContext, in TReadContext, in TProviderWriteContext, in TProviderReadContext> where T : class
+    {
+        IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> CreateNew();
+    }
+
+    public class InstanceClassStateProviderFactory<T> : IClassStateProviderFactory<T, object, object, object, object> where T : class
+    {
+        public InstanceClassStateProviderFactory(IClassStateProvider<T, object, object, object, object> instance)
+        {
+            Instance = instance;
+        }
+
+        private IClassStateProvider<T, object, object, object, object> Instance { get; }
+
+        public IClassStateProvider<T, object, object, object, object> CreateNew()
+        {
+            return Instance;
+        }
+    }
+
+    // TODO: should we differentiate between State and Create Contexts? (e.g. Create should not have access to Graphing methods)
+    public interface IClassStateProvider<T, in TWriteContext, in TReadContext, in TProviderWriteContext, in TProviderReadContext> : IStateProvider<T, TWriteContext, TReadContext> where T : class
+    {
+        /// <summary>
+        /// Writes the provider configuration
+        /// </summary>
+        void WriteProviderConfig(TProviderWriteContext context);
+
+        /// <summary>
+        /// Reads the provider configuration
+        /// </summary>
+        void ReadProviderConfig(TProviderReadContext context);
+
+        /// <summary>
+        /// Write the mutable state
+        /// </summary>
+        void WriteState(TWriteContext context, T state);
+
+        /// <summary>
+        /// Read the mutable state
+        /// </summary>
+        void ReadState(TReadContext context, T state);
+
+        /// <summary>
+        /// Construct, and read anything necessary to construct
+        /// </summary>
+        T ReadCreate(TReadContext context);
+
+        /// <summary>
+        /// Write anything necessary to construct
+        /// </summary>
+        /// <param name="context"></param>
+        /// <param name="state"></param>
+        void WriteCreate(TWriteContext context, T state);
+    }
+    
+    public class StringStateProvider
+        : IStateProvider<string, IStreamContext, IStreamContext>, IClassStateProvider<string, IStreamContext, IStreamContext, object, object>, IClassStateProviderFactory<string, IStreamContext, IStreamContext, object, object>
+        , IStateProvider<string, IStringTreeContext, IStringTreeContext>, IClassStateProvider<string, IStringTreeContext, IStringTreeContext, object, object>, IClassStateProviderFactory<string, IStringTreeContext, IStringTreeContext, object, object>
+    {
+        public static readonly StringStateProvider Instance = new StringStateProvider();
+        
+        private StringStateProvider()
+        {
+            // nix
+        }
+        
+#region IStreamContext
+
+        IClassStateProvider<string, IStreamContext, IStreamContext, object, object> IClassStateProviderFactory<string, IStreamContext, IStreamContext, object, object>.CreateNew()
+        {
+            return this;
+        }
+        
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public string ReadCreate(IStreamContext context)
+        {
+            return Read(context);
+        }
+
+        public void ReadState(IStreamContext context, string state)
+        {
+            // nix
+        }
+
+        public void WriteCreate(IStreamContext context, string state)
+        {
+            Write(context, state);
+        }
+
+        public void WriteState(IStreamContext context, string state)
+        {
+            // nix
+        }
+
+        public string Read(IStreamContext context)
+        {
+            return context.StreamRw.ReadLongPascalString();
+        }
+
+        public void Write(IStreamContext context, string state)
+        {
+            context.StreamRw.WriteLongPascalString(state);
+        }
+        
+#endregion IStreamContext
+#region IStringTreeContext
+        
+        IClassStateProvider<string, IStringTreeContext, IStringTreeContext, object, object> IClassStateProviderFactory<string, IStringTreeContext, IStringTreeContext, object, object>.CreateNew()
+        {
+            return this;
+        }
+
+        public string ReadCreate(IStringTreeContext context)
+        {
+            return Read(context);
+        }
+
+        public void ReadState(IStringTreeContext context, string state)
+        {
+            // nix
+        }
+
+        public void WriteCreate(IStringTreeContext context, string state)
+        {
+            Write(context, state);
+        }
+
+        public void WriteState(IStringTreeContext context, string state)
+        {
+            // nix
+        }
+
+        public string Read(IStringTreeContext context)
+        {
+            return context.GetChildContext("String").GetAttribute("value");
+        }
+
+        public void Write(IStringTreeContext context, string state)
+        {
+            var vc = context.AddChildContext("String");
+            vc.SetAttribute("value", state);
+        }
+#endregion IStringTreeContext
+    }
+    
+    public class IntStateProvider
+        : IStateProvider<int, IStreamContext, IStreamContext>
+        , IStateProvider<int, IStringTreeContext, IStringTreeContext>
+    {
+        public static readonly IntStateProvider Instance = new IntStateProvider();
+
+        private IntStateProvider()
+        {
+            // nix
+        }
+
+        public int Read(IStreamContext context)
+        {
+            return context.StreamRw.ReadInt32();
+        }
+
+        public void Write(IStreamContext context, int state)
+        {
+            context.StreamRw.WriteInt32(state);
+        }
+        
+        public int Read(IStringTreeContext context)
+        {
+            return int.Parse(context.GetChildContext("Int").GetAttribute("value"));
+        }
+
+        public void Write(IStringTreeContext context, int state)
+        {
+            var vc = context.AddChildContext("Int");
+            vc.SetAttribute("value", state.ToString());
+        }
+    }
+
+    public class LongStateProvider
+        : IStateProvider<long, IStreamContext, IStreamContext>
+        , IStateProvider<long, IStringTreeContext, IStringTreeContext>
+    {
+        public static readonly LongStateProvider Instance = new LongStateProvider();
+
+        private LongStateProvider()
+        {
+            // nix
+        }
+
+        public long Read(IStreamContext context)
+        {
+            return context.StreamRw.ReadInt64();
+        }
+
+        public void Write(IStreamContext context, long state)
+        {
+            context.StreamRw.WriteInt64(state);
+        }
+        
+        public long Read(IStringTreeContext context)
+        {
+            return long.Parse(context.GetChildContext("Long").GetAttribute("value"));
+        }
+
+        public void Write(IStringTreeContext context, long state)
+        {
+            var vc = context.AddChildContext("Long");
+            vc.SetAttribute("value", state.ToString());
+        }
+    }
+
+    public class ByteStateProvider
+        : IStateProvider<byte, IStreamContext, IStreamContext>
+        , IStateProvider<byte, IStringTreeContext, IStringTreeContext>
+    {
+        public static readonly ByteStateProvider Instance = new ByteStateProvider();
+
+        private ByteStateProvider()
+        {
+            // nix
+        }
+
+        public byte Read(IStreamContext context)
+        {
+            return context.StreamRw.ReadByte();
+        }
+
+        public void Write(IStreamContext context, byte state)
+        {
+            context.StreamRw.WriteByte(state);
+        }
+        
+        public byte Read(IStringTreeContext context)
+        {
+            return byte.Parse(context.GetChildContext("Byte").GetAttribute("value"));
+        }
+
+        public void Write(IStringTreeContext context, byte state)
+        {
+            var vc = context.AddChildContext("Byte");
+            vc.SetAttribute("value", state.ToString());
+        }
+    }
+
+    public class BoolStateProvider
+        : IStateProvider<bool, IStreamContext, IStreamContext>
+        , IStateProvider<bool, IStringTreeContext, IStringTreeContext>
+    {
+        public static readonly BoolStateProvider Instance = new BoolStateProvider();
+
+        private BoolStateProvider()
+        {
+            // nix
+        }
+
+        public bool Read(IStreamContext context)
+        {
+            return context.StreamRw.ReadBool();
+        }
+
+        public void Write(IStreamContext context, bool state)
+        {
+            context.StreamRw.WriteBool(state);
+        }
+        
+        public bool Read(IStringTreeContext context)
+        {
+            return bool.Parse(context.GetChildContext("Bool").GetAttribute("value"));
+        }
+
+        public void Write(IStringTreeContext context, bool state)
+        {
+            var vc = context.AddChildContext("Bool");
+            vc.SetAttribute("value", state.ToString());
+        }
+    }
+
+    public class FloatStateProvider
+        : IStateProvider<float, IStreamContext, IStreamContext>
+        , IStateProvider<float, IStringTreeContext, IStringTreeContext>
+    {
+        public static readonly FloatStateProvider Instance = new FloatStateProvider();
+
+        private FloatStateProvider()
+        {
+            // nix
+        }
+
+        public float Read(IStreamContext context)
+        {
+            return Utils.ReadFloat32(context.StreamRw.Stream);
+        }
+
+        public void Write(IStreamContext context, float state)
+        {
+            Utils.WriteFloat32(context.StreamRw.Stream, state);
+        }
+        
+        public float Read(IStringTreeContext context)
+        {
+            return float.Parse(context.GetChildContext("Float").GetAttribute("value"));
+        }
+
+        public void Write(IStringTreeContext context, float state)
+        {
+            var vc = context.AddChildContext("Float");
+            vc.SetAttribute("value", state.ToString("R"));
+        }
+    }
+
+    public class DoubleStateProvider
+        : IStateProvider<double, IStreamContext, IStreamContext>
+        , IStateProvider<double, IStringTreeContext, IStringTreeContext>
+    {
+        public static readonly DoubleStateProvider Instance = new DoubleStateProvider();
+
+        private DoubleStateProvider()
+        {
+            // nix
+        }
+
+        public double Read(IStreamContext context)
+        {
+            return Utils.ReadFloat64(context.StreamRw.Stream);
+        }
+
+        public void Write(IStreamContext context, double state)
+        {
+            Utils.WriteFloat64(context.StreamRw.Stream, state);
+        }
+        
+        public double Read(IStringTreeContext context)
+        {
+            return double.Parse(context.GetChildContext("Double").GetAttribute("value"));
+        }
+
+        public void Write(IStringTreeContext context, double state)
+        {
+            var vc = context.AddChildContext("Double");
+            vc.SetAttribute("value", state.ToString("R"));
+        }
+    }
+
+    // this is a terrible class
+    public class EnumStateProvider<T, TStorage> : IStateProvider<T, IStreamContext, IStreamContext> where T : struct where TStorage : struct
+    {
+        // derived from https://stackoverflow.com/a/26289874/383598
+        private static class EnumConverter
+        {
+            public static readonly Func<TStorage, T> Convert = PrepareConverter();
+            public static readonly Func<T, TStorage> ConvertBack = PrepareBackConverter();
+
+            static Func<TStorage, T> PrepareConverter()
+            {
+                var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TStorage));
+                var dynamicMethod = System.Linq.Expressions.Expression.Lambda<Func<TStorage, T>>(
+                    System.Linq.Expressions.Expression.Convert(parameter, typeof(T)),
+                    parameter);
+                return dynamicMethod.Compile();
+            }
+
+            static Func<T, TStorage> PrepareBackConverter()
+            {
+                var parameter = System.Linq.Expressions.Expression.Parameter(typeof(T));
+                var dynamicMethod = System.Linq.Expressions.Expression.Lambda<Func<T, TStorage>>(
+                    System.Linq.Expressions.Expression.Convert(parameter, typeof(TStorage)),
+                    parameter);
+                return dynamicMethod.Compile();
+            }
+        }
+
+        public EnumStateProvider(IStateProvider<TStorage, IStreamContext, IStreamContext> stateProvider)
+        {
+            StateProvider = stateProvider;
+        }
+
+        public IStateProvider<TStorage, IStreamContext, IStreamContext> StateProvider { get; }
+
+        public T Read(IStreamContext context)
+        {
+            return EnumConverter.Convert(StateProvider.Read(context));
+        }
+
+        public void Write(IStreamContext context, T state)
+        {
+            StateProvider.Write(context, EnumConverter.ConvertBack(state));
+        }
+    }
+
+    // this is a terrible class
+    public class EnumStringTreeStateProvider<T, TStorage> : IStateProvider<T, IStringTreeContext, IStringTreeContext> where T : struct where TStorage : struct
+    {
+        // derived from https://stackoverflow.com/a/26289874/383598
+        private static class EnumConverter
+        {
+            public static readonly Func<TStorage, T> Convert = PrepareConverter();
+            public static readonly Func<T, TStorage> ConvertBack = PrepareBackConverter();
+
+            static Func<TStorage, T> PrepareConverter()
+            {
+                var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TStorage));
+                var dynamicMethod = System.Linq.Expressions.Expression.Lambda<Func<TStorage, T>>(
+                    System.Linq.Expressions.Expression.Convert(parameter, typeof(T)),
+                    parameter);
+                return dynamicMethod.Compile();
+            }
+
+            static Func<T, TStorage> PrepareBackConverter()
+            {
+                var parameter = System.Linq.Expressions.Expression.Parameter(typeof(T));
+                var dynamicMethod = System.Linq.Expressions.Expression.Lambda<Func<T, TStorage>>(
+                    System.Linq.Expressions.Expression.Convert(parameter, typeof(TStorage)),
+                    parameter);
+                return dynamicMethod.Compile();
+            }
+        }
+
+        public EnumStringTreeStateProvider(IStateProvider<TStorage, IStringTreeContext, IStringTreeContext> stateProvider)
+        {
+            StateProvider = stateProvider;
+        }
+
+        public IStateProvider<TStorage, IStringTreeContext, IStringTreeContext> StateProvider { get; }
+
+        public T Read(IStringTreeContext context)
+        {
+            return EnumConverter.Convert(StateProvider.Read(context));
+        }
+
+        public void Write(IStringTreeContext context, T state)
+        {
+            StateProvider.Write(context, EnumConverter.ConvertBack(state));
+        }
+    }
+    
+    public class ArrayStateProvider<T, TWriteContext, TReadContext> : IClassStateProvider<T[], TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<T[], TWriteContext, TReadContext, object, object> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        public ArrayStateProvider(IStateProvider<T, TWriteContext, TReadContext> elementStateProvider)
+        {
+            ElementStateProvider = elementStateProvider;
+        }
+
+        public IStateProvider<T, TWriteContext, TReadContext> ElementStateProvider { get; }
+
+        public IClassStateProvider<T[], TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this;
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+        
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public T[] ReadCreate(TReadContext context)
+        {
+            int count = context.StreamRw.ReadInt32();
+            
+            T[] arr = new T[count];
+
+            return arr;
+        }
+
+        public void ReadState(TReadContext context, T[] state)
+        {
+            for (int i = 0; i < state.Length; i++)
+            {
+                state[i] = ElementStateProvider.Read(context);
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, T[] state)
+        {
+            int count = state.Length;
+            context.StreamRw.WriteInt32(count);
+        }
+
+        public void WriteState(TWriteContext context, T[] state)
+        {
+            for (int i = 0; i < state.Length; i++)
+            {
+                ElementStateProvider.Write(context, state[i]);
+            }
+        }
+        
+        public T[] Read(TReadContext context)
+        {
+            int count = context.StreamRw.ReadInt32();
+
+            if (count < 0)
+                return null;
+
+            T[] arr = new T[count];
+
+            for (int i = 0; i < count; i++)
+            {
+                arr[i] = ElementStateProvider.Read(context);
+            }
+
+            return arr;
+        }
+
+        public void Write(TWriteContext context, T[] state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt32(-1);
+                return;
+            }
+
+            int count = state.Length;
+            context.StreamRw.WriteInt32(count);
+            
+            for (int i = 0; i < count; i++)
+            {
+                ElementStateProvider.Write(context, state[i]);
+            }
+        }
+    }
+    
+    public class ArrayStringTreeStateProvider<TElement, TWriteContext, TReadContext> : IClassStateProvider<TElement[], TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<TElement[], TWriteContext, TReadContext, object, object> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext>
+    {
+        public ArrayStringTreeStateProvider(IStateProvider<TElement, TWriteContext, TReadContext> elementStateProvider)
+        {
+            ElementStateProvider = elementStateProvider;
+        }
+        
+        public IStateProvider<TElement, TWriteContext, TReadContext> ElementStateProvider { get; }
+        
+        public IClassStateProvider<TElement[], TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this; // we are immutable
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+        
+        public TElement[] ReadCreate(TReadContext context)
+        {
+            var length = IntStateProvider.Instance.Read(context.GetChildContext("Length"));
+            return new TElement[length];
+        }
+
+        public void ReadState(TReadContext context, TElement[] state)
+        {
+            foreach (var entry in context.GetChildContext("Array").EnumerateChildContexts("Entry"))
+            {
+                int index = IntStateProvider.Instance.Read(context.GetChildContext("Index"));
+                TElement elem = ElementStateProvider.Read(context.GetChildContext("Value"));
+
+                state[index] = elem;
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, TElement[] state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, TElement[] state)
+        {
+            context = context.AddChildContext("Array");
+            
+            for (int i = 0; i < state.Length; i++)
+            {
+                var entry = context.AddChildContext("Entry");
+                IntStateProvider.Instance.Write(entry.AddChildContext("Index"), i);
+                ElementStateProvider.Write(entry.AddChildContext("Value"), state[i]);
+            }
+        }
+        
+        public TElement[] Read(TReadContext context)
+        {
+            if (context.TryGetChildContext("Null") != null)
+                return null;
+            
+            TElement[] dict = ReadCreate(context);
+            ReadState(context, dict);
+
+            return dict;
+        }
+
+        public void Write(TWriteContext context, TElement[] state)
+        {
+            if (state == null)
+            {
+                context.AddChildContext("Null");
+            }
+            
+            WriteCreate(context, state);
+            WriteState(context, state);
+        }
+    }
+
+    // TODO: this can be generalised to TList : class, IList<T>, new()
+    // errr.... maybe it can't... since it's an IClassStateProvider
+    public class ListStateProvider<T, TWriteContext, TReadContext> : IClassStateProvider<List<T>, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<List<T>, TWriteContext, TReadContext, object, object> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        public ListStateProvider(IStateProvider<T, TWriteContext, TReadContext> elementStateProvider)
+        {
+            ElementStateProvider = elementStateProvider;
+        }
+
+        public IStateProvider<T, TWriteContext, TReadContext> ElementStateProvider { get; }
+
+        public IClassStateProvider<List<T>, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this; // we are immutable
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public List<T> ReadCreate(TReadContext context)
+        {
+            return new List<T>();
+        }
+
+        public void ReadState(TReadContext context, List<T> state)
+        {
+            int count = context.StreamRw.ReadInt32();
+            
+            for (int i = 0; i < count; i++)
+            {
+                state.Add(ElementStateProvider.Read(context));
+            }
+        }
+
+        public void WriteCreate(TWriteContext context, List<T> state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, List<T> state)
+        {
+            int count = state.Count;
+            context.StreamRw.WriteInt32(count);
+            
+            for (int i = 0; i < count; i++)
+            {
+                ElementStateProvider.Write(context, state[i]);
+            }
+        }
+        
+        public List<T> Read(TReadContext context)
+        {
+            int count = context.StreamRw.ReadInt32();
+
+            if (count < 0)
+                return null;
+
+            List<T> list = new List<T>(count);
+
+            for (int i = 0; i < count; i++)
+            {
+                list.Add(ElementStateProvider.Read(context));
+            }
+
+            return list;
+        }
+        
+        public void Write(TWriteContext context, List<T> state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt32(-1);
+                return;
+            }
+
+            int count = state.Count;
+            context.StreamRw.WriteInt32(count);
+            
+            for (int i = 0; i < count; i++)
+            {
+                ElementStateProvider.Write(context, state[i]);
+            }
+        }
+    }
+    
+    public class ListStringTreeStateProvider<TElement, TWriteContext, TReadContext> : IClassStateProvider<List<TElement>, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<List<TElement>, TWriteContext, TReadContext, object, object> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext>
+    {
+        public ListStringTreeStateProvider(IStateProvider<TElement, TWriteContext, TReadContext> elementStateProvider)
+        {
+            ElementStateProvider = elementStateProvider;
+        }
+        
+        public IStateProvider<TElement, TWriteContext, TReadContext> ElementStateProvider { get; }
+        
+        public IClassStateProvider<List<TElement>, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this; // we are immutable
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+        
+        public List<TElement> ReadCreate(TReadContext context)
+        {
+            return new List<TElement>();
+        }
+
+        public void ReadState(TReadContext context, List<TElement> state)
+        {
+            foreach (var entry in context.GetChildContext("List").EnumerateChildContexts("Entry"))
+            {
+                state.Add(ElementStateProvider.Read(entry));
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, List<TElement> state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, List<TElement> state)
+        {
+            context = context.AddChildContext("List");
+            
+            foreach (var elem in state)
+            {
+                var entry = context.AddChildContext("Entry");
+                ElementStateProvider.Write(entry, elem);
+            }
+        }
+        
+        public List<TElement> Read(TReadContext context)
+        {
+            if (context.TryGetChildContext("Null") != null)
+                return null;
+            
+            List<TElement> list = ReadCreate(context);
+            ReadState(context, list);
+
+            return list;
+        }
+
+        public void Write(TWriteContext context, List<TElement> state)
+        {
+            if (state == null)
+            {
+                context.AddChildContext("Null");
+            }
+            
+            WriteCreate(context, state);
+            WriteState(context, state);
+        }
+    }
+
+    public class DictionaryStateProvider<TKey, TValue, TWriteContext, TReadContext> : IClassStateProvider<Dictionary<TKey, TValue>, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<Dictionary<TKey, TValue>, TWriteContext, TReadContext, object, object> where TWriteContext : IStreamContext where TReadContext : IStreamContext
+    {
+        public DictionaryStateProvider(IStateProvider<TKey, TWriteContext, TReadContext> keyStateProvider, IStateProvider<TValue, TWriteContext, TReadContext> valueStateProvider)
+        {
+            KeyStateProvider = keyStateProvider;
+            ValueStateProvider = valueStateProvider;
+        }
+
+        public IStateProvider<TKey, TWriteContext, TReadContext> KeyStateProvider { get; }
+        public IStateProvider<TValue, TWriteContext, TReadContext> ValueStateProvider { get; }
+        
+        public IClassStateProvider<Dictionary<TKey, TValue>, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this; // we are immutable
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+        
+        public Dictionary<TKey, TValue> ReadCreate(TReadContext context)
+        {
+            return new Dictionary<TKey, TValue>();
+        }
+        public void ReadState(TReadContext context, Dictionary<TKey, TValue> state)
+        {
+            int count = context.StreamRw.ReadInt32();
+            
+            for (int i = 0; i < count; i++)
+            {
+                TKey key = KeyStateProvider.Read(context);
+                TValue value = ValueStateProvider.Read(context);
+
+                state.Add(key, value);
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, Dictionary<TKey, TValue> state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, Dictionary<TKey, TValue> state)
+        {
+            int count = state.Count;
+            context.StreamRw.WriteInt32(count);
+            
+            foreach (var vk in state)
+            {
+                KeyStateProvider.Write(context, vk.Key);
+                ValueStateProvider.Write(context, vk.Value);
+            }
+        }
+        
+        public Dictionary<TKey, TValue> Read(TReadContext context)
+        {
+            int count = context.StreamRw.ReadInt32();
+
+            if (count < 0)
+                return null;
+
+            Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>(count);
+
+            for (int i = 0; i < count; i++)
+            {
+                TKey key = KeyStateProvider.Read(context);
+                TValue value = ValueStateProvider.Read(context);
+
+                dict.Add(key, value);
+            }
+
+            return dict;
+        }
+
+        public void Write(TWriteContext context, Dictionary<TKey, TValue> state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt32(-1);
+                return;
+            }
+
+            int count = state.Count;
+            context.StreamRw.WriteInt32(count);
+            
+            foreach (var vk in state)
+            {
+                KeyStateProvider.Write(context, vk.Key);
+                ValueStateProvider.Write(context, vk.Value);
+            }
+        }
+    }
+
+    public class DictionaryStringTreeStateProvider<TKey, TValue, TWriteContext, TReadContext> : IClassStateProvider<Dictionary<TKey, TValue>, TWriteContext, TReadContext, object, object>, IClassStateProviderFactory<Dictionary<TKey, TValue>, TWriteContext, TReadContext, object, object> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext>
+    {
+        public DictionaryStringTreeStateProvider(IStateProvider<TKey, TWriteContext, TReadContext> keyStateProvider, IStateProvider<TValue, TWriteContext, TReadContext> valueStateProvider)
+        {
+            KeyStateProvider = keyStateProvider;
+            ValueStateProvider = valueStateProvider;
+        }
+
+        public IStateProvider<TKey, TWriteContext, TReadContext> KeyStateProvider { get; }
+        public IStateProvider<TValue, TWriteContext, TReadContext> ValueStateProvider { get; }
+        
+        public IClassStateProvider<Dictionary<TKey, TValue>, TWriteContext, TReadContext, object, object> CreateNew()
+        {
+            return this; // we are immutable
+        }
+
+        public void ReadProviderConfig(object context)
+        {
+            // nix
+        }
+
+        public void WriteProviderConfig(object context)
+        {
+            // nix
+        }
+        
+        public Dictionary<TKey, TValue> ReadCreate(TReadContext context)
+        {
+            return new Dictionary<TKey, TValue>();
+        }
+
+        public void ReadState(TReadContext context, Dictionary<TKey, TValue> state)
+        {
+            foreach (var entry in context.GetChildContext("Dictionary").EnumerateChildContexts("Entry"))
+            {
+                TKey key = KeyStateProvider.Read(context.GetChildContext("Key"));
+                TValue value = ValueStateProvider.Read(context.GetChildContext("Value"));
+
+                state.Add(key, value);
+            }
+        }
+        
+        public void WriteCreate(TWriteContext context, Dictionary<TKey, TValue> state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, Dictionary<TKey, TValue> state)
+        {
+            context = context.AddChildContext("Dictionary");
+            
+            foreach (var vk in state)
+            {
+                var entry = context.AddChildContext("Entry");
+                KeyStateProvider.Write(entry.AddChildContext("Key"), vk.Key);
+                ValueStateProvider.Write(entry.AddChildContext("Value"), vk.Value);
+            }
+        }
+        
+        public Dictionary<TKey, TValue> Read(TReadContext context)
+        {
+            if (context.TryGetChildContext("Null") != null)
+                return null;
+            
+            Dictionary<TKey, TValue> dict = ReadCreate(context);
+            ReadState(context, dict);
+
+            return dict;
+        }
+
+        public void Write(TWriteContext context, Dictionary<TKey, TValue> state)
+        {
+            if (state == null)
+            {
+                context.AddChildContext("Null");
+            }
+            
+            WriteCreate(context, state);
+            WriteState(context, state);
+        }
+    }
+
+    public class TableStateProvider<T, TBase> : IStateProvider<T, ISimpleStreamContext<TBase>, ISimpleStreamContext<TBase>> where T : class, TBase
+    {
+        public static readonly TableStateProvider<T, TBase> Instance = new TableStateProvider<T, TBase>();
+
+        public TableStateProvider()
+        {
+        }
+
+        public T Read(ISimpleStreamContext<TBase> context)
+        {
+            // -1 is null (0 is error)
+            long id = context.StreamRw.ReadInt64();
+
+            if (id == 0)
+                throw new Exception("Id read as 0");
+
+            if (id == -1)
+                return null;
+
+            return (T)context.StateLookup.Lookup(id);
+        }
+
+        public void Write(ISimpleStreamContext<TBase> context, T state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt64(-1);
+            }
+            else
+            {
+                context.StreamRw.WriteInt64(context.StateLookup.LookupId(state));
+            }
+        }
+    }
+    
+    public class EntityStateProvider<T, TBase> : IStateProvider<T, ISimpleStreamContext<TBase>, ISimpleStreamContext<TBase>> where T : class, TBase, IEntity
+    {
+        public static readonly EntityStateProvider<T, TBase> Instance = new EntityStateProvider<T, TBase>();
+
+        public EntityStateProvider()
+        {
+        }
+        
+        public T Read(ISimpleStreamContext<TBase> context)
+        {
+            // -1 is null (0 is error)
+            long id = context.StreamRw.ReadInt64();
+
+            if (id == 0)
+                throw new Exception("Id read as 0");
+
+            if (id == -1)
+                return null;
+
+            return (T)context.StateLookup.Lookup(id);
+        }
+
+        public void Write(ISimpleStreamContext<TBase> context, T state)
+        {
+            if (state == null)
+            {
+                context.StreamRw.WriteInt64(-1);
+            }
+            else
+            {
+                context.StreamRw.WriteInt64(state.Id);
+            }
+        }
+    }
+
+    //public class BoxClassStateProvider<T, TProvider, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderWriteContext>, IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderWriteContext> where TProvider : IStateProvider<T, TWriteContext, TReadContext> where T : class
+    //{
+    //    public static readonly BoxClassStateProvider<T, TProvider, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> Instance = new BoxClassStateProvider<T, TProvider, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+
+    //    private readonly IStateProvider<T, TWriteContext, TReadContext> StateProvider;
+
+    //    public BoxClassStateProvider(IStateProvider<T, TWriteContext, TReadContext> stateProvider)
+    //    {
+    //        StateProvider = stateProvider;
+    //    }
+
+    //    public IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderWriteContext> CreateNew()
+    //    {
+    //        return this;
+    //    }
+
+    //    public T Read(TReadContext context)
+    //    {
+    //        return ReadCreate(context);
+    //    }
+
+    //    public T ReadCreate(TReadContext context)
+    //    {
+    //        return StateProvider.Read(context);
+    //    }
+
+    //    public void ReadProviderConfig(TProviderWriteContext context)
+    //    {
+    //        // nix
+    //    }
+
+    //    public void ReadState(TReadContext context, T state)
+    //    {
+    //        // nix
+    //    }
+
+    //    public void Write(TWriteContext context, T state)
+    //    {
+    //        WriteCreate(context, state);
+    //    }
+
+    //    public void WriteCreate(TWriteContext context, T state)
+    //    {
+    //        StateProvider.Write(context, state);
+    //    }
+
+    //    public void WriteProviderConfig(TProviderWriteContext context)
+    //    {
+    //        // nix
+    //    }
+
+    //    public void WriteState(TWriteContext context, T state)
+    //    {
+    //        // nix
+    //    }
+    //}
+}
diff --git a/M4MCode/NetState/NetState/Serialisation/StreamReaderWriter.cs b/M4MCode/NetState/NetState/Serialisation/StreamReaderWriter.cs
new file mode 100644
index 0000000..1d51039
--- /dev/null
+++ b/M4MCode/NetState/NetState/Serialisation/StreamReaderWriter.cs
@@ -0,0 +1,1046 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace NetState.Serialisation
+{
+    public interface IStream
+    {
+        byte ReadByte();
+        void WriteByte(byte b);
+        void Read(byte[] buffer, int offset, int count);
+        void Write(byte[] buffer, int offset, int count);
+    }
+
+    //public struct MemoryMappedViewStream : IStream
+    //{
+    //    public MemoryMappedViewStream(System.IO.MemoryMappedFiles.MemoryMappedViewAccessor memoryMappedViewAccessor)
+    //    {
+    //        MemoryMappedViewAccessor = memoryMappedViewAccessor;
+    //    }
+
+    //    public System.IO.MemoryMappedFiles.MemoryMappedViewAccessor MemoryMappedViewAccessor { get; }
+
+    //    public void Read(byte[] buffer, int offset, int count)
+    //    {
+    //        MemoryMappedViewAccessor.read
+    //        Stream.Read(buffer, offset, count);
+    //    }
+
+    //    public byte ReadByte()
+    //    {
+    //        return (byte)Stream.ReadByte();
+    //    }
+
+    //    public void Write(byte[] buffer, int offset, int count)
+    //    {
+    //        Stream.Write(buffer, offset, count);
+    //    }
+
+    //    public void WriteByte(byte b)
+    //    {
+    //        Stream.WriteByte(b);
+    //    }
+    //}
+
+    public struct StreamStream : IStream
+    {
+        public StreamStream(Stream stream)
+        {
+            Stream = stream;
+        }
+
+        public Stream Stream { get; }
+
+        public void Read(byte[] buffer, int offset, int count)
+        {
+            Stream.Read(buffer, offset, count);
+        }
+
+        public byte ReadByte()
+        {
+            return (byte)Stream.ReadByte();
+        }
+
+        public void Write(byte[] buffer, int offset, int count)
+        {
+            Stream.Write(buffer, offset, count);
+        }
+
+        public void WriteByte(byte b)
+        {
+            Stream.WriteByte(b);
+        }
+    }
+
+    /// <summary>
+    /// Convenience class providing a concrete StreamReaderWriter for the Stream class
+    /// Hopefully it won't bleed much performance
+    /// </summary>
+    public sealed class StreamReaderWriter : StreamReaderWriter<StreamStream>
+    {
+        public new Stream Stream
+        {
+            get
+            {
+                return base.Stream.Stream;
+            }
+            set
+            {
+                base.Stream = new StreamStream(value);
+            }
+        }
+        
+        public StreamReaderWriter(Stream stream, bool littleEndian = false, Encoding encoding = null) : base(new StreamStream(stream), littleEndian, encoding)
+        {
+            // nix
+        }
+    }
+
+    /// <summary>
+    /// Convenience class providing a an abstract StreamReaderWriter
+    /// </summary>
+    public sealed class GenericStreamReaderWriter : StreamReaderWriter<IStream>
+    {
+        public GenericStreamReaderWriter(IStream stream, bool littleEndian = false, Encoding encoding = null) : base(stream, littleEndian, encoding)
+        {
+            // nix
+        }
+    }
+
+    /// <summary>
+    /// Provides Read/Write methods over a generic TStream
+    /// Note: many of these methods are untested
+    /// </summary>
+    public class StreamReaderWriter<TStream> where TStream : IStream
+    {
+        private const int MinBuffer = 8; // 64bits is pretty much the smallest sensible thing
+        
+        private byte[] Buffer = new byte[MinBuffer];
+
+        public TStream Stream { get; set; }
+        public bool IsLittleEndian { get; set; }
+        public Encoding Encoding { get; set; }
+
+        /// <summary>
+        /// Initialises a StreamReaderWriter
+        /// </summary>
+        /// <param name="stream"></param>
+        /// <param name="littleEndian"></param>
+        /// <param name="encoding">The default encoding is UTF-8</param>
+        public StreamReaderWriter(TStream stream, bool littleEndian = false, Encoding encoding = null)
+        {
+            Stream = stream;
+            IsLittleEndian = littleEndian;
+            Encoding = encoding ?? UTF8Encoding.UTF8;
+        }
+        
+        // checks for MinBuffer, which may enable this to be optimised away in some instances
+        private void AllocateConst(int capacity)
+        {
+            if (capacity > MinBuffer && Buffer.Length < capacity)
+            {
+                var newBuffer = new byte[Math.Max(Buffer.Length * 2, capacity)];
+                Buffer = newBuffer;
+            }
+        }
+        
+        // don't check MinBuffer where we have no idea
+        private void Allocate(int capacity)
+        {
+            if (capacity > MinBuffer && Buffer.Length < capacity)
+            {
+                var newBuffer = new byte[Math.Max(Buffer.Length * 2, capacity)];
+                Buffer = newBuffer;
+            }
+        }
+        
+        // don't check MinBuffer where we have no idea
+        private void Allocate(int capacity, int preserveCount)
+        {
+            if (capacity > MinBuffer && Buffer.Length < capacity)
+            {
+                var newBuffer = new byte[Math.Max(Buffer.Length * 2, capacity)];
+                if (preserveCount > 0)
+                   System.Buffer.BlockCopy(Buffer, 0, newBuffer, 0, preserveCount);
+                Buffer = newBuffer;
+            }
+        }
+
+        // reading
+
+        private void Read(int count)
+        {
+            Allocate(count, 0);
+            Stream.Read(Buffer, 0, count);
+        }
+
+        /// <summary>
+        /// Returns the internal buffer: do NOT mess with it
+        /// </summary>
+        public byte[] ViewBufferedBytes(int count)
+        {
+            Read(count);
+            return Buffer;
+        }
+
+        /// <summary>
+        /// Reads the given number of bytes from the stream
+        /// Count must be >= 0
+        /// </summary>
+        public byte[] ReadBytes(int count)
+        {
+            byte[] barr = new byte[count];
+            Stream.Read(barr, 0, count);
+            return barr;
+        }
+
+        /// <summary>
+        /// Reads bytes rom the stream
+        /// </summary>
+        public void ReadBytes(byte[] barr, int offset, int count)
+        {
+            Stream.Read(barr, offset, count);
+        }
+
+        /// <summary>
+        /// Reads the given number of bytes from the stream, using the given buffer if possible
+        /// Count must be >= 0
+        /// </summary>
+        public byte[] ReadBytes(int count, byte[] availableBuffer)
+        {
+            byte[] barr = availableBuffer.Length >= count ? availableBuffer : new byte[count];
+            Stream.Read(barr, 0, count);
+            return barr;
+        }
+
+        /// <summary>
+        /// Returns the internal buffer (if not null): do NOT mess with it
+        /// </summary>
+        public byte[] ViewBufferedLongPascalBytes()
+        {
+            int count = ReadInt32();
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                Read(count);
+                return Buffer;
+            }
+        }
+        
+        /*
+        /// <summary>
+        /// This method does not work
+        /// </summary>
+        public string ReadNullTerminatedString()
+        {
+            return ReadNullTerminatedString(out int _);
+        }
+        
+        /// <summary>
+        /// This method does not work
+        /// </summary>
+        public string ReadNullTerminatedString(out int bytesRead)
+        {
+            var decoder = Encoding.GetDecoder();
+            
+            int partialSize = 1024;
+            byte[] byteBuffer = new byte[partialSize];
+            char[] charBuffer = new char[partialSize];
+
+            StringBuilder sb = new StringBuilder();
+            bytesRead = 0;
+            
+            int carryCount = 0;
+            while (true)
+            {
+                int offset = carryCount;
+                int pos = offset;
+                carryCount = 0;
+                bool provisionalEnd = false;
+
+                while (pos < partialSize)
+                {
+                    byte b = ReadByte();
+                    byteBuffer[pos++] = b;
+
+                    if (b == 0)
+                    {
+                        provisionalEnd = true;
+                        carryCount = 1;
+                        break;
+                    }
+                }
+                bytesRead += pos - offset;
+                
+                // don't convert the carry stuff
+                decoder.Convert(byteBuffer, offset, pos - carryCount, charBuffer, 0, partialSize, false, out int bytesUsed, out int charsUsed, out bool completed);
+
+                // stuff what we have do far onto the string
+                sb.Append(charBuffer, 0, charsUsed);
+
+                if (provisionalEnd && completed)
+                {
+                    // saw a null, and the decoder is happy: finish
+                    return sb.ToString();
+                }
+                else if (carryCount > 0)
+                {
+                    // stuff on the end to carry over
+                    for (int i = 0; i < carryCount; i++)
+                        byteBuffer[i] = byteBuffer[pos - carryCount + i];
+                }
+            }
+        }
+        */
+        
+        public byte[] ReadLongPascalBytes()
+        {
+            int count = ReadInt32();
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                byte[] barr = new byte[count];
+                Stream.Read(barr, 0, count);
+                return barr;
+            }
+        }
+        
+        public byte[] ReadLongPascalBytes(byte[] availableBuffer)
+        {
+            int count = ReadInt32();
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                byte[] barr = availableBuffer.Length >= count ? availableBuffer : new byte[count];
+                Stream.Read(barr, 0, count);
+                return barr;
+            }
+        }
+
+        public string ReadString(int length)
+        {
+            int count = length;
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                Read(count);
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+
+        /// <summary>
+        /// Length is int32
+        /// </summary>
+        public string ReadLongPascalString()
+        {
+            int count = ReadInt32();
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                Read(count);
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+        
+        /// <summary>
+        /// Length is int32, supports null
+        /// </summary>
+        public string ReadLongPascalString(out int bytesRead)
+        {
+            int count = ReadInt32();
+
+            if (count < 0)
+            {
+                bytesRead = 4;
+                return null;
+            }
+            else
+            {
+                Read(count);
+                bytesRead = 4 + count;
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+        
+        /// <summary>
+        /// Length is uint16
+        /// </summary>
+        public string ReadUShortPascalString()
+        {
+            int count = ReadUInt16();
+            
+            Read(count);
+            return Encoding.GetString(Buffer, 0, count);
+        }
+        
+        /// <summary>
+        /// Length is uint16
+        /// </summary>
+        public string ReadUShortPascalString(out int bytesRead)
+        {
+            int count = ReadUInt16();
+            
+            Read(count);
+            bytesRead = 2 + count;
+            return Encoding.GetString(Buffer, 0, count);
+        }
+        
+        /// <summary>
+        /// Length is int16, supports null
+        /// </summary>
+        public string ReadShortPascalString()
+        {
+            int count = ReadInt16();
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                Read(count);
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+        
+        /// <summary>
+        /// Length is int16, supports null
+        /// </summary>
+        public string ReadShortPascalString(out int bytesRead)
+        {
+            int count = ReadInt16();
+
+            if (count < 0)
+            {
+                bytesRead = 2;
+                return null;
+            }
+            else
+            {
+                Read(count);
+                bytesRead = 2 + count;
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+        
+        /// <summary>
+        /// Length is byte
+        /// </summary>
+        public string ReadUVeryShortPascalString()
+        {
+            int count = ReadByte();
+            
+            Read(count);
+            return Encoding.GetString(Buffer, 0, count);
+        }
+        
+        /// <summary>
+        /// Length is byte
+        /// </summary>
+        public string ReadUVeryShortPascalString(out int bytesRead)
+        {
+            int count = ReadByte();
+            
+            Read(count);
+            bytesRead = 1 + count;
+            return Encoding.GetString(Buffer, 0, count);
+        }
+        
+        /// <summary>
+        /// Length is sbyte, supports null
+        /// </summary>
+        public string ReadVeryShortPascalString()
+        {
+            sbyte count = ReadSByte();
+
+            if (count < 0)
+            {
+                return null;
+            }
+            else
+            {
+                Read(count);
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+        
+        /// <summary>
+        /// Length is sbyte, supports null
+        /// </summary>
+        public string ReadVeryShortPascalString(out int bytesRead)
+        {
+            sbyte count = ReadSByte();
+
+            if (count < 0)
+            {
+                bytesRead = 1;
+                return null;
+            }
+            else
+            {
+                Read(count);
+                bytesRead = 1 + count;
+                return Encoding.GetString(Buffer, 0, count);
+            }
+        }
+        
+        public byte ReadByte()
+        {
+            return (byte)Stream.ReadByte();
+        }
+        
+        public sbyte ReadSByte()
+        {
+            unchecked
+            {
+                return (sbyte)ReadByte();
+            }
+        }
+        
+        public bool ReadBool()
+        {
+            return ReadByte() > 0;
+        }
+        
+        public ushort ReadUInt16()
+        {
+            unchecked
+            {
+                return (ushort)ReadInt16Internal();
+            }
+        }
+        
+        public short ReadInt16()
+        {
+            unchecked
+            {
+                return (short)ReadInt16Internal();
+            }
+        }
+
+        private int ReadInt16Internal()
+        {
+            Read(2);
+
+            if (IsLittleEndian)
+            {
+                return
+                    Buffer[0] << 0 |
+                    Buffer[1] << 8;
+            }
+            else
+            {
+                return
+                    Buffer[0] << 8 |
+                    Buffer[1] << 0;
+            }
+        }
+        
+        public uint ReadUInt32()
+        {
+            unchecked
+            {
+                return (uint)ReadInt32();
+            }
+        }
+
+        public int ReadInt32()
+        {
+            Read(4);
+
+            if (IsLittleEndian)
+            {
+                return
+                    Buffer[0] << 0 |
+                    Buffer[1] << 8 |
+                    Buffer[2] << 16 |
+                    Buffer[3] << 24;
+            }
+            else
+            {
+                return
+                    Buffer[0] << 24|
+                    Buffer[1] << 16 |
+                    Buffer[2] << 8 |
+                    Buffer[3] << 0;
+            }
+        }
+        
+        public long ReadInt64()
+        {
+            unchecked
+            {
+                return (long)ReadUInt64();
+            }
+        }
+
+        public ulong ReadUInt64()
+        {
+            Read(8);
+
+            unchecked
+            {
+                if (IsLittleEndian)
+                {
+                    uint low = (uint)(
+                        Buffer[0] << 0 |
+                        Buffer[1] << 8 |
+                        Buffer[2] << 16 |
+                        Buffer[3] << 24);
+                    uint high = (uint)(
+                        Buffer[4] << 0 |
+                        Buffer[5] << 8 |
+                        Buffer[6] << 16 |
+                        Buffer[7] << 24);
+                    return (ulong)(low + ((ulong)high << 32));
+                }
+                else
+                {
+                    uint high = (uint)(
+                        Buffer[0] << 24 |
+                        Buffer[1] << 16 |
+                        Buffer[2] << 8 |
+                        Buffer[3] << 0);
+                    uint low = (uint)(
+                        Buffer[4] << 24 |
+                        Buffer[5] << 16 |
+                        Buffer[6] << 8 |
+                        Buffer[7] << 0);
+                    return (low + ((ulong)high << 32));
+                }
+            }
+        }
+
+        public float ReadFloat32()
+        {
+            Read(4);
+
+            if (IsLittleEndian != BitConverter.IsLittleEndian)
+            {
+                Array.Reverse(Buffer, 0, 4);
+            }
+
+            return BitConverter.ToSingle(Buffer, 0);
+        }
+
+        public double ReadFloat64()
+        {
+            Read(8);
+            
+            if (IsLittleEndian != BitConverter.IsLittleEndian)
+            {
+                Array.Reverse(Buffer, 0, 8);
+            }
+
+            return BitConverter.ToDouble(Buffer, 0);
+        }
+
+        // writing
+        
+        private void Write(int count)
+        {
+            Stream.Write(Buffer, 0, count);
+        }
+        
+        private void WriteBytes(byte[] barr, int offset, int count)
+        {
+            Stream.Write(barr, offset, count);
+        }
+
+        /*
+        /// <summary>
+        /// Does not support null
+        /// </summary>
+        public void WriteNullTerminatedString(string str)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException(nameof(str), "Cannot serialise a null string as a null-terminated string");
+            }
+            else
+            {
+                int count = Encoding.GetByteCount(str);
+
+                Allocate(count);
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                Write(count);
+                WriteByte(0);
+            }
+        }
+
+        /// <summary>
+        /// Does not support null
+        /// </summary>
+        public void WriteNullTerminatedString(string str, out int bytesWritten)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException(nameof(str), "Cannot serialise a null string as a null-terminated string");
+            }
+            else
+            {
+                int count = Encoding.GetByteCount(str);
+
+                Allocate(count);
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                Write(count);
+                WriteByte(0);
+                bytesWritten = count + 1;
+            }
+        }
+        */
+
+        public void WriteLongPascalBytes(byte[] bytes, int offset, int count)
+        {
+            if (bytes == null)
+            {
+                WriteInt32(-1);
+            }
+            else
+            {
+                WriteInt32(count);
+                Stream.Write(bytes, offset, count);
+            }
+        }
+
+        public void WriteString(string str, int declaredLength)
+        {
+            if (str == null)
+                return;
+
+            Allocate(declaredLength);
+
+            Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+            Write(declaredLength);
+        }
+
+        public void WriteLongPascalString(string str)
+        {
+            if (str == null)
+            {
+                WriteInt32(-1);
+            }
+            else
+            {
+                int count = Encoding.GetByteCount(str);
+
+                Allocate(count);
+                WriteInt32(count);
+                
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                Write(count);
+            }
+        }
+
+        public void WriteLongPascalString(string str, out int bytesWritten)
+        {
+            if (str == null)
+            {
+                bytesWritten = 4;
+                WriteInt32(-1);
+            }
+            else
+            {
+                int count = Encoding.GetByteCount(str);
+
+                Allocate(count);
+                WriteInt32(count);
+                
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                bytesWritten = 4 + count;
+                Write(count);
+            }
+        }
+
+        public void WriteUShortPascalString(string str)
+        {
+            ushort count = (ushort)Encoding.GetByteCount(str);
+
+            Allocate(count);
+            WriteUInt16(count);
+                
+            Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+            Write(count);
+        }
+
+        public void WriteUShortPascalString(string str, out int bytesWritten)
+        {
+            ushort count = (ushort)Encoding.GetByteCount(str);
+
+            Allocate(count);
+            WriteUInt16(count);
+                
+            Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+            bytesWritten = 2 + count;
+            Write(count);
+        }
+
+        public void WriteShortPascalString(string str)
+        {
+            if (str == null)
+            {
+                WriteInt16(-1);
+            }
+            else
+            {
+                short count = (short)Encoding.GetByteCount(str);
+
+                Allocate(count);
+                WriteInt16(count);
+                
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                Write(count);
+            }
+        }
+
+        public void WriteShortPascalString(string str, out int bytesWritten)
+        {
+            if (str == null)
+            {
+                WriteInt16(-1);
+                bytesWritten = 2;
+            }
+            else
+            {
+                short count = (short)Encoding.GetByteCount(str);
+
+                Allocate(count);
+                WriteInt16(count);
+                
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                bytesWritten = 2 + count;
+                Write(count);
+            }
+        }
+
+        public void WriteUVeryShortPascalString(string str)
+        {
+            byte count = (byte)Encoding.GetByteCount(str);
+
+            Allocate(count);
+            WriteByte(count);
+                
+            Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+            Write(count);
+        }
+
+        public void WriteUVeryShortPascalString(string str, out int bytesWritten)
+        {
+            byte count = (byte)Encoding.GetByteCount(str);
+
+            Allocate(count);
+            WriteByte(count);
+                
+            Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+            bytesWritten = 1 + count;
+            Write(count);
+        }
+
+        public void WriteVeryShortPascalString(string str)
+        {
+            if (str == null)
+            {
+                WriteSByte(-1);
+            }
+            else
+            {
+                sbyte count = (sbyte)Encoding.GetByteCount(str);
+
+                Allocate(count);
+                WriteSByte(count);
+                
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                Write(count);
+            }
+        }
+
+        public void WriteVeryShortPascalString(string str, out int bytesWritten)
+        {
+            if (str == null)
+            {
+                WriteSByte(-1);
+                bytesWritten = 1;
+            }
+            else
+            {
+                sbyte count = (sbyte)Encoding.GetByteCount(str);
+
+                Allocate(count);
+                WriteSByte(count);
+                
+                Encoding.GetBytes(str, 0, str.Length, Buffer, 0);
+                bytesWritten = 1 + count;
+                Write(count);
+            }
+        }
+        
+        public void WriteSByte(sbyte v)
+        {
+            unchecked
+            {
+                WriteByte((byte)v);
+            }
+        }
+
+        public void WriteByte(byte v)
+        {
+            Stream.WriteByte(v);
+        }
+
+        public void WriteBool(bool v)
+        {
+            Stream.WriteByte(v ? (byte)1 : (byte)0);
+        }
+
+        public void WriteInt16(short v)
+        {
+            unchecked
+            {
+                WriteUInt16((ushort)v);
+            }
+        }
+
+        public void WriteUInt16(ushort v)
+        {
+            AllocateConst(2);
+
+            unchecked
+            {
+                if (IsLittleEndian)
+                {
+                    Buffer[0] = (byte)(v >> 0);
+                    Buffer[1] = (byte)(v >> 8);
+                }
+                else
+                {
+                    Buffer[0] = (byte)(v >> 8);
+                    Buffer[1] = (byte)(v >> 0);
+                }
+            }
+
+            Write(2);
+        }
+        
+        public void WriteInt32(int v)
+        {
+            unchecked
+            {
+                WriteUInt32((uint)v);
+            }
+        }
+
+        public void WriteUInt32(uint v)
+        {
+            AllocateConst(4);
+
+            unchecked
+            {
+                if (IsLittleEndian)
+                {
+                    Buffer[0] = (byte)(v >> 0);
+                    Buffer[1] = (byte)(v >> 8);
+                    Buffer[2] = (byte)(v >> 16);
+                    Buffer[3] = (byte)(v >> 24);
+                }
+                else
+                {
+                    Buffer[0] = (byte)(v >> 24);
+                    Buffer[1] = (byte)(v >> 16);
+                    Buffer[2] = (byte)(v >> 8);
+                    Buffer[3] = (byte)(v >> 0);
+                }
+            }
+
+            Write(4);
+        }
+        
+        public void WriteInt64(long v)
+        {
+            unchecked
+            {
+                WriteUInt64((ulong)v);
+            }
+        }
+
+        public void WriteUInt64(ulong v)
+        {
+            AllocateConst(8);
+
+            unchecked
+            {
+                if (IsLittleEndian)
+                {
+                    Buffer[0] = (byte)(v >> 0);
+                    Buffer[1] = (byte)(v >> 8);
+                    Buffer[2] = (byte)(v >> 16);
+                    Buffer[3] = (byte)(v >> 24);
+                    Buffer[4] = (byte)(v >> 32);
+                    Buffer[5] = (byte)(v >> 40);
+                    Buffer[6] = (byte)(v >> 48);
+                    Buffer[7] = (byte)(v >> 56);
+                }
+                else
+                {
+                    Buffer[0] = (byte)(v >> 56);
+                    Buffer[1] = (byte)(v >> 48);
+                    Buffer[2] = (byte)(v >> 40);
+                    Buffer[3] = (byte)(v >> 32);
+                    Buffer[4] = (byte)(v >> 24);
+                    Buffer[5] = (byte)(v >> 16);
+                    Buffer[6] = (byte)(v >> 8);
+                    Buffer[7] = (byte)(v >> 0);
+                }
+            }
+
+            Write(8);
+        }
+
+        public void WriteFloat32(float v)
+        {
+            // TODO: allocation: bad
+            var bytes = BitConverter.GetBytes(v);
+
+            if (IsLittleEndian == BitConverter.IsLittleEndian)
+            {
+                Array.Reverse(bytes);
+            }
+
+            WriteBytes(bytes, 0, 4);
+        }
+
+        public void WriteFloat64(double v)
+        {
+            // TODO: allocation: bad
+            var bytes = BitConverter.GetBytes(v);
+
+            if (IsLittleEndian == BitConverter.IsLittleEndian)
+            {
+                Array.Reverse(bytes);
+            }
+
+            WriteBytes(bytes, 0, 8);
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/Serialisation/StringTree.cs b/M4MCode/NetState/NetState/Serialisation/StringTree.cs
new file mode 100644
index 0000000..6a45998
--- /dev/null
+++ b/M4MCode/NetState/NetState/Serialisation/StringTree.cs
@@ -0,0 +1,209 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+
+namespace NetState.Serialisation
+{
+    public abstract class StringTreeContextBase<STC> : IStringTreeContext<STC> where STC : StringTreeContextBase<STC>
+    {
+        public string Name { get; }
+        private Dictionary<string, string> Attributes { get; }
+        private List<STC> ChildContexts { get; }
+        
+        public IEnumerable<KeyValuePair<string, string>> EnumerateAttributes() => Attributes;
+        public IEnumerable<STC> EnumerateChildContexts() => ChildContexts;
+        public IEnumerable<STC> EnumerateChildContexts(string name) => ChildContexts.Where(cc => cc.Name == name);
+        IEnumerable<IStringTreeContext> IStringTreeContext.EnumerateChildContexts() => ChildContexts;
+        IEnumerable<IStringTreeContext> IStringTreeContext.EnumerateChildContexts(string name) => ChildContexts.Where(cc => cc.Name == name);
+
+        protected StringTreeContextBase(string name)
+        {
+            Name = name;
+        }
+
+        IStringTreeContext IStringTreeContext.GetChildContext(string name) => GetChildContext(name);
+        public STC GetChildContext(string name)
+        {
+            var matching = EnumerateChildContexts(name);
+            int count = matching.Count();
+
+            if (count == 0)
+                throw new Exception("No child context with name '" + name + "'");
+            if (matching.Count() > 1)
+                throw new Exception("More than one child context with name '" + name + "'");
+
+            return matching.First();
+        }
+        
+        IStringTreeContext IStringTreeContext.TryGetChildContext(string name) => TryGetChildContext(name);
+        public STC TryGetChildContext(string name)
+        {
+            var matching = EnumerateChildContexts(name);
+            int count = matching.Count();
+            
+            if (count == 0)
+                return null;
+            if (matching.Count() > 1)
+                throw new Exception("More than one child context with name '" + name + "'");
+            else
+            {
+                return matching.First();
+            }
+        }
+
+        IStringTreeContext IStringTreeContext.AddChildContext(string name) => AddChildContext(name);
+        public STC AddChildContext(string name)
+        {
+            STC cc = PrepareChildContext(name);
+            ChildContexts.Add(cc);
+            return cc;
+        }
+
+        protected abstract STC PrepareChildContext(string name);
+
+        public void SetAttribute(string key, string value)
+        {
+            Attributes[key] = value;
+        }
+
+        public string GetAttribute(string key)
+        {
+            return Attributes[key];
+        }
+    }
+
+    //public class StringTreeContext<CX> : IStringTreeContext<StringTreeContext<CX>> where CX : class
+    //{
+    //    public CX AdditionalContext { get; }
+
+    //    public string Name { get; }
+    //    private Dictionary<string, string> Attributes { get; }
+    //    private List<StringTreeContext<CX>> ChildContexts { get; }
+        
+    //    public IEnumerable<KeyValuePair<string, string>> EnumerateAttributes() => Attributes;
+    //    public IEnumerable<StringTreeContext<CX>> EnumerateChildContexts() => ChildContexts;
+    //    public IEnumerable<StringTreeContext<CX>> EnumerateChildContexts(string name) => ChildContexts.Where(cc => cc.Name == name);
+    //    IEnumerable<IStringTreeContext> IStringTreeContext.EnumerateChildContexts() => ChildContexts;
+    //    IEnumerable<IStringTreeContext> IStringTreeContext.EnumerateChildContexts(string name) => ChildContexts.Where(cc => cc.Name == name);
+
+    //    public StringTreeContext(string name, CX additionalContext)
+    //    {
+    //        Name = name;
+    //        AdditionalContext = additionalContext;
+    //    }
+
+    //    IStringTreeContext IStringTreeContext.GetChildContext(string name) => GetChildContext(name);
+    //    public StringTreeContext<CX> GetChildContext(string name)
+    //    {
+    //        var matching = EnumerateChildContexts(name);
+    //        int count = matching.Count();
+
+    //        if (count == 0)
+    //            throw new Exception("No child context with name '" + name + "'");
+    //        if (matching.Count() > 1)
+    //            throw new Exception("More than one child context with name '" + name + "'");
+
+    //        return matching.First();
+    //    }
+        
+    //    IStringTreeContext IStringTreeContext.TryGetChildContext(string name) => TryGetChildContext(name);
+    //    public StringTreeContext<CX> TryGetChildContext(string name)
+    //    {
+    //        var matching = EnumerateChildContexts(name);
+    //        int count = matching.Count();
+            
+    //        if (count == 0)
+    //            return null;
+    //        if (matching.Count() > 1)
+    //            throw new Exception("More than one child context with name '" + name + "'");
+    //        else
+    //        {
+    //            return matching.First();
+    //        }
+    //    }
+
+    //    IStringTreeContext IStringTreeContext.AddChildContext(string name) => AddChildContext(name);
+    //    public StringTreeContext<CX> AddChildContext(string name)
+    //    {
+    //        var context = new StringTreeContext<CX>(name, AdditionalContext);
+    //        return context;
+    //    }
+
+    //    public void SetAttribute(string key, string value)
+    //    {
+    //        Attributes[key] = value;
+    //    }
+
+    //    public string GetAttribute(string key)
+    //    {
+    //        return Attributes[key];
+    //    }
+    //}
+
+    public interface IStringTreeContextSerialiser
+    {
+        void Serialise(Stream stream, IStringTreeContext context);
+        void Deserialise(Stream stream, IStringTreeContext context);
+    }
+
+    public class XmlStringTreeContextSerialiser : IStringTreeContextSerialiser
+    {
+        private struct FlatElem
+        {
+            public XmlNode Parent { get; }
+            public IStringTreeContext Context;
+
+            public FlatElem(XmlNode parent, IStringTreeContext context) : this()
+            {
+                Parent = parent;
+                Context = context;
+            }
+        }
+
+        public void Deserialise(Stream stream, IStringTreeContext context)
+        {
+            throw new NotImplementedException("Implement XmlStringTree Deserialiser");
+        }
+
+        /// <summary>
+        /// Serialises the IStringTreeContext to the given stream as barely legal Xml
+        /// </summary>
+        public void Serialise(Stream stream, IStringTreeContext context)
+        {
+            XmlDocument doc = new XmlDocument();
+            var root = doc.CreateElement("XmlRoot");
+
+            Queue<FlatElem> due = new Queue<FlatElem>();
+            due.Enqueue(new FlatElem(root, context));
+
+            while (due.Count > 0)
+                SerialiseNode(doc, root, context, due);
+            
+            doc.WriteTo(XmlWriter.Create(stream));
+        }
+
+        private void SerialiseNode(XmlDocument doc, XmlElement parent, IStringTreeContext context, Queue<FlatElem> dueQueue)
+        {
+            // prepare us
+            var node = doc.CreateElement(context.Name);
+
+            // fill in our attributes
+            foreach (var attribute in context.EnumerateAttributes())
+            {
+                node.SetAttribute(attribute.Key, attribute.Value);
+            }
+
+            // queue children
+            foreach (var child in context.EnumerateChildContexts())
+            {
+                dueQueue.Enqueue(new FlatElem(node, child));
+            }
+
+            // append us
+            parent.AppendChild(node);
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/SoftState/SoftState.cs b/M4MCode/NetState/NetState/SoftState/SoftState.cs
new file mode 100644
index 0000000..f01dab6
--- /dev/null
+++ b/M4MCode/NetState/NetState/SoftState/SoftState.cs
@@ -0,0 +1,381 @@
+using NetState.AutoState;
+using NetState.Serialisation;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NetState.SoftState
+{
+    public interface ISoftStateMethod
+    {
+    }
+
+    public class SoftStateMethodAttribute : Attribute, ISoftStateMethod
+    {
+    }
+
+    public interface ISoftStateAttribute
+    {
+    }
+
+    public class SoftClassAttribute : Attribute, ISoftStateAttribute
+    {
+    }
+
+    //[AttributeUsage(AttributeTargets.Property)] // not valid on iface
+    public interface ISoftStatePropertyAttribute : IStateProviderAttribute
+    {
+        string Name { get; }
+        object Label { get; }
+
+        bool Deprecated { get; }
+    }
+    
+    public class SoftPropertyAttributeAttribute : Attribute, ISoftStatePropertyAttribute
+    {
+        public string Name { get; }
+        public object Label { get; }
+
+        public bool Deprecated { get; }
+
+        private object[] StateProviders;
+
+        public SoftPropertyAttributeAttribute(string name, object label, params Type[] stateProviderTypes)
+        {
+            Name = name;
+            Label = label;
+
+            StateProviders = stateProviderTypes.Select(spt => AutoSerialisationHelpers.AcquireInstance<object>(spt)).ToArray();
+
+            Deprecated = false;
+        }
+
+        public SoftPropertyAttributeAttribute(string name, object label, bool deprecated, params Type[] stateProviderTypes)
+        {
+            Name = name;
+            Label = label;
+
+            StateProviders = stateProviderTypes.Select(spt => AutoSerialisationHelpers.AcquireInstance<object>(spt)).ToArray();
+
+            Deprecated = deprecated;
+        }
+
+        public virtual IStateProvider<PT, TSourceContext, TSinkContext> Prepare<PT, TSourceContext, TSinkContext>()
+        {
+            foreach (var stateProvider in StateProviders)
+            {
+                if (stateProvider is IStateProvider<PT, TSourceContext, TSinkContext> foundProvider)
+                {
+                    return foundProvider;
+                }
+                if (stateProvider is IStateProviderPreparer<TSourceContext, TSinkContext> foundProviderPreparer)
+                {
+                    var provisional = foundProviderPreparer.Prepare<PT>();
+
+                    if (provisional != null)
+                        return provisional;
+                }
+            }
+            
+            // nothing here is compatible; just return null
+            return null;
+        }
+    }
+
+    public interface ISoftStatePropertyInfo<OT, TWriteContext, TReadContext>
+    {
+        string Name { get; }
+
+        bool Deprecated { get; }
+
+        void Write(OT target, TWriteContext context);
+        void Read(OT target, TReadContext context);
+    }
+
+    public class SoftStateProviderInfo<OT, TWriteContext, TReadContext>/* where TWriteContext : IStreamContext where TReadContext : IStreamContext*/
+    {
+        private class SoftStatePropertyInfo : ISoftStatePropertyInfo<OT, TWriteContext, TReadContext>
+        {
+            public string Name { get; }
+            public AutoState.IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext> UntypedPropertyStateProvider { get; }
+
+            public bool Deprecated { get; }
+
+            public SoftStatePropertyInfo(string name, IUntypedPropertyStateProvider<OT, TWriteContext, TReadContext> untypedPropertyStateProvider, bool deprecated)
+            {
+                Name = name;
+                UntypedPropertyStateProvider = untypedPropertyStateProvider;
+
+                Deprecated = deprecated;
+            }
+
+            public void Write(OT target, TWriteContext context)
+            {
+                UntypedPropertyStateProvider.Write(target, context);
+            }
+
+            public void Read(OT target, TReadContext context)
+            {
+                UntypedPropertyStateProvider.Read(target, context);
+            }
+        }
+
+        private static ISoftStatePropertyInfo<OT, TWriteContext, TReadContext> Prepare<PT>(System.Reflection.PropertyInfo propertyInfo, ISoftStatePropertyAttribute sspa)
+        {
+            var usps = PropertyStateProvisioning<OT, TWriteContext, TReadContext>.Prepare<PT>(propertyInfo, sspa);
+            return new SoftStatePropertyInfo(sspa.Name, usps, sspa.Deprecated);
+        }
+
+        private static readonly Lazy<SoftStateProviderInfo<OT, TWriteContext, TReadContext>> _instance = new Lazy<SoftStateProviderInfo<OT, TWriteContext, TReadContext>>(() => new SoftStateProviderInfo<OT, TWriteContext, TReadContext>());
+        public static SoftStateProviderInfo<OT, TWriteContext, TReadContext> Instance => _instance.Value;
+        
+        public Dictionary<string, ISoftStatePropertyInfo<OT, TWriteContext, TReadContext>> SoftStateProperties { get; }
+
+        public int Version { get; }
+        
+        public Action<OT, TReadContext> PreRead { get; }
+        public Action<OT, TReadContext> PostRead { get; }
+        public Action<OT, TWriteContext> PreWrite { get; }
+        public Action<OT, TWriteContext> PostWrite { get; }
+
+        public SoftStateProviderInfo()
+        {
+            Type type = typeof(OT);
+
+            var properties = AutoSerialisationHelpers.EnumerateAllProperties(type);
+            
+            // accumultate properties
+            SoftStateProperties = new Dictionary<string, ISoftStatePropertyInfo<OT, TWriteContext, TReadContext>>();
+
+            foreach (var property in properties)
+            {
+                var attributes = property.GetCustomAttributes(true);
+                bool hasAsa = false;
+                bool hasValidAsa = false;
+
+                foreach (var attribute in attributes)
+                {
+                    if (attribute is ISoftStatePropertyAttribute sspa)
+                    {
+                        hasAsa = true;
+                        
+                        var name = sspa.Name;
+                        var sspi = AutoSerialisationHelpers.CallNamedStatic<ISoftStatePropertyInfo<OT, TWriteContext, TReadContext>>(
+                            typeof(SoftStateProviderInfo<OT, TWriteContext, TReadContext>),
+                            null,
+                            nameof(SoftStateProviderInfo<OT, TWriteContext, TReadContext>.Prepare),
+                            new[] { property.PropertyType },
+                            property,
+                            sspa);
+
+                        if (sspi != null)
+                        {
+                            if (SoftStateProperties.ContainsKey(name))
+                                throw new Exception("More than one property with name '" + name + "' in type " + type.Name + " (have you considered sub-types?)");
+
+                            SoftStateProperties.Add(name, sspi);
+
+                            hasValidAsa = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (hasAsa && !hasValidAsa)
+                {
+                    throw new Exception($"Property {property.Name} on type {type.FullName} has one or more associated StateProviders, but none are compatible with WriteContext {typeof(TWriteContext).FullName} and ReadContext {typeof(TReadContext).FullName}");
+                }
+            }
+
+            // accumulate pre/post call methods
+            var methods = AutoSerialisationHelpers.EnumerateAllMethods(type);
+
+            foreach (var mi in methods)
+            {
+                if (!mi.GetCustomAttributesData().Any(cad => cad.AttributeType.GetInterfaces().Contains(typeof(ISoftStateMethod))))
+                    continue;
+
+                var mparams = mi.GetParameters();
+                
+                if (mi.Name == "PreRead" && mparams.Length == 1 && mparams[0].ParameterType.IsAssignableFrom(typeof(TReadContext)) && mi.ReturnType == typeof(void))
+                {
+                    PreRead = (Action<OT, TReadContext>)mi.CreateDelegate(typeof(Action<OT, TReadContext>));
+                }
+
+                if (mi.Name == "PostRead" && mparams.Length == 1 && mparams[0].ParameterType.IsAssignableFrom(typeof(TReadContext)) && mi.ReturnType == typeof(void))
+                {
+                    PostRead = (Action<OT, TReadContext>)mi.CreateDelegate(typeof(Action<OT, TReadContext>));
+                }
+                
+                if (mi.Name == "PreWrite" && mparams.Length == 1 && mparams[0].ParameterType.IsAssignableFrom(typeof(TWriteContext)) && mi.ReturnType == typeof(void))
+                {
+                    PreWrite = (Action<OT, TWriteContext>)mi.CreateDelegate(typeof(Action<OT, TWriteContext>));
+                }
+
+                if (mi.Name == "PostWrite" && mparams.Length == 1 && mparams[0].ParameterType.IsAssignableFrom(typeof(TWriteContext)) && mi.ReturnType == typeof(void))
+                {
+                    PostWrite = (Action<OT, TWriteContext>)mi.CreateDelegate(typeof(Action<OT, TWriteContext>));
+                }
+            }
+            
+            // heh
+            Version = -1;
+        }
+    }
+
+    public class SoftStateClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where T : class where TWriteContext : IStreamContext where TReadContext : IStreamContext where TProviderWriteContext : IStreamContext where TProviderReadContext : IStreamContext
+    {
+        public static readonly SoftStateClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> Instance = new SoftStateClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+
+        public IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> CreateNew()
+        {
+            return new SoftStateClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+        }
+    }
+
+    public class SoftStateClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where T : class where TWriteContext : IStreamContext where TReadContext : IStreamContext where TProviderWriteContext : IStreamContext where TProviderReadContext : IStreamContext
+    {
+        private ISoftStatePropertyInfo<T, TWriteContext, TReadContext>[] Configuration = null;
+        
+        // force this as soon as we instanciate the class
+        private static Func<T> Constructor = DefaultConstructor<T>.Constructor;
+
+        public SoftStateClassStateProvider()
+        {
+            SetDefaultConfiguration();
+        }
+
+        public void SetDefaultConfiguration()
+        {
+            Configuration = SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.SoftStateProperties.Values.Where(sspi => !sspi.Deprecated).ToArray();
+        }
+
+        // config
+        public void ReadProviderConfig(TProviderReadContext context)
+        {
+            short propertyCount = context.StreamRw.ReadInt16();
+            
+            Configuration = new ISoftStatePropertyInfo<T, TWriteContext, TReadContext>[propertyCount];
+
+            for (int i = 0; i < propertyCount; i++)
+            {
+                var name = context.StreamRw.ReadShortPascalString();
+                Configuration[i] = SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.SoftStateProperties[name];
+            }
+        }
+        
+        public void WriteProviderConfig(TProviderWriteContext context)
+        {
+            int propertyCount = Configuration.Length;
+            context.StreamRw.WriteInt16((short)propertyCount);
+            
+            for (int i = 0; i < propertyCount; i++)
+            {
+                var name = Configuration[i].Name;
+                context.StreamRw.WriteShortPascalString(name);
+            }
+        }
+
+        // create/state
+        public T ReadCreate(TReadContext context)
+        {
+            return Constructor();
+        }
+
+        public void ReadState(TReadContext context, T state)
+        {
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PreRead?.Invoke(state, context);
+
+            for (int i = 0; i < Configuration.Length; i++)
+                Configuration[i].Read(state, context);
+
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PostRead?.Invoke(state, context);
+        }
+        
+        public void WriteCreate(TWriteContext context, T state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, T state)
+        {
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PreWrite?.Invoke(state, context);
+
+            for (int i = 0; i < Configuration.Length; i++)
+                Configuration[i].Write(state, context);
+
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PostWrite?.Invoke(state, context);
+        }
+        
+        // state provider methods (no null support...)
+        public void Write(TWriteContext context, T state)
+        {
+            bool notNull = state != null;
+            context.StreamRw.WriteBool(notNull);
+
+            if (notNull)
+            {
+                WriteCreate(context, state);
+                WriteState(context, state);
+            }
+        }
+
+        public T Read(TReadContext context)
+        {
+            bool notNull = context.StreamRw.ReadBool();
+
+            if (notNull)
+            {
+                T state = ReadCreate(context);
+                ReadState(context, state);
+                return state;
+            }
+            else
+            {
+                return null;
+            }
+        }
+    }
+    
+    /// <summary>
+    /// Provides a SoftStateClassProviderFactory for anything marked with an ISoftStateAttribute
+    /// </summary>
+    public class AutoSoftStateProviders<TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : ICustomStateProviderProvider<object, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where TWriteContext : IStreamContext where TReadContext : IStreamContext where TProviderWriteContext : IStreamContext where TProviderReadContext : IStreamContext
+    {
+        public readonly static AutoSoftStateProviders<TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> Instance = new AutoSoftStateProviders<TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+        
+        private AutoSoftStateProviders()
+        {
+        }
+
+        public IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> TryResolve<T>(ref int version) where T : class
+        {
+            Type provisional = TryResolve(typeof(T));
+
+            if (provisional == null)
+                return null;
+
+            return AutoSerialisationHelpers.AcquireInstance<IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>>(provisional);
+        }
+
+        public Type TryResolve(Type t)
+        {
+            if (t.GetCustomAttributesData().Any(cad => cad.AttributeType.GetInterfaces().Contains(typeof(ISoftStateAttribute))))
+            {
+                var at = typeof(SoftStateClassStateProviderFactory<,,,,>);
+                var agt = at.MakeGenericType(
+                    t,
+                    typeof(TWriteContext),
+                    typeof(TReadContext),
+                    typeof(TProviderWriteContext),
+                    typeof(TProviderReadContext)
+                    );
+
+                return agt;
+            }
+            
+            return null;
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/SoftState/SoftStateStringTree.cs b/M4MCode/NetState/NetState/SoftState/SoftStateStringTree.cs
new file mode 100644
index 0000000..41bf758
--- /dev/null
+++ b/M4MCode/NetState/NetState/SoftState/SoftStateStringTree.cs
@@ -0,0 +1,168 @@
+using NetState.AutoState;
+using NetState.Serialisation;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NetState.SoftState
+{
+    public class SoftStateStringTreeClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where T : class where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext> where TProviderWriteContext : IStringTreeContext<TProviderWriteContext> where TProviderReadContext : IStringTreeContext<TProviderReadContext>
+    {
+        public static readonly SoftStateStringTreeClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> Instance = new SoftStateStringTreeClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+
+        public IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> CreateNew()
+        {
+            return new SoftStateStringTreeClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+        }
+    }
+
+    public class SoftStateStringTreeClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : IClassStateProvider<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where T : class where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext> where TProviderWriteContext : IStringTreeContext<TProviderWriteContext> where TProviderReadContext : IStringTreeContext<TProviderReadContext>
+    {
+        private ISoftStatePropertyInfo<T, TWriteContext, TReadContext>[] Configuration = null;
+        
+        // force this as soon as we instanciate the class
+        private static Func<T> Constructor = DefaultConstructor<T>.Constructor;
+
+        public SoftStateStringTreeClassStateProvider()
+        {
+            SetDefaultConfiguration();
+        }
+
+        public void SetDefaultConfiguration()
+        {
+            Configuration = SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.SoftStateProperties.Values.ToArray();
+        }
+
+        // config
+        public void ReadProviderConfig(TProviderReadContext context)
+        {
+            var softProperties = context.EnumerateChildContexts("SoftProperty").ToArray();
+            
+            int propertyCount = softProperties.Length;
+
+            Configuration = new ISoftStatePropertyInfo<T, TWriteContext, TReadContext>[propertyCount];
+
+            foreach (var propertyContext in softProperties)
+            {
+                int index = int.Parse(propertyContext.GetAttribute("Index"));
+                string name = propertyContext.GetAttribute("Name");
+                
+                Configuration[index] = SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.SoftStateProperties[name];
+            }
+        }
+        
+        public void WriteProviderConfig(TProviderWriteContext context)
+        {
+            for (int i = 0; i < Configuration.Length; i++)
+            {
+                var name = Configuration[i].Name;
+
+                var propertyContext = context.AddChildContext("SoftProperty");
+                propertyContext.SetAttribute("Index", i.ToString());
+                propertyContext.SetAttribute("Name", name);
+            }
+        }
+
+        // create/state
+        public T ReadCreate(TReadContext context)
+        {
+            return Constructor();
+        }
+
+        public void ReadState(TReadContext context, T state)
+        {
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PreRead?.Invoke(state, context);
+
+            for (int i = 0; i < Configuration.Length; i++)
+            {
+                var propertyContext = context.AddChildContext("P");
+                context.SetAttribute("Index", i.ToString());
+                context.SetAttribute("Debug_Name", Configuration[i].Name);
+                Configuration[i].Read(state, propertyContext);
+            }
+
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PostRead?.Invoke(state, context);
+        }
+        
+        public void WriteCreate(TWriteContext context, T state)
+        {
+            // nix
+        }
+
+        public void WriteState(TWriteContext context, T state)
+        {
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PreWrite?.Invoke(state, context);
+
+            for (int i = 0; i < Configuration.Length; i++)
+                Configuration[i].Write(state, context);
+
+            SoftStateProviderInfo<T, TWriteContext, TReadContext>.Instance.PostWrite?.Invoke(state, context);
+        }
+        
+        // state provider methods (no null support...)
+        public void Write(TWriteContext context, T state)
+        {
+            if (state == null)
+            {
+                context.AddChildContext("Null");
+            }
+            else
+            {
+                WriteCreate(context.AddChildContext("Create"), state);
+                WriteState(context.AddChildContext("State"), state);
+            }
+        }
+
+        public T Read(TReadContext context)
+        {
+            if (context.TryGetChildContext("Null") != null)
+                return null;
+            
+            T state = ReadCreate(context.GetChildContext("Create"));
+            ReadState(context.GetChildContext("State"), state);
+            return state;
+        }
+    }
+    
+    /// <summary>
+    /// Provides a SoftStateClassProviderFactory for anything marked with an ISoftStateAttribute
+    /// </summary>
+    public class AutoSoftStateStringTreeProviders<TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> : ICustomStateProviderProvider<object, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> where TWriteContext : IStringTreeContext<TWriteContext> where TReadContext : IStringTreeContext<TReadContext> where TProviderWriteContext : IStringTreeContext<TProviderWriteContext> where TProviderReadContext : IStringTreeContext<TProviderReadContext>
+    {
+        public readonly static AutoSoftStateStringTreeProviders<TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> Instance = new AutoSoftStateStringTreeProviders<TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>();
+        
+        private AutoSoftStateStringTreeProviders()
+        {
+        }
+
+        public IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext> TryResolve<T>(ref int version) where T : class
+        {
+            Type provisional = TryResolve(typeof(T));
+
+            if (provisional == null)
+                return null;
+
+            return AutoSerialisationHelpers.AcquireInstance<IClassStateProviderFactory<T, TWriteContext, TReadContext, TProviderWriteContext, TProviderReadContext>>(provisional);
+        }
+
+        public Type TryResolve(Type t)
+        {
+            if (t.GetCustomAttributesData().Any(cad => cad.AttributeType.GetInterfaces().Contains(typeof(ISoftStateAttribute))))
+            {
+                var at = typeof(SoftStateClassStateProviderFactory<,,,,>);
+                var agt = at.MakeGenericType(
+                    t,
+                    typeof(TWriteContext),
+                    typeof(TReadContext),
+                    typeof(TProviderWriteContext),
+                    typeof(TProviderReadContext)
+                    );
+
+                return agt;
+            }
+            
+            return null;
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/State/State.cs b/M4MCode/NetState/NetState/State/State.cs
new file mode 100644
index 0000000..525bc01
--- /dev/null
+++ b/M4MCode/NetState/NetState/State/State.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace NetState.State
+{
+    // state provider interfaces
+
+    /// <summary>
+    /// Writes the state out
+    /// </summary>
+    public interface IStateWriter<in T, in CX>
+    {
+        void Write(CX context, T state);
+    }
+
+    /// <summary>
+    /// Reads, assembles, and returns the state
+    /// </summary>
+    public interface IStateReader<out T, in CX>
+    {
+        T Read(CX context);
+    }
+
+    /// <summary>
+    /// Reads state into an existing instance
+    /// </summary>
+    public interface IStateInplaceReader<in T, in CX> where T : class
+    {
+        void Read(CX context, T state);
+    }
+
+    // state management interfaces
+
+    public class IdRange
+    {
+        /// <summary>
+        /// A range of IDs
+        /// </summary>
+        /// <param name="first">The first ID</param>
+        /// <param name="last">The last ID (inclusive)</param>
+        public IdRange(long first, long last)
+        {
+            if (first > last)
+                throw new Exception("Last ID must be greater than First ID");
+
+            if (first <= 0 && 0 <= last)
+                throw new Exception("0 is not a legal ID");
+
+            if (first <= -1 && -1 <= last)
+                throw new Exception("-1 is not a legal ID");
+
+            First = first;
+            Last = last;
+        }
+        
+        public long First { get; }
+        public long Last { get; }
+
+        public static IdRange FromCount(long first, long count)
+        {
+            return new IdRange(first, first + count - 1);
+        }
+    }
+
+    public interface IStateLookup<TBase>
+    {
+        TBase Lookup(long id);
+        long LookupId(TBase t);
+    }
+
+    public interface IStateTable<in TBase>
+    {
+        void Add(long id, TBase t);
+        void RemoveById(long id);
+        void Remove(TBase t);
+        int Count { get; }
+    }
+    
+    // could just pour stuff into this... and then have an `EntityTable` extend it
+    public class SimpleStateTable<TBase> : IStateTable<TBase>, IStateLookup<TBase>
+    {
+        private readonly Dictionary<TBase, long> ReverseIndex = new Dictionary<TBase, long>();
+        private readonly Dictionary<long, TBase> Table = new Dictionary<long, TBase>();
+
+        public int Count => Table.Count;
+
+        public void Add(long id, TBase t)
+        {
+            ReverseIndex.Add(t, id);
+            Table.Add(id, t);
+        }
+
+        public TBase Lookup(long id)
+        {
+            return Table[id];
+        }
+
+        public long LookupId(TBase t)
+        {
+            return ReverseIndex[t];
+        }
+
+        public void Remove(TBase t)
+        {
+            var id = ReverseIndex[t];
+            ReverseIndex.Remove(t);
+            Table.Remove(id);
+        }
+
+        public void RemoveById(long id)
+        {
+            TBase t = Table[id];
+            ReverseIndex.Remove(t);
+            Table.Remove(id);
+        }
+        
+        public bool TryLookup(long id, out TBase result)
+        {
+            return Table.TryGetValue(id, out result);
+        }
+
+        public bool LookupId(TBase t, out long result)
+        {
+            return ReverseIndex.TryGetValue(t, out result);
+        }
+    }
+}
diff --git a/M4MCode/NetState/NetState/State/StateFactory.cs b/M4MCode/NetState/NetState/State/StateFactory.cs
new file mode 100644
index 0000000..c9a26e6
--- /dev/null
+++ b/M4MCode/NetState/NetState/State/StateFactory.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NetState.State
+{
+    public interface IStateFactory<out T, in CX>
+    {
+        T Create(CX context);
+        string UniqueName { get; }
+        Type Type { get; }
+    }
+
+    public delegate T StateFactory<out T, in CX>(CX context);
+
+    public class LambdaStateFactory<T, CX> : IStateFactory<T, CX>
+    {
+        private readonly StateFactory<T, CX> Factory;
+
+        public LambdaStateFactory(StateFactory<T, CX> factory, string uniqueName)
+        {
+            Factory = factory;
+            UniqueName = uniqueName;
+            Type = typeof(T);
+        }
+
+        public string UniqueName { get; }
+        public Type Type { get; }
+
+        public T Create(CX context)
+        {
+            return Factory(context);
+        }
+    }
+
+    public interface IStateFactoryLookup<out TBase, in CX, TKey>
+    {
+        IStateFactory<TBase, CX> Lookup(TKey key);
+    }
+
+    public class CustomFactoryLookup<TBase, CX, TKey> : IStateFactoryLookup<TBase, CX, TKey>
+    {
+        private readonly Dictionary<TKey, IStateFactory<TBase, CX>> Table = new Dictionary<TKey, IStateFactory<TBase, CX>>();
+        
+        public void Add(TKey key, IStateFactory<TBase, CX> factory)
+        {
+            Table.Add(key, factory);
+        }
+
+        public IStateFactory<TBase, CX> Lookup(TKey key)
+        {
+            return Table[key];
+        }
+    }
+
+    public class FactoryLookup<TBase, CX> : IStateFactoryLookup<TBase, CX, string>
+    {
+        private readonly Dictionary<string, IStateFactory<TBase, CX>> NameTable = new Dictionary<string, IStateFactory<TBase, CX>>();
+        
+        public void Add(IStateFactory<TBase, CX> factory)
+        {
+            NameTable.Add(factory.UniqueName, factory);
+        }
+
+        public IStateFactory<TBase, CX> Lookup(string name)
+        {
+            return NameTable[name];
+        }
+    }
+
+    // serious stuff
+
+    public interface IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo>
+    {
+        IStateFactory<TState, TStateFactoryContext> StateFactory { get; }
+        int StateFactoryId { get; }
+        T Create<T>(TStateFactoryContext context, TCreationInfo creationInfo) where T : TState;
+    }
+    
+    public interface IDestroyHandle
+    {
+        void Destroy();
+    }
+    
+    public delegate void StateCreated<TState, TCreationInfo>(TState state, TCreationInfo creationInfo);
+
+    // provides methods to lookup by name, assign by id, look up by id, and register callbacks
+    public class NegotiableAssignmentStateFactoryProvider<TState, TCreationInfo, TStateFactoryContext>
+    {
+        private class StateFactoryInfo : IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo>
+        {
+            public StateFactoryInfo(IStateFactory<TState, TStateFactoryContext> stateFactory, int stateFactoryId)
+            {
+                StateFactory = stateFactory;
+                StateFactoryId = stateFactoryId;
+            }
+
+            public IStateFactory<TState, TStateFactoryContext> StateFactory { get; }
+            public int StateFactoryId { get; }
+
+            public List<IUntypedStateFactoryCreatedCallback> Callbacks { get; } = new List<IUntypedStateFactoryCreatedCallback>();
+
+            public T Create<T>(TStateFactoryContext context, TCreationInfo creationInfo) where T : TState
+            {
+                T t = (T)StateFactory.Create(context);
+
+                foreach (var callback in Callbacks)
+                    callback.Notify<T>(t, creationInfo);
+
+                return t;
+            }
+        }
+
+        private interface IUntypedStateFactoryCreatedCallback : IDestroyHandle
+        {
+            void Notify<T>(T state, TCreationInfo creationInfo);
+        }
+
+        private class StateFactoryCreatedCallback<T> : IUntypedStateFactoryCreatedCallback
+        {
+            private StateFactoryCreatedCallback(StateFactoryInfo stateFactoryInfo, StateCreated<T, TCreationInfo> callback)
+            {
+                StateFactoryInfo = stateFactoryInfo;
+                Callback = callback;
+            }
+
+            public StateFactoryInfo StateFactoryInfo { get; }
+            public StateCreated<T, TCreationInfo> Callback { get; }
+
+            /// <summary>
+            /// hooks itself up to the SFI
+            /// </summary>
+            public static IDestroyHandle Prepare(StateFactoryInfo stateFactoryInfo, StateCreated<T, TCreationInfo> callback)
+            {
+                if (typeof(T) != stateFactoryInfo.StateFactory.Type)
+                    throw new Exception("Callback type not the same as SF type");
+
+                StateFactoryCreatedCallback<T> sfcc = new StateFactoryCreatedCallback<T>(stateFactoryInfo, callback);
+                stateFactoryInfo.Callbacks.Add(sfcc);
+
+                return sfcc;
+            }
+
+            public void Destroy()
+            {
+                StateFactoryInfo.Callbacks.Remove(this);
+            }
+
+            public void Notify<TAssumed>(TAssumed state, TCreationInfo creationInfo)
+            {
+                if (state is T)
+                {
+                    Callback((T)(object)state, creationInfo);
+                }
+                else
+                {
+                    throw new Exception("Wrong type used somewhere to do with SF " + StateFactoryInfo.StateFactory.UniqueName + " (Local-Internal SFID " + StateFactoryInfo.StateFactoryId + ")");
+                }
+            }
+        }
+        
+        private readonly IStateFactoryLookup<TState, TStateFactoryContext, string> StateFactoryLookupByName;
+
+        private readonly Dictionary<string, StateFactoryInfo> StateFactoriesByString = new Dictionary<string, StateFactoryInfo>();
+        private readonly Dictionary<int, StateFactoryInfo> StateFactoriesById = new Dictionary<int, StateFactoryInfo>();
+        private int _nextSfId = 1;
+        private int NextSfId => _nextSfId++;
+
+        public NegotiableAssignmentStateFactoryProvider(IStateFactoryLookup<TState, TStateFactoryContext, string> stateFactoryLookupByName)
+        {
+            StateFactoryLookupByName = stateFactoryLookupByName;
+        }
+
+        public IDestroyHandle RegisterOnStateCreated<T>(string sfName, StateCreated<T, TCreationInfo> callback)
+        {
+            var sfInfo = InternalGrabStateInfoByName(sfName);
+            return StateFactoryCreatedCallback<T>.Prepare(sfInfo, callback);
+        }
+        
+        public IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo> LookupStateFactoryById(int id)
+        {
+            return StateFactoriesById[id];
+        }
+
+        public IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo> AssignFactoryByName(string name)
+        {
+            return InternalAssignFactoryByName(name);
+        }
+
+        private StateFactoryInfo InternalAssignFactoryByName(string name)
+        {
+            return InternalAssignFactoryIdByName(NextSfId, name);
+        }
+
+        public IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo> AssignFactoryIdByName(int id, string name)
+        {
+            return InternalAssignFactoryIdByName(id, name);
+        }
+
+        private StateFactoryInfo InternalAssignFactoryIdByName(int id, string name)
+        {
+            var factory = StateFactoryLookupByName.Lookup(name);
+            return AssignFactory(id, factory);
+        }
+
+        private StateFactoryInfo AssignFactory(int sfId, IStateFactory<TState, TStateFactoryContext> stateFactory)
+        {
+            var sfi = new StateFactoryInfo(stateFactory, sfId);
+
+            StateFactoriesByString.Add(stateFactory.UniqueName, sfi);
+            StateFactoriesById.Add(sfId, sfi);
+
+            return sfi;
+        }
+
+        public IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo> GrabStateInfoByName(string sfName)
+        {
+            return InternalGrabStateInfoByName(sfName);
+        }
+
+        private StateFactoryInfo InternalGrabStateInfoByName(string sfName)
+        {
+            if (StateFactoriesByString.TryGetValue(sfName, out StateFactoryInfo res))
+            {
+                return res;
+            }
+            else
+            {
+                return InternalAssignFactoryByName(sfName);
+            }
+        }
+
+        public bool TryGetStateFactory(string sfName, out IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo> res)
+        {
+            if (StateFactoriesByString.TryGetValue(sfName, out StateFactoryInfo found))
+            {
+                res = found;
+                return true;
+            }
+            else
+            {
+                res = null;
+                return false;
+            }
+        }
+
+        public IEnumerable<IStateFactoryInfo<TState, TStateFactoryContext, TCreationInfo>> EnumerateAssignedStateFactories()
+        {
+            return StateFactoriesById.Values;
+        }
+
+        public int AssignedFactoryCount => StateFactoriesById.Count;
+    }
+}
diff --git a/M4MCode/NetState/NetState/State/StateLock.cs b/M4MCode/NetState/NetState/State/StateLock.cs
new file mode 100644
index 0000000..6ad7c4d
--- /dev/null
+++ b/M4MCode/NetState/NetState/State/StateLock.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NetState.State
+{
+    public class StateLock
+    {
+        System.Threading.SemaphoreSlim Semaphore = new System.Threading.SemaphoreSlim(1);
+        private int LockToken = 0;
+
+        public async Task<int> WaitAsync()
+        {
+            await Semaphore.WaitAsync().ConfigureAwait(false);
+            if (LockToken > 100)
+                LockToken = 0;
+            return LockToken;
+        }
+
+        public int Wait()
+        {
+            Semaphore.Wait();
+            if (LockToken > 100)
+                LockToken = 0;
+            return LockToken;
+        }
+
+        public void Free(int lockToken)
+        {
+            int newToken = System.Threading.Interlocked.Increment(ref LockToken); // returns incremented value
+            Semaphore.Release();
+
+            if (lockToken + 1 != newToken)
+            {
+                throw new Exception("Invalid LockToken, State may be compromised");
+            }
+        }
+    }
+}
diff --git a/M4MStuff/genSpread.ps1 b/M4MStuff/genSpread.ps1
index 6575454..90307b2 100644
--- a/M4MStuff/genSpread.ps1
+++ b/M4MStuff/genSpread.ps1
@@ -16,6 +16,26 @@ param(
   $moreParams=""
 )
 
+<# Generates experiments and scripts to run, start, and post-process them.
+
+$topdir the directory in which to generate everything
+$name the name of the spread
+$epochs the number of epochs to run
+$xtitle the outer title of the spread
+$ytitle the inner title of the spread
+$xprefix the outer prefix of the spread
+$yprefix the inner prefix pf the spread
+$xs the list of x values
+$ys the list of y values
+$paramStrFunc a function taking the x, y parameters that returns the params string
+$runName the name of the run to prepare in the starter file
+$wsperiod the wholesample sample period
+$paramFile the file from which to take the basic parameters
+$nogen set to $true so that experiments are not generate, only starter files etc.
+$moreParams an additional params string
+
+#>
+
 mkdir -f $topDir
 
 # saves a file as utf8 with no BOM
@@ -26,6 +46,7 @@ function save($fname)
   [System.IO.File]::WriteAllText($fname, $input -join "`n", $Utf8NoBomEncoding);
 }
 
+# removes the last character from a string (used to strip trailing delimiters)
 function clip($s)
 {
   return $s.Substring(0, $s.Length - 1)
@@ -102,7 +123,7 @@ $yAliases | save "$topDir/yAliases.txt"
 # IRIDIS (batch) starter
 $starter | save "$topDir/starter$name.sh"
 
-# PS start (sequencer)
+# PS start (sequential starter)
 $psStarter | save "$topDir/psStarter$name.ps1"
 
 
diff --git a/M4MStuff/readme.md b/M4MStuff/readme.md
new file mode 100644
index 0000000..f9a7fb7
--- /dev/null
+++ b/M4MStuff/readme.md
@@ -0,0 +1,17 @@
+# M4MStuff
+
+These are the powershell scripts that generate the experiments discussed in the paper.
+
+Running `. gens.ps1` will generate all the experiments, each in their own directory. Within that directory will be methods to run the experiments and to perform post-processing.
+
+Having run any experiments of interest, you can run `. paperSequences.ps1` to generate figures as per the paper: the figures appear in the directory along with the data-files.
+
+You need `m4m` in path (or as a function) to use these scripts. Assuming you have powershell and .NET Core (2.1, or 3.1, or Net 5) SDK installed, you need only run `. m4minit.ps1 netcoreapp3.1` in the M4MCode/M4M_Mk1 directory to make the `m4m` command available. If you have not already done so, you will need to compile M4M by running `netstateb` and `m4mb` in that order. You can choose which version of .NET to use with the parameter to `m4minit.ps1`; it can be any of `netcoreapp2.1`, `netcoreapp3.1`, and `net5`.
+
+There is another readme in the M4MCode/M4M_Mk1 directory that describes many more things, but the above commands are all that suffice to reproduce the sub-figures from the paper.
+
+Misc notes:
+ - If you run an experiment, and are horrified by how many files it produces, you can use the `m4mclean` command to clean up a bit. The `m4mdeepclean` may be more aggressive.
+ - You can download the latest .NET SDK for Windows/Linux/MaxOS from https://dotnet.microsoft.com/download
+ - You can install powershell (pwsh) with .NET by running `dotnet tool install -g powershell` (this works on windows or linux); if you can't install tools globally, then consult https://docs.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use or install powershell some other way
+ - Starter scripts are generated for the IRIDIS5 computing cluser, but you'll need to modify `genSpread` (Ctrl-F for "# IRIDIS group runner")
\ No newline at end of file
diff --git a/readme.md b/readme.md
index a5e423b..a24896e 100644
--- a/readme.md
+++ b/readme.md
@@ -1,12 +1,16 @@
 # IVMCTrim
 
-This is a (somewhat) minimal implementation of a HillClimber/IVMC experiment from M4M. It runs exactly the same experiments (given the same parameters), producing the same output (which requires a couple of weird implementations details, but nothing too painful).
+This is a (somewhat) minimal implementation of a HillClimber/IVMC experiment from M4M. It runs exactly the same experiments given the same parameters (which requires a couple of weird implementations details, but nothing too painful).
+
+If you want to reproduce the exact figures from the paper, then look at the readme in the M4MStuff directory.
+
+If you want the joy of using M4M for something novel, then you must face the readme in the M4MCode/M4M_Mk1 directory.
 
 ## Dependencies
 
 ### Runtime
 
-IVMCTrim depends on .NET Core 2.1, .NET Core 3.1, or .NET 5.0. You need an SDK for one of these in order to run it from the source code (which is what is supplied in this repository): a Runtime alone will not let you build the program.
+Everything depends on .NET Core 2.1, .NET Core 3.1, or .NET 5.0. You need an SDK for one of these in order to run it from the source code (which is what is supplied in this repository): a Runtime alone will not let you build the program.
 
 You can download the latest .NET SDK for Windows/Linux/MaxOS from https://dotnet.microsoft.com/download
 
@@ -32,15 +36,15 @@ This will run each of the experiments called in the `Program.Main` method. Each
 
 ## Notes
 
-The idea behind IVMCTrim is that it is possible to understand/mess with the model quickly, instead of having to trawl through M4M looking for the right place to plug in an `IComposer` or whatever, only to find that your code doesn't serialise properly. If you want to understand the model or just want to reproduce existing results, IVMCTrim is the place to look. If you want to perform serious analysis, then you will want M4M.
+The idea behind IVMCTrim is that it is possible to understand/mess with the model quickly, instead of having to trawl through M4M looking for the right place to plug in an `IComposer` or whatever, only to find that your code doesn't serialise properly. If you want to understand the model or just want to reproduce qualatitive, IVMCTrim is the place to look. If you want to perform extensive analysis, then you will want M4M.
 
-In order to ensure the simulations are exactly the same as those run under M4M, the order and method by which random numbers are generated must be exactly the same. This ordering is fairly easy to see in IVMCTrim.
+In order to ensure the simulations are exactly the same as those run under M4M, the order and method by which random numbers are generated must be exactly the same. This ordering is fairly easy to see in IVMCTrim (less so in M4M).
 
 .NET does not enforce a consistent floating point implementation across platforms, so it is possible that the same seed will produce different outputs on different machines; however, this has not been observed on a cross section of modern and not-so-modern computers.
 
 ## Help / More / Contact
 
-If you run into trouble or want access to M4M (the nightmare inducing program that ran the original experiments in the paper) then contact fjn1g13@soton.ac.uk. It is not made available 'by default' due to the complexity of the implementation, the many unfinished and broken components it contains, and the complexity involved in using it: we believe it is better to supply an understandable implementation, but if you want to reproduce the figures from the paper exactly or otherwise mess with the model/analysis, then I'll be happy to help.
+If you run into trouble or have questions then contact fjn1g13@soton.ac.uk.
 
 ## FAQ
 
-- 
GitLab