diff --git a/.gitlab-ci.py b/.gitlab-ci.py
new file mode 100755
index 0000000000000000000000000000000000000000..d35b143fdc1233c041f7a482bf593322c9b70de9
--- /dev/null
+++ b/.gitlab-ci.py
@@ -0,0 +1,83 @@
+#! /usr/bin/env python3
+
+from os import environ as env
+from platform import system
+from subprocess import check_call, check_output
+from argparse import ArgumentParser
+
+
+def define(key: str, value: str):
+    return ['-D', f'{key}={value}']
+
+
+def define_env(name: str):
+    return define(name, env[name]) if name in env else []
+
+
+if system() == 'Windows':
+
+    def setup_msvc():
+        msvc_path = 'C:/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/Common7/Tools'
+        lines = check_output(
+            [
+                'cmd',
+                '/c',
+                'VsDevCmd.bat',
+                '-arch=amd64',
+                f'-vcvars_ver={env["VCVARS_VER"]}',
+                '&',
+                'set'
+            ],
+            cwd=msvc_path
+        ).decode('utf-8').splitlines()
+        for line in lines:
+            split = line.split('=')
+            if len(split) != 2:
+                continue
+            key, value = split
+            if key in env and env[key] == value:
+                continue
+            env[key] = value
+
+
+def build(args):
+    if system() == 'Windows':
+        setup_msvc()
+
+    command = ['cmake']
+    command += ['-S', '.']
+    command += ['-B', 'build']
+    command += ['-G', 'Ninja']
+    command += define_env('CMAKE_BUILD_TYPE')
+    command += define('CMAKE_SUPPRESS_REGENERATION', 'ON')
+    command += define('CMAKE_SKIP_PACKAGE_ALL_DEPENDENCY', 'ON')
+
+    if system() == 'Windows':
+        command += define(
+            'CMAKE_TOOLCHAIN_FILE',
+            env['VCPKG_DIR'] + '/scripts/buildsystems/vcpkg.cmake'
+        )
+        command += define('VCPKG_TARGET_TRIPLET', env['VCPKG_TRIPLET'])
+    check_call(command)
+
+    command = ['ninja', '-C', 'build']
+    check_call(command)
+
+
+def package(args):
+    command = ['ninja', '-C', 'build', 'package']
+    check_call(command)
+
+
+if __name__ == '__main__':
+    parser = ArgumentParser()
+    subparsers = parser.add_subparsers()
+
+    build_parser = subparsers.add_parser('build')
+    build_parser.set_defaults(task=build)
+
+    package_parser = subparsers.add_parser('package')
+    package_parser.set_defaults(task=package)
+
+    args = parser.parse_args()
+    args.task(args)
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1b5d7e5e5c30928eca8e6071840475df2bbbd239..630b4171f0819757679db6498ace521d309c7f5d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,9 +8,9 @@ stages:
   tags: [ linux, docker ]
   image: git.imp.fu-berlin.de:5000/bioroboticslab/robofish/docker:devel-ubuntu18.04
 
-.windows:
-  tags: [ windows, docker ]
-  image: git.imp.fu-berlin.de:5000/bioroboticslab/robofish/docker:devel-windows
+.windows-1809:
+  tags: [ windows-1809, docker ]
+  image: git.imp.fu-berlin.de:5000/bioroboticslab/robofish/docker:devel-windows1809
 
 
 .gcc8: &gcc8
@@ -18,49 +18,41 @@ stages:
   CXX: g++-8
 
 .msvc15.9: &msvc15_9
-  VSDevEnv -arch=amd64 -vcvars_ver="14.16"
-
-.debug: &debug
-  CMAKE_BUILD_TYPE: Debug
+  VCVARS_VER: '14.16'
 
 .release: &release
   CMAKE_BUILD_TYPE: Release
 
+.debug: &debug
+  CMAKE_BUILD_TYPE: Debug
 
-.build ubuntu-18.04:
-  extends: .ubuntu-18.04
-  stage: build
-  artifacts:
-    paths:
-      - build
-    expire_in: 1 day
-  script:
-    - cmake -Bbuild -H. -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" -G Ninja -DCMAKE_SUPPRESS_REGENERATION=ON -DCMAKE_SKIP_PACKAGE_ALL_DEPENDENCY=ON
-    - ninja -C build
 
-.build windows:
-  extends: .windows
+.build: &build
   stage: build
   artifacts:
     paths:
       - build
     expire_in: 1 day
-  script:
-    - cmake -Bbuild "-H." -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" -G Ninja -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_DIR/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET="$env:VCPKG_TRIPLET" -DCMAKE_SUPPRESS_REGENERATION=ON -DCMAKE_SKIP_PACKAGE_ALL_DEPENDENCY=ON
-    - ninja -C build
+  script: ./.gitlab-ci.py build
 
 build ubuntu-18.04:
-  extends: .build ubuntu-18.04
+  extends: .ubuntu-18.04
+  <<: *build
   variables:
     <<: [ *gcc8, *release ]
 
-build windows:
-  extends: .build windows
+build windows-1809:
+  extends: .windows-1809
+  <<: *build
   variables:
-    <<: [ *release ]
-  before_script:
-    - . $Profile
-    - *msvc15_9
+    <<: [ *msvc15_9, *release ]
+
+build windows-1809[debug]:
+  extends: .windows-1809
+  <<: *build
+  variables:
+    <<: [ *msvc15_9, *debug ]
+
 
 .package: &package
   stage: package
@@ -68,8 +60,7 @@ build windows:
     paths:
       - build/*.tar.xz
     expire_in: 1 week
-  script:
-    - ninja -C build package
+  script: ./.gitlab-ci.py package
 
 package ubuntu-18.04:
   extends: .ubuntu-18.04
@@ -77,12 +68,19 @@ package ubuntu-18.04:
     - build ubuntu-18.04
   <<: *package
 
-package windows:
-  extends: .windows
+package windows-1809:
+  extends: .windows-1809
   dependencies:
-    - build windows
+    - build windows-1809
   <<: *package
 
+package windows-1809[debug]:
+  extends: .windows-1809
+  dependencies:
+    - build windows-1809[debug]
+  <<: *package
+
+
 trigger robofish/simulator:
   stage: deploy
   trigger:
@@ -97,4 +95,3 @@ trigger biotracker/biotracker:
   stage: deploy
   trigger:
     project: bioroboticslab/biotracker/biotracker
-
diff --git a/.style.yapf b/.style.yapf
new file mode 100644
index 0000000000000000000000000000000000000000..104ea2656097a858bbc5c4ab1e3f7ac615e574c0
--- /dev/null
+++ b/.style.yapf
@@ -0,0 +1,52 @@
+[style]
+align_closing_bracket_with_visual_indent = true
+allow_multiline_lambdas = false
+allow_multiline_dictionary_keys = false
+allow_split_before_default_or_named_assigns = true
+allow_split_before_dict_value = true
+arithmetic_precedence_indication = false
+blank_line_before_nested_class_or_def = false
+blank_line_before_class_docstring = false
+blank_line_before_module_docstring = false
+blank_lines_around_top_level_definition = 2
+coalesce_brackets = false
+column_limit = 99
+continuation_align_style = 'space'
+continuation_indent_width = 4
+dedent_closing_brackets = true
+disable_ending_comma_heuristic = false
+each_dict_entry_on_separate_line = true
+i18n_comment = ''
+i18n_function_call = ''
+indent_dictionary_value = true
+indent_width = 4
+indent_blank_lines = false
+join_multiple_lines = true
+no_spaces_around_selected_binary_operators = set()
+space_between_ending_comma_and_closing_bracket = true
+spaces_around_power_operator = true
+spaces_around_default_or_named_assign = false
+spaces_before_comment = 2
+split_arguments_when_comma_terminated = false
+split_all_comma_separated_values = true
+split_before_arithmetic_operator = true
+split_before_bitwise_operator = true
+split_before_closing_bracket = true
+split_before_dict_set_generator = true
+split_before_dot = true
+split_before_expression_after_opening_paren = true
+split_before_first_argument = false
+split_before_logical_operator = true
+split_before_named_assigns = true
+split_complex_comprehension = true
+split_penalty_after_opening_bracket = 300
+split_penalty_after_unary_operator = 10000
+split_penalty_arithmetic_operator = 300
+split_penalty_before_if_expr = 0
+split_penalty_bitwise_operator = 300
+split_penalty_comprehension = 80
+split_penalty_excess_character = 7000
+split_penalty_for_added_line_split = 30
+split_penalty_import_names = 0
+split_penalty_logical_operator = 300
+use_tabs = false