Repository: NextCenturyCorporation/MCS (language: python)

Issue:
Added trace logging level.  Added call to init_logging such that it s…
To test backup init_logging:

- Run your favorite entry point  (I.E. 'run_human_input.py') and verify default logging
- Remove init_logging from your favorite entry point (be sure to revert this when you are done)
- Run your favorite entry point and verify no logging (besides print statements. Note: run_human_input has a lot of print statements which will circumvent logging configurations.)
- Create a 'log.config.user.py' file in your working directory and add a config (copy and modify from 'Logging_config.py').  I recommend adding a prefix string to the format like this: '"format": "TEST%(message)s"' to make it obvious when this file is used.
- Run your favorite entry point.  Verify your logging works. 
- Remove or rename 'log.config.user.py' so it doesn't get used continuously

To test trace:
- add a logger.trace("message") somewhere
- Run and verify no message (assuming default logging config)
- add a 'log.config.user.py' file that supports trace logging
- Run and verify your log message now appears
- Remove or rename 'log.config.user.py' so it doesn't get used continuously

Here is an override 'log.config.user.py' file that I used:
`
{
    "version": 1,
    "root": {
        "level": "TRACE",
        "handlers": ["console"],
        "propagate": False
    },
    "loggers": {
        "machine_common_sense": {
            "level": "TRACE",
            "handlers": ["console"],
            "propagate": False
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "brief",
            "level": "TRACE",
            "stream": "ext://sys.stdout"
        }
    },
    "formatters": {
        "brief": {
            "format": "TEST%(message)s"
        }
    }
}
`


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
index 110539095..c09bc7aa4 100644
--- a/.flake8
+++ b/.flake8
@@ -2,6 +2,7 @@
 ignore = E402, W504
 exclude = 
         machine_common_sense/__init__.py,
+        build,
         docs,
         setup.py,
         template_playroom.py,
diff --git a/.github/master-merge.yaml b/.github/master-merge.yaml
index 624c736ad..0747c4eb9 100644
--- a/.github/master-merge.yaml
+++ b/.github/master-merge.yaml
@@ -11,7 +11,7 @@ jobs:
       matrix:
         # we are building a pure python wheel so it should work on any platform
         os: [ubuntu-latest]
-        python-versions: [3.6]
+        python-versions: [3.7]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7a025e8e8..9d37249ff 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,4 +1,4 @@
-name: Mac and Ubuntu Python 3.6 Unit Tests 
+name: Mac/Ubuntu Python 3.7 Unit Tests 
 
 on:
   push:
@@ -19,7 +19,7 @@ jobs:
       - name: Install Python 3
         uses: actions/setup-python@v2
         with:
-          python-version: 3.6
+          python-version: 3.7
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip setuptools wheel
@@ -27,8 +27,10 @@ jobs:
       - name: Run unit tests
         run: python -m unittest
       - name: Run isort
-        uses: jamescurtin/isort-action@master
+        uses: isort/isort-action@master
+        with:
+            requirementsFiles: "requirements.txt requirements-test.txt"
       - name: Flake8
         run: flake8
       - name: Autopep8
-        run: autopep8 --in-place --aggressive --recursive machine_common_sense
\ No newline at end of file
+        run: autopep8 --in-place --aggressive --recursive machine_common_sense
diff --git a/.github/workflows/development-merge.yaml b/.github/workflows/development-merge.yaml
index d056fa6c1..1667262e8 100644
--- a/.github/workflows/development-merge.yaml
+++ b/.github/workflows/development-merge.yaml
@@ -1,4 +1,4 @@
-name: Mac and Ubuntu Python 3.6, 3.7 and 3.8 Unit Tests
+name: Mac/Ubuntu Python 3.7-3.9 Unit Tests
 
 on:
   push:
@@ -10,7 +10,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        python-versions: [3.6, 3.7, 3.8]
+        python-versions: [3.7, 3.8, 3.9]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
@@ -19,6 +19,9 @@ jobs:
         uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python-versions }}
+      - name: Install Geos for Mac
+        if: runner.os == 'macos'
+        run: brew install geos
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip setuptools wheel
@@ -28,4 +31,4 @@ jobs:
       - name: Flake8
         run: flake8
       - name: Autopep8
-        run: autopep8 --in-place --aggressive --recursive machine_common_sense
\ No newline at end of file
+        run: autopep8 --in-place --aggressive --recursive machine_common_sense
diff --git a/.github/workflows/docs-publish-pypi.yaml b/.github/workflows/docs-publish-pypi.yaml
index e5e757168..e135a209e 100644
--- a/.github/workflows/docs-publish-pypi.yaml
+++ b/.github/workflows/docs-publish-pypi.yaml
@@ -25,7 +25,7 @@ jobs:
       matrix:
         # we are building a pure python wheel so it should work on any platform
         os: [ubuntu-latest]
-        python-versions: [3.6]
+        python-versions: [3.7]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
diff --git a/.gitignore b/.gitignore
index c00f0f5d8..662a2ad41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
 .venv
 env
 venv
+venv*
 ENV/
 env.bak/
 venv.bak/
diff --git a/CPU_Container.dockerfile b/CPU_Container.dockerfile
index fa71844ce..e944f5b8f 100644
--- a/CPU_Container.dockerfile
+++ b/CPU_Container.dockerfile
@@ -8,8 +8,8 @@ LABEL maintainer="Next Century Corporation"
 ARG DEBIAN_FRONTEND=noninteractive
 ENV TZ=America/New_York
 
-# --build-arg mcsversion=0.0.x to override default in docker build command
-ARG mcsversion=0.4.6
+# --build-arg mcsversion=x.y.z to override default in docker build command
+ARG mcsversion=0.5.0
 ARG mcs_library_version=master
 
 WORKDIR /mcs
diff --git a/Dockerfile b/Dockerfile
index 46e787326..41398b4f2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,8 +18,8 @@ ARG DEBIAN_FRONTEND=noninteractive
 ENV NVIDIA_DRIVER_CAPABILITIES ${NVIDIA_DRIVER_CAPABILITIES},display
 # ENV LANG C.UTF-8
 
-# --build-arg mcsversion=0.0.x to override default in docker build command
-ARG mcsversion=0.4.6
+# --build-arg mcsversion=x.y.z to override default in docker build command
+ARG mcsversion=0.5.0
 ARG mcs_library_version=master
 
 WORKDIR /mcs
diff --git a/README.md b/README.md
index 8156a5364..bf70c4b8b 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 
 # MCS Python Package
 
-Python interface for interacting with MCS AI2Thor environment and running scenes. The latest release of the MCS Python library is `0.4.6`. You can find the latest documentation [here](https://nextcenturycorporation.github.io/MCS).
+Python interface for interacting with MCS AI2Thor environment and running scenes. The latest release of the MCS Python library is `0.5.0`. You can find the latest documentation [here](https://nextcenturycorporation.github.io/MCS).
 
 - [Quickstart Installation](#quickstart-installation)
 - [Usage](#usage)
@@ -17,12 +17,12 @@ Python interface for interacting with MCS AI2Thor environment and running scenes
 
 ### Virtual Environments
 
-Python virtual environments are recommended when using the  Machine Common Sense package. All steps below presume the activation of the virtual environment. The developer can choose between traditional Python or Anaconda depending on need. These instructions work for Ubuntu Linux or MacOS. The MCS package has a minimum requirement of Python 3.6 regardless of Python distribution. As of 0.4.4, the MCS package will automatically download the Unity app release for that version. On the first run, the Unity app may take a while to start in order to download all the assets. 
+Python virtual environments are recommended when using the  Machine Common Sense package. All steps below presume the activation of the virtual environment. The developer can choose between traditional Python or Anaconda depending on need. These instructions work for Ubuntu Linux or MacOS. The MCS package has a minimum requirement of Python 3.7 regardless of Python distribution. As of 0.4.4, the MCS package will automatically download the Unity app release for that version. On the first run, the Unity app may take a while to start in order to download all the assets. 
 
 #### Traditional Python Environment
 
 ```bash
-$ python3.6 -m venv venv
+$ python3.7 -m venv --prompt mcs venv
 $ source venv/bin/activate
 (venv) $ python -m pip install --upgrade pip setuptools wheel
 ```
diff --git a/docs/source/api.rst b/docs/source/api.rst
index 0bd5d5a02..dd206d965 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -11,7 +11,6 @@ Python API
    machine_common_sense.GoalMetadata
    machine_common_sense.Material
    machine_common_sense.ObjectMetadata
-   machine_common_sense.Pose
    machine_common_sense.ReturnStatus
    machine_common_sense.Reward
    machine_common_sense.SerializerJson
diff --git a/docs/source/containerize.rst b/docs/source/containerize.rst
index 02e05209f..6c47d9da4 100644
--- a/docs/source/containerize.rst
+++ b/docs/source/containerize.rst
@@ -4,7 +4,7 @@ Containerization
 GPU Image
 ---------
 
-Please note that the GPU image requires an Nvidia GPU and Nvidia Docker to be installed.
+GPU images requires an Nvidia GPU and Nvidia Docker to be installed.
 
 You can run the GPU image with:
 
diff --git a/docs/source/dev.rst b/docs/source/dev.rst
index 427c28d5e..16984caa2 100644
--- a/docs/source/dev.rst
+++ b/docs/source/dev.rst
@@ -9,7 +9,7 @@ Create a Virtual Environment
 
 .. code-block:: console
 
-    $ python3.6 -m venv --prompt mcs venv
+    $ python3.7 -m venv --prompt mcs venv
     $ source venv/bin/activate
     (mcs) $ python -m pip install --upgrade pip setuptools wheel
 
@@ -122,7 +122,7 @@ Outlined here is a list of config file options that can be used **in addition**
 
 To use a specific configuration, you can either pass in a file path or dictionary of values via the `config_file_or_dict` in the create_controller() method, or set the `MCS_CONFIG_FILE_PATH` environment variable to the path of your MCS configuration file (note that the configuration must be an INI file -- see `sample_config.ini <https://github.com/NextCenturyCorporation/MCS/blob/master/sample_config.ini>`_ for an example).
 
-Please note that the `MCS_CONFIG_FILE_PATH` environment variable is meant for use by the TA2 team during evaluation.
+The `MCS_CONFIG_FILE_PATH` environment variable is meant for use by the TA2 team during evaluation.
 
 Developer Config File Properties
 ********************************
diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index 6880e593b..c256d15cf 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -6,7 +6,7 @@ Here are some example usage snippets for different use cases. Note that there ar
 Using your own config data and/or unity build
 ----------------------------------------------
 
-Please note that for the below example, make sure `MCS_CONFIG_FILE_PATH` is not set, since it will override the `config_file_or_dict` specified below and is meant more for internal use by TA2 during evaluation:
+For the below example, make sure `MCS_CONFIG_FILE_PATH` is not set, since it will override the `config_file_or_dict` specified below and is meant more for internal use by TA2 during evaluation:
 
 .. code-block:: python
 
diff --git a/docs/source/install.rst b/docs/source/install.rst
index c9b413fc9..2afd659b8 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -2,20 +2,20 @@
 Installation and Setup
 =======================
 
-.. _Download and unzip the Mac ZIP: https://github.com/NextCenturyCorporation/MCS/releases/download/0.4.6/MCS-AI2-THOR-Unity-App-v0.4.6-mac.zip
-.. _Download and unzip the Linux ZIP: https://github.com/NextCenturyCorporation/MCS/releases/download/0.4.6/MCS-AI2-THOR-Unity-App-v0.4.6-linux.zip
+.. _Download and unzip the Mac ZIP: https://github.com/NextCenturyCorporation/MCS/releases/download/0.5.0/MCS-AI2-THOR-Unity-App-v0.5.0-mac.zip
+.. _Download and unzip the Linux ZIP: https://github.com/NextCenturyCorporation/MCS/releases/download/0.5.0/MCS-AI2-THOR-Unity-App-v0.5.0-linux.zip
 
 Virtual Environments
 ------------------------
 
-Python virtual environments are recommended when using the MCS package. All steps below presume the activation of the virtual environment. The developer can choose between traditional Python or Anaconda depending on need. These instructions are for Ubuntu Linux or MacOS. The Machine Common Sense package has a minimum requirement of Python 3.6 regardless of Python distribution.
+Python virtual environments are recommended when using the MCS package. All steps below presume the activation of the virtual environment. The developer can choose between traditional Python or Anaconda depending on need. These instructions are for Ubuntu Linux or MacOS. The Machine Common Sense package has a minimum requirement of Python 3.7 regardless of Python distribution.
 
 Traditional Python Environment
 *******************************
 
 .. code-block:: console
 
-    $ python3.6 -m venv venv
+    $ python3.7 -m venv venv
     $ source venv/bin/activate
     (venv) $ python -m pip install --upgrade pip setuptools wheel
 
@@ -136,9 +136,9 @@ metadata
 The `metadata` property describes what metadata will be returned by the MCS Python library. The `metadata` property is available so that users can run baseline or ablation studies during training. It can be set to one of the following strings:
 
 - `oracle`: Returns the metadata for all the objects in the scene, including visible, held, and hidden objects. Object masks will have consistent colors throughout all steps for a scene.
-- `level2`: Only returns the images (with depth maps AND object masks), camera info, and properties corresponding to the player themself (like head tilt or pose). No information about specific objects will be included. Note that here, object masks will have randomized colors per step.
-- `level1`: Only returns the images (with depth maps but NOT object masks), camera info, and properties corresponding to the player themself (like head tilt or pose). No information about specific objects will be included.
-- `none`: Only returns the images (but no depth maps or object masks), camera info, and properties corresponding to the player themself (like head tilt or pose). No information about specific objects will be included.
+- `level2`: Only returns the images (with depth maps AND object masks), camera info, and properties corresponding to the player themself (like head tilt). No information about specific objects will be included. Note that here, object masks will have randomized colors per step.
+- `level1`: Only returns the images (with depth maps but NOT object masks), camera info, and properties corresponding to the player themself (like head tilt). No information about specific objects will be included.
+- `none`: Only returns the images (but no depth maps or object masks), camera info, and properties corresponding to the player themself (like head tilt). No information about specific objects will be included.
 
 If no metadata level is set:
 - `default`: Fallback if no metadata level is specified. Only meant for use during development (evaluations will never be run this way). Includes metadata for visible and held objects in the scene, as well as camera info and properties corresponding to the player. Does not include depth maps or object masks.
@@ -164,13 +164,6 @@ save_debug_json
 
 Save AI2-THOR/Unity input, AI2-THOR/Unity output, and MCS StepMetadata output to JSON file on each step. Default: False
 
-seed
-^^^^
-
-(int, optional)
-
-A seed for the Python random number generator (defaults to None).
-
 size
 ^^^^
 
diff --git a/docs/source/remotely.rst b/docs/source/remotely.rst
index 77d114ed4..f10ba2345 100644
--- a/docs/source/remotely.rst
+++ b/docs/source/remotely.rst
@@ -2,7 +2,7 @@ Running Remotely
 ================
 
 Requirements:
-* Make sure your system has Python 3.6 or greater.
+* Make sure your system has Python 3.7 or greater.
 * Make sure MCS has been installed per the install instructions
 
 To run MCS on a remote GPU server, use the following steps to launch an X11 server.
@@ -78,7 +78,7 @@ The following code was run on an AWS p2-xlarge with the Ubuntu Deep Learning AMI
 
     cd MCS
 
-    python3.6 -m venv venv
+    python3.7 -m venv venv
     . venv/bin/activate
 
     pip install --upgrade pip
diff --git a/docs/source/schema.rst b/docs/source/schema.rst
index bfa0b7505..b9ffaa892 100644
--- a/docs/source/schema.rst
+++ b/docs/source/schema.rst
@@ -110,8 +110,8 @@ Each **object config** has the following properties:
 - `kinematic` (boolean, optional): If true, the object will ignore all forces including gravity. See Unity's `isKinematic property <https://docs.unity3d.com/ScriptReference/Rigidbody-isKinematic.html>`_. Usually paired with `structure`. Default: `false`
 - `locationParent` (string, optional): The `id` of another object in the scene. If given, this object's `shows.position` and `shows.rotation` will both start from the position and rotation of the `locationParent` object rather than from `0`. Default: none
 - `mass` (float, optional): The mass of the object, which affects the physics simulation. Default: `1`
-- `materials` (string array, optional): The material(s) (colors/textures) of the object. An object `type` may use multiple individual materials; if so, they must be listed in a specific order. Please note that most non-primitive objects already have specific material(s). See the :ref:`material list <Material List>` for options. Default: none
-- `materialFile` (string, optional): Deprecated (please use `materials` now). The material (color/texture) of the object. Please note that most non-primitive objects already have specific material(s). See the :ref:`material list <Material List>` for options. Default: none
+- `materials` (string array, optional): The material(s) (colors/textures) of the object. An object `type` may use multiple individual materials; if so, they must be listed in a specific order. Most non-primitive objects already have specific material(s). See the :ref:`material list <Material List>` for options. Default: none
+- `materialFile` (string, optional): Deprecated (please use `materials` now). The material (color/texture) of the object. Most non-primitive objects already have specific material(s). See the :ref:`material list <Material List>` for options. Default: none
 - `moveable` (boolean, optional): Whether the object should be moveable, if it is not already moveable based on its `type`. Default: depends on `type`
 - `moves` (:ref:`move config<Move Config>` array, optional): The steps on which to move the object, moving it from one position in the scene to another. The config `vector` describes the amount of position to change, added to the object's current position. Useful if you want to move objects that are `kinematic`. A fifth of each move is made over each of the five substeps (five screenshots) during the step. Default: `[]`
 - `nullParent` (:ref:`transform config <Transform Config>`, optional): Whether to wrap the object in a null parent object. Useful if you want to rotate an object by a point other than its center point. Default: none
@@ -127,7 +127,7 @@ Each **object config** has the following properties:
 - `rotates` (:ref:`move config <Move Config>` array, optional): The steps on which to rotate the object. The config `vector` describes the amount of rotation (in degrees) to change, added to the object's current rotation. Useful if you want to rotate objects that are `kinematic`. A fifth of each move is made over each of the five substeps (five screenshots) during the step. Default: `[]`
 - `salientMaterials` (string array, optional)
 - `seesaw` (bool, optional): Whether this object should move like a seesaw. Default: `false`
-- `shows` (:ref:`show config <Show Config>` array, optional): The steps on which to show the object, adding its existence to the scene. Please note that each object begins hidden within the scene, so each object should have at least one element in its `shows` array to be useful. Default: `[]`
+- `shows` (:ref:`show config <Show Config>` array, optional): The steps on which to show the object, adding its existence to the scene. Each object begins hidden within the scene, so each object should have at least one element in its `shows` array to be useful. Default: `[]`
 - `shrouds` (:ref:`step begin and end config config <Step Begin And End Config>` array, optional): The steps on which to shroud the object, temporarily making it invisible, but moving with its existing intertia and able to collide with objects. Useful if you want to have impossible events. Default: `[]`
 - `states` (string array array, optional): An array of string arrays containing the state label(s) of the object at each step in the scene, returned by the simulation environment in the object's output metadata. Default: `[]`
 - `structure` (boolean, optional): Whether the object is a structural part of the environment. Usually paired with `kinematic`. Default: `false`
@@ -1111,7 +1111,7 @@ Furniture Objects
 Primitive Objects
 *****************
 
-The following objects have a default mass of 1, base size of (x=1, y=1, z=1), and no material restrictions. You can configure them with properties like `physics`, `moveable`, `pickupable`, or `structure`. Please note that these are NOT the internal Unity primitive 3D GameObjects.
+The following objects have a default mass of 1, base size of (x=1, y=1, z=1), and no material restrictions. You can configure them with properties like `physics`, `moveable`, `pickupable`, or `structure`. These are NOT the internal Unity primitive 3D GameObjects.
 
 - `"circle_frustum"`
 - `"cone"`
diff --git a/machine_common_sense/__init__.py b/machine_common_sense/__init__.py
index 1cba21f55..58ecb6d8e 100644
--- a/machine_common_sense/__init__.py
+++ b/machine_common_sense/__init__.py
@@ -3,7 +3,9 @@
 import logging.config
 import signal
 from contextlib import contextmanager
-from os.path import exists
+from typing import Dict, Union
+
+import typeguard
 
 from ._version import __version__
 from .action import Action
@@ -14,16 +16,14 @@
 from .logging_config import LoggingConfig
 from .material import Material
 from .object_metadata import ObjectMetadata
-from .pose import Pose
 from .return_status import ReturnStatus
 from .reward import Reward
 from .scene_history import SceneHistory
-from .serializer import SerializerJson, SerializerMsgPack
-from .setup import add_subscribers
+from .serializer import SerializerMsgPack
 from .step_metadata import StepMetadata
 from .stringifier import Stringifier
+from .subscriber import add_subscribers
 from .unity_executable_provider import UnityExecutableProvider
-from .validation import Validation
 
 logger = logging.getLogger(__name__)
 # Set default logging handler to avoid "No handler found" warnings
@@ -46,8 +46,9 @@ def signal_handler(signum, frame):
         signal.alarm(0)
 
 
-def init_logging(log_config=None,
-                 log_config_file="log.config.user.py"):
+@typeguard.typechecked
+def init_logging(log_config: Dict = None,
+                 log_config_file: str = "log.config.user.py"):
     """
     Initializes logging system.  If no parameters are provided, a
     default configuration will be applied.  See python logging
@@ -58,21 +59,23 @@ def init_logging(log_config=None,
     Parameters
     ----------
     log_config : dict, optional
-        A dictionary the contains the logging configuration.  If None, a default configuration
-        will be used
+        A dictionary the contains the logging configuration.  If None, a
+        default configuration will be used
     log_config_file: str, optional
-        Path to an override configuration file.  The file will contain a python dictionary
-        for the logging configuration.  This file is typically not used, but allows a user
-        to change the logging configuration without code changes.  Default, log.config.user.py
+        Path to an override configuration file.  The file will contain a
+        python dictionary for the logging configuration.  This file is
+        typically not used, but allows a user to change the logging
+        configuration without code changes.  Default, log.config.user.py
     """
     LoggingConfig.init_logging(
         log_config=log_config,
         log_config_file=log_config_file)
 
 
-def create_controller(config_file_or_dict=None,
-                      unity_app_file_path=None,
-                      unity_cache_version=None):
+@typeguard.typechecked
+def create_controller(config_file_or_dict: Union[Dict, str] = None,
+                      unity_app_file_path: str = None,
+                      unity_cache_version: str = None):
     """
     Creates and returns a new MCS Controller object.
 
@@ -126,8 +129,9 @@ def create_controller(config_file_or_dict=None,
         return None
 
 
+@typeguard.typechecked
 def change_config(controller: Controller,
-                  config_file_or_dict=None):
+                  config_file_or_dict: Union[Dict, str] = None):
     """
     Creates and returns a new MCS Controller object.  Should only be called
     After a run and before a scene is changed.
@@ -149,7 +153,8 @@ def change_config(controller: Controller,
     add_subscribers(controller, config)
 
 
-def load_scene_json_file(scene_json_file_path):
+@typeguard.typechecked
+def load_scene_json_file(scene_json_file_path: str):
     """
     Loads the given JSON scene config file and returns its data.
 
diff --git a/machine_common_sense/_version.py b/machine_common_sense/_version.py
index ab45471df..2b8877c50 100644
--- a/machine_common_sense/_version.py
+++ b/machine_common_sense/_version.py
@@ -1,1 +1,1 @@
-__version__ = '0.4.6'
+__version__ = '0.5.0'
diff --git a/machine_common_sense/action.py b/machine_common_sense/action.py
index ef7d1b96b..b89a45610 100644
--- a/machine_common_sense/action.py
+++ b/machine_common_sense/action.py
@@ -1,6 +1,7 @@
 from enum import Enum, unique
+from typing import Tuple
 
-from .validation import Validation
+import typeguard
 
 
 @unique
@@ -65,24 +66,6 @@ class Action(Enum):
         Unexpected error; please report immediately to development team.
     """
 
-    CRAWL = (
-        "Crawl",
-        "c",
-        "Change pose to 'CRAWLING' (no params)"
-    )
-    """
-    Change pose to "CRAWLING". Can help you move underneath or over objects.
-
-    Returns
-    -------
-    "SUCCESSFUL"
-        Action successful.
-    "OBSTRUCTED"
-        If you cannot enter the pose because the path above you is obstructed.
-    "FAILED"
-        Unexpected error; please report immediately to development team.
-    """
-
     DROP_OBJECT = (
         "DropObject",
         "2",
@@ -109,22 +92,6 @@ class Action(Enum):
         Unexpected error; please report immediately to development team.
     """
 
-    LIE_DOWN = (
-        "LieDown",
-        "x",
-        "Change pose to 'LYING' (rotation=float)"
-    )
-    """
-    Change pose to "LYING". Can help you move underneath objects.
-
-    Returns
-    -------
-    "SUCCESSFUL"
-        Action successful.
-    "FAILED"
-        Unexpected error; please report immediately to development team.
-    """
-
     MOVE_AHEAD = (
         "MoveAhead",
         "w",
@@ -518,60 +485,6 @@ class Action(Enum):
         Unexpected error; please report immediately to development team.
     """
 
-    STAND = (
-        "Stand",
-        "u",
-        "Change pose to 'STANDING' (no params)"
-    )
-    """
-    Change pose to "STANDING". Can help you move over objects.
-
-    Returns
-    -------
-    "SUCCESSFUL"
-        Action successful.
-    "OBSTRUCTED"
-        If you cannot enter the pose because the path above you is obstructed.
-    "FAILED"
-        Unexpected error; please report immediately to development team.
-    """
-
-    THROW_OBJECT = (
-        "ThrowObject",
-        "q",
-        "Throw an object you are holding. (objectId=string, " +
-        "objectImageCoordsX=float, objectImageCoordsY=float, " +
-        "force=float (default:0.5))"
-    )
-    """
-    Throw an object you are holding.
-
-    Parameters
-    ----------
-    objectId : string, optional
-        The "uuid" of the held object. Defaults to the first held object.
-    objectImageCoordsX : float, optional
-        The X of a pixel coordinate on where you would like to
-        throw the object based on your current viewport.
-        (See note under "Action" header regarding image coordinates.)
-    objectImageCoordsY : float, optional
-        The Y of a pixel coordinate on where you would like to
-        throw the object based on your current viewport.
-        (See note under "Action" header regarding image coordinates.)
-
-    Returns
-    -------
-    "SUCCESSFUL"
-        Action successful.
-    "NOT_HELD"
-        If you cannot throw the object corresponding to the "objectId"
-        because you are not holding it.
-    "NOT_OBJECT"
-        If the object corresponding to the "objectId" is not an object.
-    "FAILED"
-        Unexpected error; please report immediately to development team.
-    """
-
     END_HABITUATION = (
         "EndHabituation",
         "h",
@@ -646,16 +559,17 @@ def __repr__(self):
             ', '.join([self._value_, self._key, self._desc])
         )
 
-    @ property
+    @property
     def key(self):
         return self._key
 
-    @ property
+    @property
     def desc(self):
         return self._desc
 
     @staticmethod
-    def input_to_action_and_params(input_str):
+    @typeguard.typechecked
+    def input_to_action_and_params(input_str: str) -> Tuple:
         """
         Transforms the given input string into an action string
         and parameter dict.
@@ -678,7 +592,7 @@ def input_to_action_and_params(input_str):
         action = input_split[0]
 
         try:
-            validate_action = Action(action).name  # noqa: F841
+            _ = Action(action).name
         except BaseException:
             return None, {}
 
@@ -689,11 +603,14 @@ def input_to_action_and_params(input_str):
 
         try:
             for param in input_split[1:]:
-                paramKey, paramValue = param.split('=')
-                if Validation.is_number(paramValue.strip()):
-                    params[paramKey.strip()] = float(paramValue.strip())
-                else:
-                    params[paramKey.strip()] = paramValue.strip()
+                param_key, param_value = param.split('=')
+                params[param_key.strip()] = param_value.strip()
+
+                try:
+                    params[param_key.strip()] = float(param_value.strip())
+                except ValueError:
+                    pass
+
         except BaseException:
             return action, None
 
diff --git a/machine_common_sense/config_manager.py b/machine_common_sense/config_manager.py
index 2f51493cb..d55f864ff 100644
--- a/machine_common_sense/config_manager.py
+++ b/machine_common_sense/config_manager.py
@@ -4,10 +4,11 @@
 import os
 from dataclasses import dataclass, field
 from enum import Enum, unique
-from typing import List
+from typing import Dict, List, Tuple
 
 import numpy as np
 from marshmallow import Schema, fields, post_load
+from marshmallow.decorators import post_dump
 
 from .action import Action
 from .goal_metadata import GoalMetadata
@@ -58,7 +59,6 @@ class ConfigManager(object):
     CONFIG_NOISE_ENABLED = 'noise_enabled'
     CONFIG_SAVE_DEBUG_IMAGES = 'save_debug_images'
     CONFIG_SAVE_DEBUG_JSON = 'save_debug_json'
-    CONFIG_SEED = 'seed'
     CONFIG_SIZE = 'size'
     CONFIG_TEAM = 'team'
     CONFIG_VIDEO_ENABLED = 'video_enabled'
@@ -112,6 +112,13 @@ def _validate_screen_size(self):
                 str(self.SCREEN_WIDTH_DEFAULT)
             )
 
+    def is_file_writing_enabled(self):
+        return (
+            self.is_save_debug_images() or
+            self.is_save_debug_json() or
+            self.is_video_enabled()
+        )
+
     def get_evaluation_name(self):
         return self._config.get(
             self.CONFIG_DEFAULT_SECTION,
@@ -135,13 +142,6 @@ def set_metadata_tier(self, mode):
             mode
         )
 
-    def get_seed(self):
-        return self._config.getint(
-            self.CONFIG_DEFAULT_SECTION,
-            self.CONFIG_SEED,
-            fallback=None
-        )
-
     def get_size(self):
         return self._config.getint(
             self.CONFIG_DEFAULT_SECTION,
@@ -210,6 +210,9 @@ def is_object_masks_enabled(self) -> bool:
             ]
         )
 
+    def get_screen_size(self) -> Tuple[int, int]:
+        return (self.get_screen_width(), self.get_screen_height())
+
     def get_screen_width(self) -> int:
         return self.get_size()
 
@@ -265,14 +268,14 @@ class GoalSchema(Schema):
     answer = fields.Dict()  # UI property
     category = fields.Str()
     description = fields.Str()
-    domainsInfo = fields.Dict()  # UI property
+    domains_info = fields.Dict(data_key='domainsInfo')  # UI property
     habituation_total = fields.Int()
     info_list = fields.List(fields.Str())
     last_preview_phase_step = fields.Int()
     last_step = fields.Int()
     metadata = fields.Dict()
-    objectsInfo = fields.Dict()  # UI property
-    sceneInfo = fields.Dict()  # UI property
+    objects_info = fields.Dict(data_key='objectsInfo')  # UI property
+    scene_info = fields.Dict(data_key='sceneInfo')  # UI property
     skip_preview_phase = fields.Bool()
     task_list = fields.List(fields.Str())
     type_list = fields.List(fields.Str())
@@ -283,7 +286,7 @@ def make_goal(self, data, **kwargs):
 
 
 class ChangeMaterialConfigSchema(Schema):
-    stepBegin = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
     materials = fields.List(fields.Str())
 
     @post_load
@@ -292,12 +295,12 @@ def make_change_material(self, data, **kwargs):
 
 
 class ForceConfigSchema(Schema):
-    stepBegin = fields.Int()
-    stepEnd = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
+    step_end = fields.Int(data_key='stepEnd')
     vector = fields.Nested(Vector3dSchema)
     relative = fields.Bool()
     repeat = fields.Bool()
-    stepWait = fields.Int()
+    step_wait = fields.Int(data_key='stepWait')
 
     @post_load
     def make_force_config(self, data, **kwargs):
@@ -305,11 +308,11 @@ def make_force_config(self, data, **kwargs):
 
 
 class MoveConfigSchema(Schema):
-    stepBegin = fields.Int()
-    stepEnd = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
+    step_end = fields.Int(data_key='stepEnd')
     vector = fields.Nested(Vector3dSchema)
     repeat = fields.Bool()
-    stepWait = fields.Int()
+    step_wait = fields.Int(data_key='stepWait')
 
     @post_load
     def make_move_config(self, data, **kwargs):
@@ -327,11 +330,11 @@ def make_move_config(self, data, **kwargs):
 
 class PhysicsConfigSchema(Schema):
     enable = fields.Bool()
-    angularDrag = fields.Float()
+    angular_drag = fields.Float(data_key='angularDrag')
     bounciness = fields.Float()
     drag = fields.Float()
-    dynamicFriction = fields.Float()
-    staticFriction = fields.Float()
+    dynamic_friction = fields.Float(data_key='dynamicFriction')
+    static_friction = fields.Float(data_key='staticFriction')
 
     @post_load
     def make_physics_config(self, data, **kwargs):
@@ -357,11 +360,12 @@ def make_floor_textures_config(self, data, **kwargs):
 
 
 class ShowConfigSchema(Schema):
-    stepBegin = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
     position = fields.Nested(Vector3dSchema)
     rotation = fields.Nested(Vector3dSchema)
     scale = fields.Nested(Vector3dSchema)
-    boundingBox = fields.List(fields.Dict())  # debug property
+    bounding_box = fields.List(fields.Dict(),
+                               data_key='boundingBox')  # debug property
 
     @post_load
     def make_show_config(self, data, **kwargs):
@@ -369,11 +373,11 @@ def make_show_config(self, data, **kwargs):
 
 
 class SizeConfigSchema(Schema):
-    stepBegin = fields.Int()
-    stepEnd = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
+    step_end = fields.Int(data_key='stepEnd')
     size = fields.Nested(Vector3dSchema)
     repeat = fields.Bool()
-    stepWait = fields.Int()
+    step_wait = fields.Int(data_key='stepWait')
 
     @post_load
     def make_size_config(self, data, **kwargs):
@@ -381,7 +385,7 @@ def make_size_config(self, data, **kwargs):
 
 
 class SingleStepConfigSchema(Schema):
-    stepBegin = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
 
     @post_load
     def make_single_step_config(self, data, **kwargs):
@@ -389,10 +393,10 @@ def make_single_step_config(self, data, **kwargs):
 
 
 class StepBeginEndConfigSchema(Schema):
-    stepBegin = fields.Int()
-    stepEnd = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
+    step_end = fields.Int(data_key='stepEnd')
     repeat = fields.Bool()
-    stepWait = fields.Int()
+    step_wait = fields.Int(data_key='stepWait')
 
     @post_load
     def make_step_begin_end_config(self, data, **kwargs):
@@ -400,7 +404,7 @@ def make_step_begin_end_config(self, data, **kwargs):
 
 
 class TeleportConfigSchema(Schema):
-    stepBegin = fields.Int()
+    step_begin = fields.Int(data_key='stepBegin')
     position = fields.Nested(Vector3dSchema)
 
     @post_load
@@ -421,46 +425,55 @@ def make_transform_config(self, data, **kwargs):
 class SceneObjectSchema(Schema):
     id = fields.Str()
     type = fields.Str()  # should this be an enum?
-    centerOfMass = fields.Nested(Vector3dSchema)
-    changeMaterials = fields.List(fields.Nested(ChangeMaterialConfigSchema))
+    center_of_mass = fields.Nested(Vector3dSchema, data_key='centerOfMass')
+    change_materials = fields.List(
+        fields.Nested(ChangeMaterialConfigSchema),
+        data_key='changeMaterials')
     debug = fields.Dict()
     forces = fields.List(fields.Nested(ForceConfigSchema))
     ghosts = fields.List(fields.Nested(StepBeginEndConfigSchema))
     hides = fields.List(fields.Nested(SingleStepConfigSchema))
     kinematic = fields.Bool()
-    locationParent = fields.Str()
+    location_parent = fields.Str(data_key='locationParent')
     mass = fields.Float()
     materials = fields.List(fields.Str())
-    materialFile = fields.Str()  # deprecated; please use materials
+    # deprecated; please use materials
+    material_file = fields.Str(data_key='materialFile')
     moveable = fields.Bool()
     moves = fields.List(fields.Nested(MoveConfigSchema))
-    nullParent = fields.Nested(TransformConfigSchema)
+    null_parent = fields.Nested(TransformConfigSchema, data_key='nullParent')
     openable = fields.Bool()
     opened = fields.Bool()
-    openClose = fields.List(fields.Nested(OpenCloseConfigSchema))
+    open_close = fields.List(
+        fields.Nested(OpenCloseConfigSchema),
+        data_key='openClose')
     physics = fields.Bool()
-    physicsProperties = fields.Nested(PhysicsConfigSchema)
+    physics_properties = fields.Nested(
+        PhysicsConfigSchema,
+        data_key='physicsProperties')
     pickupable = fields.Bool()
     receptacle = fields.Bool()
-    resetCenterOfMass = fields.Bool()
+    reset_center_of_mass = fields.Bool(data_key='resetCenterOfMass')
     resizes = fields.List(fields.Nested(SizeConfigSchema))
     rotates = fields.List(fields.Nested(MoveConfigSchema))
-    salientMaterials = fields.List(fields.Str())
+    salient_materials = fields.List(fields.Str(), data_key='salientMaterials')
     seesaw = fields.Bool()
     shows = fields.List(fields.Nested(ShowConfigSchema))
     shrouds = fields.List(fields.Nested(StepBeginEndConfigSchema))
     states = fields.List(fields.List(fields.Str(), allow_none=True))
     structure = fields.Bool()
     teleports = fields.List(fields.Nested(TeleportConfigSchema))
-    togglePhysics = fields.List(fields.Nested(SingleStepConfigSchema))
+    toggle_physics = fields.List(
+        fields.Nested(SingleStepConfigSchema),
+        data_key='togglePhysics')
     torques = fields.List(fields.Nested(MoveConfigSchema))
 
     # These are deprecated, but needed for Eval 3 backwards compatibility
-    canContainTarget = fields.Bool()
+    can_contain_target = fields.Bool(data_key='canContainTarget')
     obstacle = fields.Bool()
     occluder = fields.Bool()
-    positionY = fields.Float()
-    stackTarget = fields.Bool()
+    position_y = fields.Float(data_key='positionY')
+    stack_target = fields.Bool(data_key='stackTarget')
 
     @post_load
     def make_scene_object(self, data, **kwargs):
@@ -469,39 +482,63 @@ def make_scene_object(self, data, **kwargs):
 
 class SceneConfigurationSchema(Schema):
     # The passive agent scenes can have a None ceilingMaterial
-    ceilingMaterial = fields.Str(allow_none=True)
+    ceiling_material = fields.Str(allow_none=True, data_key='ceilingMaterial')
     debug = fields.Dict()
-    floorMaterial = fields.Str()
-    floorProperties = fields.Nested(PhysicsConfigSchema)
+    floor_material = fields.Str(data_key='floorMaterial')
+    floor_properties = fields.Nested(
+        PhysicsConfigSchema,
+        data_key='floorProperties')
     goal = fields.Nested(GoalSchema)
-    intuitivePhysics = fields.Bool()
+    intuitive_physics = fields.Bool(data_key='intuitivePhysics')
     isometric = fields.Bool()
     name = fields.Str()
     objects = fields.List(fields.Nested(SceneObjectSchema))
     observation = fields.Bool()  # deprecated; please use intuitivePhysics
-    performerStart = fields.Nested(PerformerStartSchema)
-    roomDimensions = fields.Nested(Vector3dSchema)
-    roomMaterials = fields.Nested(RoomMaterialsSchema)
+    performer_start = fields.Nested(
+        PerformerStartSchema,
+        data_key='performerStart')
+    room_dimensions = fields.Nested(Vector3dSchema, data_key='roomDimensions')
+    room_materials = fields.Nested(
+        RoomMaterialsSchema,
+        data_key='roomMaterials')
     screenshot = fields.Bool()  # developer use only; for the image generator
     version = fields.Integer()
-    wallMaterial = fields.Str()
-    wallProperties = fields.Nested(PhysicsConfigSchema)
+    wall_material = fields.Str(data_key='wallMaterial')
+    wall_properties = fields.Nested(
+        PhysicsConfigSchema,
+        data_key='wallProperties')
     holes = fields.List(fields.Nested(FloorHolesAndTexturesXZConfigSchema))
-    floorTextures = fields.List(fields.Nested(FloorTexturesConfigSchema))
+    floor_textures = fields.List(
+        fields.Nested(FloorTexturesConfigSchema),
+        data_key='floorTextures')
 
     # These are deprecated, but needed for Eval 3 backwards compatibility
     evaluation = fields.Str(allow_none=True)
-    evaluationOnly = fields.Bool()
-    evalName = fields.Str()
-    hypercubeNumber = fields.Int()
-    sceneNumber = fields.Int()
-    sequenceNumber = fields.Int()
+    evaluation_only = fields.Bool(data_key='evaluationOnly')
+    eval_name = fields.Str(data_key='evalName')
+    hypercube_number = fields.Int(data_key='hypercubeNumber')
+    scene_number = fields.Int(data_key='sceneNumber')
+    sequence_number = fields.Int(data_key='sequenceNumber')
     training = fields.Bool()
 
     @post_load
     def make_scene_configuration(self, data, **kwargs):
         return SceneConfiguration(**data)
 
+    @post_dump
+    def remove_none(self, d, **kwargs) -> Dict:
+        '''Remove all none's from dictionaries'''
+        for key, value in dict(d).items():
+            if isinstance(value, dict):
+                d[key] = self.remove_none(value)
+            if isinstance(value, list):
+                for index, val in enumerate(value):
+                    if isinstance(val, dict):
+                        value[index] = self.remove_none(val)
+            if value is None:
+                del d[key]
+        return d
+
 
 @dataclass
 class RoomMaterials:
@@ -523,15 +560,15 @@ class Goal:
     answer: dict = None  # UI property
     category: str = None
     description: str = None
-    domainsInfo: dict = None  # UI property
+    domains_info: dict = None  # UI property
     habituation_total: int = None
     info_list: list = None
     last_preview_phase_step: int = None
     last_step: int = None
     # TODO metadata objects
     metadata: dict = None
-    objectsInfo: dict = None  # UI property
-    sceneInfo: dict = None  # UI property
+    objects_info: dict = None  # UI property
+    scene_info: dict = None  # UI property
     skip_preview_phase: bool = False
     task_list: List[str] = None
     type_list: List[str] = None
@@ -539,27 +576,27 @@ class Goal:
 
 @dataclass
 class ChangeMaterialConfig:
-    stepBegin: int
+    step_begin: int
     materials: List[str]
 
 
 @dataclass
 class ForceConfig:
-    stepBegin: int
-    stepEnd: int
+    step_begin: int
+    step_end: int
     vector: Vector3d = Vector3d(0, 0, 0)
     relative: bool = False
     repeat: bool = False
-    stepWait: int = 0
+    step_wait: int = 0
 
 
 @dataclass
 class MoveConfig:
-    stepBegin: int
-    stepEnd: int
+    step_begin: int
+    step_end: int
     vector: Vector3d = Vector3d(0, 0, 0)
     repeat: bool = False
-    stepWait: int = 0
+    step_wait: int = 0
 
 
 @dataclass
@@ -571,11 +608,11 @@ class OpenCloseConfig:
 @dataclass
 class PhysicsConfig:
     enable: bool = False
-    angularDrag: float = None
+    angular_drag: float = None
     bounciness: float = None
     drag: float = None
-    dynamicFriction: float = None
-    staticFriction: float = None
+    dynamic_friction: float = None
+    static_friction: float = None
 
 
 @dataclass
@@ -592,38 +629,38 @@ class FloorTexturesConfig:
 
 @dataclass
 class ShowConfig:
-    stepBegin: int
+    step_begin: int
     position: Vector3d = Vector3d(0, 0, 0)
     rotation: Vector3d = Vector3d(0, 0, 0)
     scale: Vector3d = Vector3d(1, 1, 1)
-    boundingBox: List[dict] = field(default_factory=list)  # debug property
+    bounding_box: List[dict] = field(default_factory=list)  # debug property
 
 
 @dataclass
 class SizeConfig:
-    stepBegin: int
-    stepEnd: int
+    step_begin: int
+    step_end: int
     size: Vector3d = Vector3d(1, 1, 1)
     repeat: bool = False
-    stepWait: int = 0
+    step_wait: int = 0
 
 
 @dataclass
 class SingleStepConfig:
-    stepBegin: int
+    step_begin: int
 
 
 @dataclass
 class StepBeginEndConfig:
-    stepBegin: int
-    stepEnd: int
+    step_begin: int
+    step_end: int
     repeat: bool = False
-    stepWait: int = 0
+    step_wait: int = 0
 
 
 @dataclass
 class TeleportConfig:
-    stepBegin: int
+    step_begin: int
     position: Vector3d = Vector3d(0, 0, 0)
 
 
@@ -638,81 +675,81 @@ class TransformConfig:
 class SceneObject:
     id: str
     type: str  # should this be an enum?
-    centerOfMass: Vector3d = None
-    changeMaterials: List[ChangeMaterialConfig] = None
+    center_of_mass: Vector3d = None
+    change_materials: List[ChangeMaterialConfig] = None
     debug: dict = None
     forces: List[ForceConfig] = None
     ghosts: List[StepBeginEndConfig] = None
     hides: List[SingleStepConfig] = None
     kinematic: bool = None
-    locationParent: str = None
+    location_parent: str = None
     mass: float = None
     materials: List[str] = None
-    materialFile: str = None  # deprecated; please use materials
+    material_file: str = None  # deprecated; please use materials
     # Docs say moveable's default is dependant on type.  That could
     # be a problem for the concrete classes.  Needs more review later
     moveable: bool = None
     moves: List[MoveConfig] = None
-    nullParent: TransformConfig = None
+    null_parent: TransformConfig = None
     openable: bool = None
     opened: bool = None
-    openClose: List[OpenCloseConfig] = None
+    open_close: List[OpenCloseConfig] = None
     physics: bool = None
-    physicsProperties: PhysicsConfig = None
+    physics_properties: PhysicsConfig = None
     pickupable: bool = None
     receptacle: bool = None
-    resetCenterOfMass: bool = None
+    reset_center_of_mass: bool = None
     resizes: List[SizeConfig] = None
     rotates: List[MoveConfig] = None
-    salientMaterials: List[str] = None
+    salient_materials: List[str] = None
     seesaw: bool = None
     shows: List[ShowConfig] = None
     shrouds: List[StepBeginEndConfig] = None
     states: List[List[str]] = None
     structure: bool = None
     teleports: List[TeleportConfig] = None
-    togglePhysics: List[SingleStepConfig] = None
+    toggle_physics: List[SingleStepConfig] = None
     torques: List[MoveConfig] = None
 
     # These are deprecated, but needed for Eval 3 backwards compatibility
-    canContainTarget: bool = None
+    can_contain_target: bool = None
     obstacle: bool = None
     occluder: bool = None
-    positionY: float = None
-    stackTarget: bool = None
+    position_y: float = None
+    stack_target: bool = None
 
 
 @dataclass
 class SceneConfiguration:
     '''Class for keeping track of scene configuration'''
-    ceilingMaterial: str = None
+    ceiling_material: str = None
     debug: dict = None
-    floorMaterial: str = None
-    floorProperties: PhysicsConfig = None
+    floor_material: str = None
+    floor_properties: PhysicsConfig = None
     goal: Goal = None  # TODO change to concrete class
-    intuitivePhysics: bool = False
+    intuitive_physics: bool = False
     isometric: bool = False
     name: str = None
     objects: List[SceneObject] = field(default_factory=list)
     observation: bool = False  # deprecated; please use intuitivePhysics
-    performerStart: PerformerStart = None
-    roomDimensions: Vector3d = field(
+    performer_start: PerformerStart = None
+    room_dimensions: Vector3d = field(
         default=ConfigManager.DEFAULT_ROOM_DIMENSIONS)
-    roomMaterials: RoomMaterials = None
+    room_materials: RoomMaterials = None
     screenshot: bool = False  # developer use only; for the image generator
     version: int = None
-    wallMaterial: str = None
-    wallProperties: PhysicsConfig = None
+    wall_material: str = None
+    wall_properties: PhysicsConfig = None
     holes: List[FloorHolesAndTexturesXZConfig] = field(default_factory=list)
-    floorTextures: List[FloorTexturesConfig] = field(default_factory=list)
+    floor_textures: List[FloorTexturesConfig] = field(default_factory=list)
 
     # These are deprecated, but needed for Eval 3 backwards compatibility
     evaluation: str = None
-    evaluationOnly: bool = None
-    evalName: str = None
-    hypercubeNumber: int = None
-    sceneNumber: int = None
-    sequenceNumber: int = None
+    evaluation_only: bool = None
+    eval_name: str = None
+    hypercube_number: int = None
+    scene_number: int = None
+    sequence_number: int = None
     training: bool = None
 
     def retrieve_object_states(self,
diff --git a/machine_common_sense/controller.py b/machine_common_sense/controller.py
index 3431dbb04..97dfefd33 100644
--- a/machine_common_sense/controller.py
+++ b/machine_common_sense/controller.py
@@ -4,13 +4,13 @@
 import json
 import logging
 import os
-import random
-from typing import Dict, Union
+from typing import Dict, List, Optional, Union
 
 import ai2thor.controller
 import ai2thor.server
 import marshmallow
 import numpy as np
+import typeguard
 
 logger = logging.getLogger(__name__)
 
@@ -18,20 +18,17 @@
 # How far the player can reach.  I think this value needs to be bigger
 # than the MAX_MOVE_DISTANCE or else the player may not be able to move
 # into a position to reach some objects (it may be mathematically impossible).
-# TODO Reduce this number once the player can crouch down to reach and
-# pickup small objects on the floor.
-
 DEFAULT_MOVE = 0.1
 
 from .action import Action
-from .config_manager import (ConfigManager, MetadataTier, SceneConfiguration,
+from .config_manager import (ConfigManager, SceneConfiguration,
                              SceneConfigurationSchema)
 from .controller_events import (AfterStepPayload, BeforeStepPayload,
                                 EndScenePayload, EventType, StartScenePayload)
 from .controller_output_handler import ControllerOutputHandler
 from .goal_metadata import GoalMetadata
+from .parameter import Parameter
 from .step_metadata import StepMetadata
-from .validation import Validation
 
 
 def __reset_override(self, scene):
@@ -85,69 +82,24 @@ class Controller():
 
     Parameters
     ----------
-    unity_app_file: str
+    unity_app_file_path: str
     config: ConfigManager
     """
 
-    # AI2-THOR creates a square grid across the scene that is
-    # uses for "snap-to-grid" movement. (This value may not
-    # really matter because we set continuous to True in
-    # the step input.)
-    GRID_SIZE = 0.1
-
-    MAX_FORCE = 50.0
-
-    DEFAULT_HORIZON = 0
-    DEFAULT_ROTATION = 0
-    DEFAULT_FORCE = 0.5
-    DEFAULT_AMOUNT = 0.5
-    DEFAULT_IMG_COORD = 0
-    DEFAULT_OBJECT_MOVE_AMOUNT = 1
-
-    MAX_FORCE = 1
-    MIN_FORCE = 0
-    MAX_AMOUNT = 1
-    MIN_AMOUNT = 0
-
-    ROTATION_KEY = 'rotation'
-    HORIZON_KEY = 'horizon'
-    FORCE_KEY = 'force'
-    AMOUNT_KEY = 'amount'
-
-    OBJECT_IMAGE_COORDS_X_KEY = 'objectImageCoordsX'
-    OBJECT_IMAGE_COORDS_Y_KEY = 'objectImageCoordsY'
-    RECEPTACLE_IMAGE_COORDS_X_KEY = 'receptacleObjectImageCoordsX'
-    RECEPTACLE_IMAGE_COORDS_Y_KEY = 'receptacleObjectImageCoordsY'
-
-    # used for EndHabituation teleport
-    TELEPORT_X_POS = 'xPosition'
-    TELEPORT_Z_POS = 'zPosition'
-    TELEPORT_Y_ROT = 'yRotation'
-
-    # Hard coding actions that effect MoveMagnitude so the appropriate
-    # value is set based off of the action
-    # TODO: Move this to an enum or some place, so that you can determine
-    # special move interactions that way
-    FORCE_ACTIONS = ["ThrowObject", "PushObject", "PullObject"]
-    OBJECT_MOVE_ACTIONS = ["CloseObject", "OpenObject"]
-    MOVE_ACTIONS = ["MoveAhead", "MoveLeft", "MoveRight", "MoveBack"]
+    MAX_FORCE = 50.0  # DW: used for testing but had it twice
+    # once at 50.0 and the other at 1.0
 
+    @typeguard.typechecked
     def __init__(self, unity_app_file_path: str, config: ConfigManager):
 
-        self._subscribers = []
-
-        self._end_scene_not_registered = True
-
         self._controller = ai2thor.controller.Controller(
             quality='Medium',
             fullscreen=False,
-            # The headless flag does not work for me
-            headless=False,
+            headless=False,  # TODO confirm functionality
             local_executable_path=unity_app_file_path,
             width=config.get_screen_width(),
             height=config.get_screen_height(),
-            # Set the name of our Scene in our Unity app
-            scene='MCS',
+            scene='MCS',  # Unity scene name
             logs=True,
             # This constructor always initializes a scene, so add a scene
             # config to ensure it doesn't error
@@ -162,58 +114,11 @@ def __init__(self, unity_app_file_path: str, config: ConfigManager):
         self._on_init()
         self._set_config(config)
 
-    def subscribe(self, subscriber):
-        if subscriber not in self._subscribers:
-            self._subscribers.append(subscriber)
-
-    def remove_all_event_handlers(self):
-        self._subscribers = []
-
-    def _publish_event(self, event_type: EventType,
-                       payload: Union[StartScenePayload, BeforeStepPayload,
-                                      AfterStepPayload,
-                                      EndScenePayload]):
-        for subscriber in self._subscribers:
-            # TODO should we make a deep copy of the payload so subscribers
-            # cannot change source data?
-            # seems like performance vs safety
-
-            try:
-                subscriber.on_event(event_type, payload)
-            except Exception as msg:
-                logger.error(
-                    f"Error in event with type={event_type}" +
-                    f" to subscriber={type(subscriber)}",
-                    exc_info=msg)
-
-    def _create_event_payload_kwargs(self) -> dict:
-        return {"step_number": self.__step_number,
-                "config": self._config,
-                "scene_config": self._scene_config}
-
-    def _create_post_step_event_payload_kwargs(
-            self, wrapped_step, step_metadata, step_output: StepMetadata,
-            restricted_step_output: StepMetadata) -> dict:
-        args = self._create_event_payload_kwargs()
-        args['output_folder'] = self.__output_folder
-        args['timestamp'] = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
-        args['wrapped_step'] = wrapped_step
-        args['step_metadata'] = step_metadata
-        args['step_output'] = step_output
-        args['restricted_step_output'] = restricted_step_output
-        args['goal'] = self._goal
-        return args
-
-    # Pixel coordinates are expected to start at the top left, but
-    # in Unity, (0,0) is the bottom left.
-
-    def _convert_y_image_coord_for_unity(self, y_coord):
-        if(y_coord != 0):
-            return self._config.get_screen_height() - y_coord
-        else:
-            return y_coord
-
     def _on_init(self):
+        '''Set class variables after controller is initialized'''
+        self._subscribers = []
+        self._failure_handler_registered = False
+        self._end_scene_called = False
         self._goal = GoalMetadata()
         self.__habituation_trial = 1
         # Output folder used to save debug image, video, and JSON files.
@@ -221,7 +126,7 @@ def _on_init(self):
         self._scene_config = None
         self.__step_number = 0
 
-    def _set_config(self, config):
+    def _set_config(self, config: ConfigManager):
         '''Allows config to be changed without changing the controller and
         attached Unity process.  This typically should only be called by the
         MCS package itself.
@@ -229,122 +134,12 @@ def _set_config(self, config):
         For users, call machine_common_sense.change_config()
         '''
         self._config = config
-
         self._output_handler = ControllerOutputHandler(self._config)
+        self.parameter_converter = Parameter(config)
 
-        self.__noise_enabled = self._config.is_noise_enabled()
-        self.__seed = self._config.get_seed()
-
-        if self.__seed:
-            random.seed(self.__seed)
-
-    def end_scene(
-        self,
-        rating: Union[float, int, str] = None,
-        score: float = 1.0,
-        report: Dict[int, object] = None
-    ):
-        """
-        Ends the current scene.  Calling end_scene() before calling
-        start_scene() will do nothing.
-
-        Parameters
-        ----------
-        rating : float or int or string, required
-            The plausibility rating to classify a passive / VoE scene as either
-            plausible or implausible. Not used for any interactive scenes. For
-            passive agent scenes, this rating should be continuous, from 0.0
-            (completely implausible) to 1.0 (completely plausible). For other
-            passive scenes, this rating must be binary, either 0 (implausible)
-            or 1 (plausible). Please note that end-of-scene ratings are
-            required for all passive / VoE scenes. (default None)
-        score : float, optional
-            The continuous plausibility score between 0.0 (completely
-            implausible) and 1.0 (completely plausible). End-of-scene scores
-            are required for all passive / VoE scenes except agent scenes.
-            Not used for any interactive scenes or passive agent scenes.
-            (default 1.0)
-
-            Note: when an issue causes the program to exit prematurely or
-            end_scene isn't properly called but history_enabled is true,
-            this value will be written to file as -1.
-        report : Dict[int, object], optional
-            Variable for retrospective per frame reporting for passive / VoE
-            scenes. Not used for any interactive scenes or passive agent
-            scenes. (default None)
-
-            Key is an int representing a step/frame number from output step
-            metadata, starting at 1. Value or payload contains:
-
-                * rating : float or int or string, optional
-                    The plausibility rating to classify a passive / VoE scene
-                    as either plausible or implausible. Not used for any
-                    interactive scenes. For passive agent scenes, this rating
-                    should be continuous, from 0.0 (completely implausible) to
-                    1.0 (completely plausible). For other passive scenes, this
-                    rating must be binary, either 0 (implausible) or 1
-                    (plausible). Please note that frame-by-frame ratings are no
-                    longer required for any scenes (but end-of-scene ratings
-                    are). (default None)
-                * score : float, optional
-                    The continuous plausibility score between 0.0 (completely
-                    implausible) and 1.0 (completely plausible). Frame-by-frame
-                    scores are required for all passive / VoE scenes except
-                    agent scenes. Not used for any interactive scenes or
-                    passive agent scenes. (default None)
-                * violations_xy_list : List[Dict[str, float]], optional
-                    A list of one or more (x, y) locations
-                    (ex: [{"x": 1, "y": 3.4}]), each representing a potential
-                    violation-of-expectation. These locations are required for
-                    all passive / VoE scenes except agent scenes. Not used for
-                    any interactive scenes or passive agent scenes.
-                    (default None)
-                * internal_state : object, optional
-                    A properly formatted json object representing various kinds
-                    of internal states at a particular moment. Examples include
-                    the estimated position of the agent, current map of the
-                    world, etc. (default None)
-
-            Example report:
-
-            {
-
-                    1: {
-
-                        "rating": 1,
-
-                        "score": 0.75,
-
-                        "violations_xy_list": [{"x": 1,"y": 1}],
-
-                        "internal_state": {"test": "some state"}
-
-                    }
-
-            }
-
-        """
-        payloadArgs = self._create_event_payload_kwargs()
-        payloadArgs['rating'] = str(rating)
-        payloadArgs['score'] = score
-        payloadArgs['report'] = report
-
-        self._publish_event(
-            EventType.ON_END_SCENE,
-            EndScenePayload(
-                **payloadArgs))
-
-        if (not self._end_scene_not_registered):
-            atexit.unregister(self.end_scene)
-            self._end_scene_not_registered = True
-
-    def _convert_scene_config(self, config_data) -> SceneConfiguration:
-        if isinstance(config_data, SceneConfiguration):
-            return config_data
-        schema = SceneConfigurationSchema()
-        return schema.load(config_data)
-
-    def start_scene(self, config_data):
+    @typeguard.typechecked
+    def start_scene(self, config_data: Union[SceneConfiguration, Dict]) -> \
+            StepMetadata:
         """
         Starts a new scene using the given scene configuration data dict and
         returns the scene output data object.
@@ -368,11 +163,13 @@ def start_scene(self, config_data):
         self.__habituation_trial = 1
         self.__step_number = 0
         self._goal = self._scene_config.retrieve_goal()
+        self._end_scene_called = False
 
         skip_preview_phase = (scene_config.goal is not None and
                               scene_config.goal.skip_preview_phase)
 
-        if (self.isFileWritingEnabled()):
+        if (self._scene_config.name is not None and
+                self._config.is_file_writing_enabled()):
             os.makedirs('./' + scene_config.name, exist_ok=True)
             self.__output_folder = './' + scene_config.name + '/'
             file_list = glob.glob(self.__output_folder + '*')
@@ -382,12 +179,16 @@ def start_scene(self, config_data):
         sc = SceneConfigurationSchema(
             unknown=marshmallow.EXCLUDE).dump(
             scene_config)
-        sc = self._remove_none(sc)
+        """
+        All this did for conversion was to wrap a hard-coded step
         wrapped_step = self.wrap_step(
             action='Initialize',
             sceneConfig=sc
         )
-        step_output = self._controller.step(wrapped_step)
+        """
+        ai2thor_step = self.parameter_converter.wrap_step(
+            action='Initialize', sceneConfig=sc)
+        step_output = self._controller.step(ai2thor_step)
 
         self._output_handler.set_scene_config(scene_config)
         (pre_restrict_output, output) = self._output_handler.handle_output(
@@ -420,164 +221,28 @@ def start_scene(self, config_data):
 
             # TODO Should this be in the if block?  Now that we are using
             # subscribers, we may want to always register
-            if(self._end_scene_not_registered is True and
+            if(self._failure_handler_registered is False and
                     self._config.is_history_enabled()):
                 # make sure history file is written when program exits
-                atexit.register(self.end_scene, rating="", score=-1)
-                self._end_scene_not_registered = False
+                atexit.register(self.end_scene, rating=None, score=-1)
+                self._failure_handler_registered = True
 
-        payloadArgs = self._create_post_step_event_payload_kwargs(
-            wrapped_step, step_output, pre_restrict_output, output)
-        payload = StartScenePayload(**payloadArgs)
+        payload = self._create_post_step_event_payload_kwargs(
+            ai2thor_step, step_output, pre_restrict_output, output)
+        start_scene_payload = StartScenePayload(**payload)
         self._publish_event(
-            EventType.ON_START_SCENE, payload)
+            EventType.ON_START_SCENE, start_scene_payload)
 
         return output
 
-    def isFileWritingEnabled(self):
-        return self._scene_config.name is not None and (
-            self._config.is_save_debug_images() or
-            self._config.is_save_debug_json() or
-            self._config.is_video_enabled()
-        )
-
-    # TODO: may need to reevaluate validation strategy/error handling in the
-    # future
-    """
-    Need a validation/conversion step for what ai2thor will accept as input
-    to keep parameters more simple for the user (in this case, wrapping
-    rotation degrees into an object)
-    """
-
-    def validate_and_convert_params(self, action, **kwargs):
-        moveMagnitude = DEFAULT_MOVE
-        rotation = kwargs.get(self.ROTATION_KEY, self.DEFAULT_ROTATION)
-        horizon = kwargs.get(self.HORIZON_KEY, self.DEFAULT_HORIZON)
-        amount = kwargs.get(
-            self.AMOUNT_KEY,
-            self.DEFAULT_OBJECT_MOVE_AMOUNT
-            if action in self.OBJECT_MOVE_ACTIONS
-            else self.DEFAULT_AMOUNT
-        )
-        force = kwargs.get(self.FORCE_KEY, self.DEFAULT_FORCE)
-
-        objectImageCoordsX = kwargs.get(
-            self.OBJECT_IMAGE_COORDS_X_KEY, self.DEFAULT_IMG_COORD)
-        objectImageCoordsY = kwargs.get(
-            self.OBJECT_IMAGE_COORDS_Y_KEY, self.DEFAULT_IMG_COORD)
-        receptacleObjectImageCoordsX = kwargs.get(
-            self.RECEPTACLE_IMAGE_COORDS_X_KEY, self.DEFAULT_IMG_COORD)
-        receptacleObjectImageCoordsY = kwargs.get(
-            self.RECEPTACLE_IMAGE_COORDS_Y_KEY, self.DEFAULT_IMG_COORD)
-
-        if not Validation.is_number(amount, self.AMOUNT_KEY):
-            # The default for open/close is 1, the default for "Move" actions
-            # is 0.5
-            if action in self.OBJECT_MOVE_ACTIONS:
-                amount = self.DEFAULT_OBJECT_MOVE_AMOUNT
-            else:
-                amount = self.DEFAULT_AMOUNT
-
-        if not Validation.is_number(force, self.FORCE_KEY):
-            force = self.DEFAULT_FORCE
-
-        # Check object directions are numbers
-        if not Validation.is_number(
-                objectImageCoordsX,
-                self.OBJECT_IMAGE_COORDS_X_KEY):
-            objectImageCoordsX = self.DEFAULT_IMG_COORD
-
-        if not Validation.is_number(
-                objectImageCoordsY,
-                self.OBJECT_IMAGE_COORDS_Y_KEY):
-            objectImageCoordsY = self.DEFAULT_IMG_COORD
-
-        # Check receptacle directions are numbers
-        if not Validation.is_number(
-                receptacleObjectImageCoordsX,
-                self.RECEPTACLE_IMAGE_COORDS_X_KEY):
-            receptacleObjectImageCoordsX = self.DEFAULT_IMG_COORD
-
-        if not Validation.is_number(
-                receptacleObjectImageCoordsY,
-                self.RECEPTACLE_IMAGE_COORDS_Y_KEY):
-            receptacleObjectImageCoordsY = self.DEFAULT_IMG_COORD
-
-        amount = Validation.is_in_range(
-            amount,
-            self.MIN_AMOUNT,
-            self.MAX_AMOUNT,
-            self.DEFAULT_AMOUNT,
-            self.AMOUNT_KEY)
-        force = Validation.is_in_range(
-            force,
-            self.MIN_FORCE,
-            self.MAX_FORCE,
-            self.DEFAULT_FORCE,
-            self.FORCE_KEY)
-
-        # TODO Consider the current "head tilt" value while validating the
-        # input "horizon" value.
-
-        # Set the Move Magnitude to the appropriate amount based on the action
-        if action in self.FORCE_ACTIONS:
-            moveMagnitude = force * self.MAX_FORCE
-
-        if action in self.OBJECT_MOVE_ACTIONS:
-            moveMagnitude = amount
-
-        if action in self.MOVE_ACTIONS:
-            moveMagnitude = DEFAULT_MOVE
-
-        # Add in noise if noise is enable
-        if self.__noise_enabled:
-            rotation = rotation * (1 + self.generate_noise())
-            horizon = horizon * (1 + self.generate_noise())
-            moveMagnitude = moveMagnitude * (1 + self.generate_noise())
-
-        rotation_vector = {'y': rotation}
-        object_vector = {
-            'x': float(objectImageCoordsX),
-            'y': self._convert_y_image_coord_for_unity(
-                float(objectImageCoordsY)),
-        }
-
-        receptacle_vector = {
-            'x': float(receptacleObjectImageCoordsX),
-            'y': self._convert_y_image_coord_for_unity(
-                float(receptacleObjectImageCoordsY)
-            ),
-        }
-
-        teleportRotInput = kwargs.get(self.TELEPORT_Y_ROT)
-        teleportPosXInput = kwargs.get(self.TELEPORT_X_POS)
-        teleportPosZInput = kwargs.get(self.TELEPORT_Z_POS)
-
-        teleportRotation = None
-        teleportPosition = None
-
-        if teleportRotInput is not None and Validation.is_number(
-                teleportRotInput):
-            teleportRotation = {'y': kwargs.get(self.TELEPORT_Y_ROT)}
-        if (teleportPosXInput is not None and
-                Validation.is_number(teleportPosXInput) and
-                teleportPosZInput is not None and
-                Validation.is_number(teleportPosZInput)):
-            teleportPosition = {'x': teleportPosXInput, 'z': teleportPosZInput}
-        return dict(
-            objectId=kwargs.get("objectId", None),
-            receptacleObjectId=kwargs.get("receptacleObjectId", None),
-            rotation=rotation_vector,
-            horizon=horizon,
-            teleportRotation=teleportRotation,
-            teleportPosition=teleportPosition,
-            moveMagnitude=moveMagnitude,
-            objectImageCoords=object_vector,
-            receptacleObjectImageCoords=receptacle_vector
-        )
+    def _convert_scene_config(self, config_data) -> SceneConfiguration:
+        if isinstance(config_data, SceneConfiguration):
+            return config_data
+        schema = SceneConfigurationSchema()
+        return schema.load(config_data)
 
-    # Override
-    def step(self, action: str, **kwargs) -> StepMetadata:
+    @typeguard.typechecked
+    def step(self, action: str, **kwargs) -> Optional[StepMetadata]:
         """
         Runs the given action within the current scene.
 
@@ -594,6 +259,11 @@ def step(self, action: str, **kwargs) -> StepMetadata:
             The MCS output data object from after the selected action and the
             physics simulation were run. Returns None if you have passed the
             "last_step" of this scene.
+
+        Raises
+        ------
+            ValueError: If values are outside acceptable ranges or unable to
+                convert to a number.
         """
         if (self._goal.last_step is not None and
                 self._goal.last_step == self.__step_number):
@@ -610,18 +280,16 @@ def step(self, action: str, **kwargs) -> StepMetadata:
             self.__step_number)
         # Only continue with this action step if the given action and
         # parameters are in the restricted action list.
-
-        continue_with_step = False
-        for restricted_action, restricted_params in action_list:
-            if action == restricted_action:
-                # If this action doesn't have restricted parameters, or each
-                # input parameter is in the restricted parameters, it's OK.
-                if len(restricted_params.items()) == 0 or all([
+        continue_with_step = any(
+            action == restricted_action and (
+                len(restricted_params.items()) == 0 or all(
                     restricted_params.get(key) == value
                     for key, value in kwargs.items()
-                ]):
-                    continue_with_step = True
-                    break
+                )
+            )
+            for restricted_action, restricted_params in action_list
+        )
+
         if not continue_with_step:
             logger.warning(
                 f"The given action '{action}' with parameters "
@@ -635,19 +303,13 @@ def step(self, action: str, **kwargs) -> StepMetadata:
 
         self.__step_number += 1
 
-        payloadArgs = self._create_event_payload_kwargs()
-        payloadArgs['action'] = action
-        payloadArgs['habituation_trial'] = self.__habituation_trial
-        payloadArgs['goal'] = self._goal
+        payload = self._create_event_payload_kwargs()
+        payload['action'] = action
+        payload['habituation_trial'] = self.__habituation_trial
+        payload['goal'] = self._goal
         self._publish_event(
             EventType.ON_BEFORE_STEP,
-            BeforeStepPayload(**payloadArgs))
-
-        params = self.validate_and_convert_params(action, **kwargs)
-
-        # Only call mcs_action_to_ai2thor_action AFTER calling
-        # validate_and_convert_params
-        action = self.mcs_action_to_ai2thor_action(action)
+            BeforeStepPayload(**payload))
 
         if (action == 'EndHabituation'):
             self.__habituation_trial += 1
@@ -659,105 +321,144 @@ def step(self, action: str, **kwargs) -> StepMetadata:
                 "your future actions will be skipped. Please call "
                 "controller.end_scene() now.")
 
+        """
+        params = self.validate_and_convert_params(action, **kwargs)
+        action = self.mcs_action_to_ai2thor_action(action)
         step_action = self.wrap_step(action=action, **params)
-        step_output = self._controller.step(step_action)
+        """
+        ai2thor_step, params = self.parameter_converter.build_ai2thor_step(
+            action=action, **kwargs)
+        step_output = self._controller.step(ai2thor_step)
 
         (pre_restrict_output, output) = self._output_handler.handle_output(
             step_output, self._goal, self.__step_number,
             self.__habituation_trial)
 
-        payloadArgs = self._create_post_step_event_payload_kwargs(
-            step_action, step_output, pre_restrict_output, output)
-        payloadArgs['ai2thor_action'] = action
-        payloadArgs['step_params'] = params
-        payloadArgs['action_kwargs'] = kwargs
+        payload = self._create_post_step_event_payload_kwargs(
+            ai2thor_step, step_output, pre_restrict_output, output)
+        payload['ai2thor_action'] = action
+        payload['step_params'] = params
+        payload['action_kwargs'] = kwargs
         self._publish_event(
             EventType.ON_AFTER_STEP,
-            AfterStepPayload(**payloadArgs))
+            AfterStepPayload(**payload))
 
         return output
 
-    def mcs_action_to_ai2thor_action(self, action):
-        if action == Action.CLOSE_OBJECT.value:
-            # The AI2-THOR Python library has buggy error checking
-            # specifically for the CloseObject action,
-            # so just use our own custom action here.
-            return "MCSCloseObject"
+    @typeguard.typechecked
+    def end_scene(
+        self,
+        rating: Optional[float] = None,
+        score: Optional[float] = None,
+        report: Dict[int, object] = None
+    ) -> None:
+        """
+        Ends the current scene.  Calling end_scene() before calling
+        start_scene() will do nothing. Calling end_scene() twice with
+        the same scene will throw an exception.
 
-        if action == Action.DROP_OBJECT.value:
-            return "DropHandObject"
+        Parameters
+        ----------
+        rating : float, optional
+            The plausibility rating to classify a passive / VoE scene as either
+            plausible or implausible. Not used for any interactive scenes. For
+            passive agent scenes, this rating should be continuous, from 0.0
+            (completely implausible) to 1.0 (completely plausible). For other
+            passive scenes, this rating must be binary, either 0 (implausible)
+            or 1 (plausible). End-of-scene ratings are
+            required for all passive / VoE scenes. (default None)
+        score : float, optional
+            The continuous plausibility score between 0.0 (completely
+            implausible) and 1.0 (completely plausible). End-of-scene scores
+            are required for all passive / VoE scenes except agent scenes.
+            Not used for any interactive scenes or passive agent scenes.
+            (default None)
 
-        if action == Action.OPEN_OBJECT.value:
-            # The AI2-THOR Python library has buggy error checking
-            # specifically for the OpenObject action,
-            # so just use our own custom action here.
-            return "MCSOpenObject"
+            Note: when an issue causes the program to exit prematurely or
+            end_scene isn't properly called but history_enabled is true,
+            this value will be written to file as -1.
+        report : Dict[int, object], optional
+            Variable for retrospective per frame reporting for passive / VoE
+            scenes. Not used for any interactive scenes or passive agent
+            scenes. (default None)
 
-        # if action == Action.ROTATE_OBJECT_IN_HAND.value:
-        #     return "RotateHand"
+            Key is an int representing a step/frame number from output step
+            metadata, starting at 1. Value or payload contains:
 
-        return action
+                * rating : float or int, optional
+                    The plausibility rating to classify a passive / VoE scene
+                    as either plausible or implausible. Not used for any
+                    interactive scenes. For passive agent scenes, this rating
+                    should be continuous, from 0.0 (completely implausible) to
+                    1.0 (completely plausible). For other passive scenes, this
+                    rating must be binary, either 0 (implausible) or 1
+                    (plausible). Frame-by-frame ratings are no
+                    longer required for any scenes (but end-of-scene ratings
+                    are). (default None)
+                * score : float, optional
+                    The continuous plausibility score between 0.0 (completely
+                    implausible) and 1.0 (completely plausible). Frame-by-frame
+                    scores are required for all passive / VoE scenes except
+                    agent scenes. Not used for any interactive scenes or
+                    passive agent scenes. (default None)
+                * violations_xy_list : List[Dict[str, float]], optional
+                    A list of one or more (x, y) locations
+                    (ex: [{"x": 1, "y": 3.4}]), each representing a potential
+                    violation-of-expectation. These locations are required for
+                    all passive / VoE scenes except agent scenes. Not used for
+                    any interactive scenes or passive agent scenes.
+                    (default None)
+                * internal_state : object, optional
+                    A properly formatted json object representing various kinds
+                    of internal states at a particular moment. Examples include
+                    the estimated position of the agent, current map of the
+                    world, etc. (default None)
 
-    def retrieve_action_list_at_step(self, goal, step_number):
-        return goal.retrieve_action_list_at_step(step_number)
+            Example report:
 
-    def retrieve_object_states(self, object_id):
-        """Return the state list at the current step for the object with the
-        given ID from the scene configuration data, if any."""
-        self._scene_config.retrieve_object_states(
-            object_id,
-            self.__step_number)
+            {
 
-    def stop_simulation(self):
-        """Stop the 3D simulation environment. This controller won't work any
-        more."""
-        self._controller.stop()
+                    1: {
 
-    def _remove_none(self, d):
-        '''Remove all none's from dictionaries'''
-        for key, value in dict(d).items():
-            if isinstance(value, dict):
-                d[key] = self._remove_none(value)
-            if isinstance(value, list):
-                for index, val in enumerate(value):
-                    if isinstance(val, dict):
-                        value[index] = self._remove_none(val)
-            if value is None:
-                del d[key]
-        return d
-
-    def wrap_step(self, **kwargs):
-        # whether or not to randomize segmentation mask colors
-        consistentColors = False
-        metadata_tier = self._config.get_metadata_tier()
-        if(metadata_tier == MetadataTier.ORACLE):
-            consistentColors = True
-
-        # Create the step data dict for the AI2-THOR step function.
-        return dict(
-            continuous=True,
-            gridSize=self.GRID_SIZE,
-            logs=True,
-            renderDepthImage=self._config.is_depth_maps_enabled(),
-            renderObjectImage=self._config.is_object_masks_enabled(),
-            snapToGrid=False,
-            consistentColors=consistentColors,
-            **kwargs
-        )
+                        "rating": 1,
+
+                        "score": 0.75,
+
+                        "violations_xy_list": [{"x": 1,"y": 1}],
+
+                        "internal_state": {"test": "some state"}
+
+                    }
+
+            }
 
-    def generate_noise(self):
-        """
-        Returns a random value between -0.05 and 0.05 used to add noise to all
-        numerical action parameters noise_enabled is True.
-        Returns
-        -------
-        float
-            A value between -0.05 and 0.05 (using random.uniform).
         """
+        if(not self._end_scene_called):
+            payload = self._create_event_payload_kwargs()
+            payload['rating'] = rating
+            payload['score'] = score
+            payload['report'] = report
+
+            self._publish_event(
+                EventType.ON_END_SCENE,
+                EndScenePayload(
+                    **payload))
+            self._end_scene_called = True
+        else:
+            raise RuntimeError("end_scene called twice with the same scene")
 
-        return random.uniform(-0.5, 0.5)
+        if (self._failure_handler_registered):
+            atexit.unregister(self.end_scene)
+            self._failure_handler_registered = False
+
+    @typeguard.typechecked
+    def stop_simulation(self) -> None:
+        """Stop the 3D simulation environment. This controller won't work any
+        more."""
+        self._controller.stop()
 
-    def get_metadata_level(self):
+    @typeguard.typechecked
+    def get_metadata_level(self) -> str:
         """
         Returns the current metadata level set in the config. If none
         specified, returns 'default'.
@@ -768,3 +469,53 @@ def get_metadata_level(self):
             A string containing the current metadata level.
         """
         return self._config.get_metadata_tier().value
+
+    def retrieve_action_list_at_step(self, goal, step_number):
+        return goal.retrieve_action_list_at_step(step_number)
+
+    @typeguard.typechecked
+    def retrieve_object_states(self, object_id: str) -> List:
+        """Return the state list at the current step for the object with the
+        given ID from the scene configuration data, if any."""
+        return self._scene_config.retrieve_object_states(
+            object_id,
+            self.__step_number)
+
+    def subscribe(self, subscriber):
+        if subscriber not in self._subscribers:
+            self._subscribers.append(subscriber)
+
+    def remove_all_event_handlers(self):
+        self._subscribers = []
+
+    @typeguard.typechecked
+    def _publish_event(self, event_type: EventType,
+                       payload: Union[StartScenePayload, BeforeStepPayload,
+                                      AfterStepPayload,
+                                      EndScenePayload]):
+        for subscriber in self._subscribers:
+            try:
+                subscriber.on_event(event_type, payload)
+            except Exception as msg:
+                logger.error(
+                    f"Error in event with type={event_type}" +
+                    f" to subscriber={type(subscriber)}",
+                    exc_info=msg)
+
+    def _create_event_payload_kwargs(self) -> dict:
+        return {"step_number": self.__step_number,
+                "config": self._config,
+                "scene_config": self._scene_config}
+
+    def _create_post_step_event_payload_kwargs(
+            self, wrapped_step, step_metadata, step_output: StepMetadata,
+            restricted_step_output: StepMetadata) -> dict:
+        args = self._create_event_payload_kwargs()
+        args['output_folder'] = self.__output_folder
+        args['timestamp'] = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+        args['wrapped_step'] = wrapped_step
+        args['step_metadata'] = step_metadata
+        args['step_output'] = step_output
+        args['restricted_step_output'] = restricted_step_output
+        args['goal'] = self._goal
+        return args
diff --git a/machine_common_sense/controller_events.py b/machine_common_sense/controller_events.py
index d0ea8740a..a565b1c4b 100644
--- a/machine_common_sense/controller_events.py
+++ b/machine_common_sense/controller_events.py
@@ -99,7 +99,7 @@ def get_step_number(self) -> int:
         return self.step_number
 
     def get_scene_config(self) -> SceneConfiguration:
-        self.scene_configuration
+        self.scene_config
 
     def get_habituation_trial(self) -> int:
         return self.habituation_trial
diff --git a/machine_common_sense/controller_logger.py b/machine_common_sense/controller_logger.py
index db0b47f98..4ce170f8f 100644
--- a/machine_common_sense/controller_logger.py
+++ b/machine_common_sense/controller_logger.py
@@ -30,9 +30,11 @@ def on_before_step(self, payload):
         logger.debug("STEP: " + str(payload.step_number))
         logger.debug("ACTION: " + payload.action)
         if payload.goal.habituation_total >= payload.habituation_trial:
-            logger.debug(f"HABITUATION TRIAL: "
-                         f"{str(payload.habituation_trial)}"
-                         f" / {str(payload.goal.habituation_total)}")
+            logger.debug(
+                f'HABITUATION TRIAL: {payload.habituation_trial} / '
+                f'{payload.goal.habituation_total}'
+            )
+
         elif payload.goal.habituation_total > 0:
             logger.debug("HABITUATION TRIAL: DONE")
         else:
diff --git a/machine_common_sense/controller_media.py b/machine_common_sense/controller_media.py
index 45cc5359e..4ab863072 100644
--- a/machine_common_sense/controller_media.py
+++ b/machine_common_sense/controller_media.py
@@ -5,8 +5,9 @@
 import numpy as np
 import PIL
 
-from .config_manager import ConfigManager, Vector3d
+from .config_manager import ConfigManager
 from .controller_events import (AbstractControllerSubscriber,
+                                BasePostActionEventPayload,
                                 ControllerEventPayload)
 from .plotter import TopDownPlotter
 from .recorder import VideoRecorder
@@ -166,25 +167,20 @@ class TopdownVideoEventHandler(AbstractVideoEventHandler):
     writes top down video
     '''
 
-    def on_start_scene(self, payload: ControllerEventPayload):
+    def on_start_scene(self, payload: BasePostActionEventPayload):
         self.__recorder = self.create_video_recorder(
             payload, AbstractVideoEventHandler.TOPDOWN)
         self.__plotter = TopDownPlotter(
             team=payload.config.get_team(),
-            scene_name=payload.scene_config.name.replace('json', ''),
-            room_size=(
-                # Room is automatically expanded in intuitive physics scenes.
-                Vector3d(14, 10, 10) if payload.scene_config.intuitivePhysics
-                else payload.scene_config.roomDimensions
-            )
+            scene_config=payload.scene_config
         )
         self.save_video_for_step(payload)
 
-    def on_after_step(self, payload: ControllerEventPayload):
+    def on_after_step(self, payload: BasePostActionEventPayload):
         self.save_video_for_step(payload)
 
-    def save_video_for_step(self, payload: ControllerEventPayload):
-        for event in payload.step_metadata.events:
+    def save_video_for_step(self, payload: BasePostActionEventPayload):
+        for _ in payload.step_metadata.events:
             # The plotter used to be inside the for loop the same as
             # image_recorder, but it seems like it would only plot one per
             # step.
@@ -199,7 +195,7 @@ def save_video_for_step(self, payload: ControllerEventPayload):
                                        goal_id)
             self.__recorder.add(plot)
 
-    def on_end_scene(self, payload: ControllerEventPayload):
+    def on_end_scene(self, payload: BasePostActionEventPayload):
         self.__recorder.finish()
 
 
@@ -208,17 +204,16 @@ class DepthVideoEventHandler(AbstractVideoEventHandler):
     writes video of depth maps
     '''
 
-    def on_start_scene(self, payload: ControllerEventPayload):
+    def on_start_scene(self, payload: BasePostActionEventPayload):
         self.__recorder = self.create_video_recorder(
             payload, AbstractVideoEventHandler.DEPTH)
         self.save_video_for_step(payload)
 
-    def on_after_step(self, payload: ControllerEventPayload):
+    def on_after_step(self, payload: BasePostActionEventPayload):
         self.save_video_for_step(payload)
 
-    def save_video_for_step(self, payload: ControllerEventPayload):
-        for index, depth_float_array in enumerate(
-                payload.step_output.depth_map_list):
+    def save_video_for_step(self, payload: BasePostActionEventPayload):
+        for depth_float_array in payload.step_output.depth_map_list:
             max_depth = payload.step_metadata.metadata.get(
                 'clippingPlaneFar',
                 ConfigManager.DEFAULT_CLIPPING_PLANE_FAR
@@ -231,7 +226,7 @@ def save_video_for_step(self, payload: ControllerEventPayload):
             )
             self.__recorder.add(depth_map)
 
-    def on_end_scene(self, payload: ControllerEventPayload):
+    def on_end_scene(self, payload: BasePostActionEventPayload):
         self.__recorder.finish()
 
 
@@ -240,18 +235,17 @@ class SegmentationVideoEventHandler(AbstractVideoEventHandler):
     writes video for segmentation or object mask images
     '''
 
-    def on_start_scene(self, payload: ControllerEventPayload):
+    def on_start_scene(self, payload: BasePostActionEventPayload):
         self.__recorder = self.create_video_recorder(
             payload, AbstractVideoEventHandler.SEGMENTATION)
         self.save_video_for_step(payload)
 
-    def on_after_step(self, payload: ControllerEventPayload):
+    def on_after_step(self, payload: BasePostActionEventPayload):
         self.save_video_for_step(payload)
 
-    def save_video_for_step(self, payload: ControllerEventPayload):
-        for index, object_mask in enumerate(
-                payload.step_output.object_mask_list):
+    def save_video_for_step(self, payload: BasePostActionEventPayload):
+        for object_mask in payload.step_output.object_mask_list:
             self.__recorder.add(object_mask)
 
-    def on_end_scene(self, payload: ControllerEventPayload):
+    def on_end_scene(self, payload: BasePostActionEventPayload):
         self.__recorder.finish()
diff --git a/machine_common_sense/controller_output_handler.py b/machine_common_sense/controller_output_handler.py
index ffaceac81..e17c30e4b 100644
--- a/machine_common_sense/controller_output_handler.py
+++ b/machine_common_sense/controller_output_handler.py
@@ -1,6 +1,7 @@
 import copy
 import logging
 from dataclasses import dataclass
+from typing import Tuple
 
 import numpy as np
 import PIL
@@ -10,7 +11,6 @@
 from .controller import DEFAULT_MOVE
 from .material import Material
 from .object_metadata import ObjectMetadata
-from .pose import Pose
 from .return_status import ReturnStatus
 from .reward import Reward
 from .step_metadata import StepMetadata
@@ -32,7 +32,7 @@ def __post_init__(self):
         self.depth_map_list = []
         self.object_mask_list = []
 
-        for index, event in enumerate(self.events):
+        for event in self.events:
             if hasattr(event, 'frame'):
                 scene_image = PIL.Image.fromarray(event.frame)
                 self.image_list.append(scene_image)
@@ -77,6 +77,10 @@ def rotation(self) -> float:
     def camera_field_of_view(self):
         return self._raw_output.metadata.get('fov', 0.0)
 
+    @property
+    def clipping_plane(self):
+        return (self.clipping_plane_near, self.clipping_plane_far)
+
     @property
     def clipping_plane_near(self):
         return self._raw_output.metadata.get('clippingPlaneNear', 0.0)
@@ -148,24 +152,12 @@ def return_status(self):
                 ].name
         except KeyError:
             logger.error(
-                "Return status {self._raw_output.metadata['lastActionStatus']}"
+                f"Return status "
+                f"{self._raw_output.metadata['lastActionStatus']}"
                 " is not currently supported.")
         finally:
             return return_status
 
-    @property
-    def pose(self) -> str:
-        pose = Pose.UNDEFINED.name
-
-        try:
-            pose = Pose[self._raw_output.metadata['pose']].name
-        except KeyError:
-            logger.error(
-                "Pose {scene_event.metadata['pose']}"
-                " is not currently supported.")
-        finally:
-            return pose
-
     @property
     def object_colors(self):
         # Use the color map for the final event (though they should all be the
@@ -262,50 +254,18 @@ def handle_output(self, raw_output, goal, step_number, habituation_trial):
             goal, habituation_trial, True)
         return (unrestricted, restricted)
 
-    # used to be wrap output
     def _get_step_metadata(self, goal, habituation_trial,
                            restricted=True) -> StepMetadata:
-        metadata_tier = self._config.get_metadata_tier()
-        restrict_depth_map = (
-            restricted and
-            metadata_tier == MetadataTier.NONE
-        )
-
-        restrict_object_mask_list = (
-            restricted and
-            (metadata_tier == MetadataTier.NONE or
-             metadata_tier == MetadataTier.LEVEL_1)
-        )
-
-        restrict_non_oracle = (
-            restricted and
-            (metadata_tier == MetadataTier.NONE or
-             metadata_tier == MetadataTier.LEVEL_1 or
-             metadata_tier == MetadataTier.LEVEL_2)
-        )
-
-        depth_map_list = [] if restrict_depth_map else (
-            self._scene_event.depth_map_list)
-        image_list = self._scene_event.image_list
-        object_mask_list = ([] if restrict_object_mask_list else
-                            self._scene_event.object_mask_list)
-
-        objects = self._scene_event.objects
-        agent = self._scene_event.agent
+        (restrict_depth_map, restrict_object_mask_list, restrict_non_oracle) =\
+            self.get_restrictions(restricted, self._config.get_metadata_tier())
         step_output = StepMetadata(
-            action_list=goal.retrieve_action_list_at_step(
-                self._step_number
-            ),
-            camera_aspect_ratio=(
-                self._config.get_screen_width(),
-                self._config.get_screen_height()),
-            camera_clipping_planes=(
-                self._scene_event.clipping_plane_near,
-                self._scene_event.clipping_plane_far
-            ),
+            action_list=goal.retrieve_action_list_at_step(self._step_number),
+            camera_aspect_ratio=self._config.get_screen_size(),
+            camera_clipping_planes=self._scene_event.clipping_plane,
             camera_field_of_view=self._scene_event.camera_field_of_view,
             camera_height=self._scene_event.camera_height,
-            depth_map_list=depth_map_list,
+            depth_map_list=[] if restrict_depth_map else (
+                self._scene_event.depth_map_list),
             goal=goal,
             habituation_trial=(
                 habituation_trial
@@ -313,19 +273,19 @@ def _get_step_metadata(self, goal, habituation_trial,
                 else None
             ),
             head_tilt=self._scene_event.head_tilt,
-            image_list=image_list,
+            image_list=self._scene_event.image_list,
             object_list=(
                 [] if restrict_non_oracle else self._scene_event.object_list),
-            object_mask_list=object_mask_list,
-            pose=self._scene_event.pose,
+            object_mask_list=([] if restrict_object_mask_list else
+                              self._scene_event.object_mask_list),
             position=(
                 None if restrict_non_oracle else self._scene_event.position),
             performer_radius=self._scene_event.performer_radius,
             performer_reach=self._scene_event.performer_reach,
             return_status=self._scene_event.return_status,
             reward=Reward.calculate_reward(
-                goal, objects, agent, self._step_number,
-                self._scene_event.performer_reach),
+                goal, self._scene_event.objects, self._scene_event.agent,
+                self._step_number, self._scene_event.performer_reach),
             rotation=(
                 None if restrict_non_oracle else self._scene_event.rotation),
             step_number=self._step_number,
@@ -335,25 +295,45 @@ def _get_step_metadata(self, goal, habituation_trial,
                                     self._scene_event.structural_object_list)
         )
 
-        # This is here to retain the exact outputs as before
-        # If None is passed into the constructor, it is turned into
-        # {}, but restricted mode turns it to None.
         if (restrict_non_oracle):
-            step_output.position = None
-
-        if (restrict_non_oracle):
-            target_name_list = ['target', 'target_1', 'target_2']
-
-            for target_name in target_name_list:
-                if (target_name in step_output.goal.metadata):
-                    step_output.goal = copy.deepcopy(
-                        step_output.goal)
-                    if 'image' in step_output.goal.metadata[target_name]:
-                        step_output.goal.metadata[target_name]['image'] = None
-                    if 'id' in step_output.goal.metadata[target_name]:
-                        step_output.goal.metadata[target_name]['id'] = None
-                    if 'image_name' in step_output.goal.metadata[target_name]:
-                        step_output.goal.metadata[
-                            target_name]['image_name'] = None
+            self.filter_step_output(step_output)
 
         return step_output
+
+    def get_restrictions(self, restricted, metadata_tier) -> Tuple:
+        restrict_depth_map = (
+            restricted and
+            metadata_tier == MetadataTier.NONE
+        )
+
+        restrict_object_mask_list = restricted and metadata_tier in [
+            MetadataTier.NONE,
+            MetadataTier.LEVEL_1,
+        ]
+
+        restrict_non_oracle = restricted and metadata_tier in [
+            MetadataTier.NONE,
+            MetadataTier.LEVEL_1,
+            MetadataTier.LEVEL_2,
+        ]
+        return (restrict_depth_map, restrict_object_mask_list,
+                restrict_non_oracle)
+
+    def filter_step_output(self, step_output):
+        # This is here to retain the exact outputs as before
+        # If None is passed into the constructor, it is turned into
+        # {}, but restricted mode turns it to None.
+        step_output.position = None
+
+        target_name_list = ['target', 'target_1', 'target_2']
+        for target_name in target_name_list:
+            if (target_name in step_output.goal.metadata):
+                step_output.goal = copy.deepcopy(
+                    step_output.goal)
+                if 'image' in step_output.goal.metadata[target_name]:
+                    step_output.goal.metadata[target_name]['image'] = None
+                if 'id' in step_output.goal.metadata[target_name]:
+                    step_output.goal.metadata[target_name]['id'] = None
+                if 'image_name' in step_output.goal.metadata[target_name]:
+                    step_output.goal.metadata[
+                        target_name]['image_name'] = None
diff --git a/machine_common_sense/goal_metadata.py b/machine_common_sense/goal_metadata.py
index 4d564767b..7a795de29 100644
--- a/machine_common_sense/goal_metadata.py
+++ b/machine_common_sense/goal_metadata.py
@@ -1,4 +1,7 @@
 from enum import Enum, unique
+from typing import List
+
+import typeguard
 
 from .action import Action
 from .stringifier import Stringifier
@@ -107,7 +110,8 @@ def __iter__(self):
         # yield 'type_list', self.type_list
         yield 'metadata', self.metadata
 
-    def retrieve_action_list_at_step(self, step_number):
+    @typeguard.typechecked
+    def retrieve_action_list_at_step(self, step_number: int) -> List:
         """Return the action list from the given goal at the given step as a
         a list of actions tuples by default."""
         if self is not None and self.action_list is not None:
@@ -116,9 +120,11 @@ def retrieve_action_list_at_step(self, step_number):
             if self.last_step is not None and step_number == self.last_step:
                 return []
             adjusted_step = step_number - self.last_preview_phase_step
-            if len(self.action_list) > adjusted_step:
-                if len(self.action_list[adjusted_step]) > 0:
-                    return self.action_list[adjusted_step]
+            if (
+                len(self.action_list) > adjusted_step and
+                len(self.action_list[adjusted_step]) > 0
+            ):
+                return self.action_list[adjusted_step]
 
         return GoalMetadata.ACTION_LIST
 
diff --git a/machine_common_sense/history_writer.py b/machine_common_sense/history_writer.py
index f57476731..a4b48c081 100644
--- a/machine_common_sense/history_writer.py
+++ b/machine_common_sense/history_writer.py
@@ -4,6 +4,8 @@
 import pathlib
 from time import perf_counter
 
+from numpyencoder import NumpyEncoder
+
 from machine_common_sense.step_metadata import StepMetadata
 
 from .controller_events import (AbstractControllerSubscriber, AfterStepPayload,
@@ -73,21 +75,21 @@ def on_end_scene(self, payload: EndScenePayload):
             # Loop back and fill out previous steps with retrospective report
             if payload.report is not None:
                 for step in self.__history_writer.current_steps:
-                    currentStep = step.get("step")
+                    current_step = step.get("step")
 
-                    findStepInReport = (
-                        payload.report.get(currentStep) or
-                        payload.report.get(str(currentStep))
+                    report_step = (
+                        payload.report.get(current_step) or
+                        payload.report.get(str(current_step))
                     )
 
-                    if (findStepInReport is not None):
+                    if (report_step is not None):
                         # Use classification and confidence rather than rating
                         # and score to be compatible with old history files.
-                        step["classification"] = findStepInReport.get("rating")
-                        step["confidence"] = findStepInReport.get("score")
-                        step["violations_xy_list"] = findStepInReport.get(
+                        step["classification"] = report_step.get("rating")
+                        step["confidence"] = report_step.get("score")
+                        step["violations_xy_list"] = report_step.get(
                             "violations_xy_list")
-                        step["internal_state"] = findStepInReport.get(
+                        step["internal_state"] = report_step.get(
                             "internal_state")
 
             self.__history_writer.write_history_file(
@@ -115,7 +117,7 @@ def __init__(self, scene_config_data=None, hist_info={}, timestamp=''):
         scene_name = scene_config_data.name
         prefix_directory = None
         if '/' in scene_name:
-            prefix, scene_basename = scene_name.rsplit('/', 1)
+            prefix, _ = scene_name.rsplit('/', 1)
             prefix_directory = os.path.join(self.HISTORY_DIRECTORY, prefix)
             if not os.path.exists(prefix_directory):
                 logger.debug(f"Making prefix directory {prefix_directory}")
@@ -134,7 +136,10 @@ def write_file(self):
         if self.scene_history_file:
             logger.info(f"Saving history file {self.scene_history_file}")
             with open(self.scene_history_file, "a+") as history_file:
-                history_file.write(json.dumps(self.history_obj))
+                history_file.write(
+                    json.dumps(
+                        self.history_obj,
+                        cls=NumpyEncoder))
 
     def filter_history_output(
             self,
diff --git a/machine_common_sense/logging_config.py b/machine_common_sense/logging_config.py
index 9171731f7..db4b8ce00 100644
--- a/machine_common_sense/logging_config.py
+++ b/machine_common_sense/logging_config.py
@@ -43,6 +43,8 @@
 
 logger = logging.getLogger(__name__)
 
+TRACE = 5
+
 
 class LoggingConfig():
 
@@ -84,6 +86,7 @@ def init_logging(log_config=None,
         If user file doesn't exist, then there is a base config that
         should be read.
         """
+        LoggingConfig._add_trace()
         init_message = ""
         if (os.path.exists(log_config_file)):
             with open(log_config_file, "r") as data:
@@ -100,6 +103,15 @@ def init_logging(log_config=None,
         logging.config.dictConfig(log_config)
         logger.info(init_message)
 
+    @staticmethod
+    def _add_trace():
+        logging.addLevelName(TRACE, "TRACE")
+
+        def trace(self, message, *args, **kws):
+            if self.isEnabledFor(TRACE):
+                self._log(TRACE, message, args, **kws)
+        logging.Logger.trace = trace
+
     @staticmethod
     def get_default_console_config():
         return LoggingConfig.default_console_config
@@ -135,6 +147,14 @@ def get_errors_only_console_config():
             }
         }
 
+    @staticmethod
+    def get_no_logging_config():
+        return {
+            "version": 1,
+            "loggers": {},
+            "handlers": {}
+        }
+
     @staticmethod
     def get_dev_logging_config():
         '''Note: This logging configuration needs the log directory to be
@@ -193,3 +213,8 @@ def get_dev_logging_config():
                 }
             }
         }
+
+
+# Call init logging early with no config to ensure it is called.  Logging
+# config can always be overwritten any time MCS is used.
+LoggingConfig.init_logging(LoggingConfig.get_errors_only_console_config())
diff --git a/machine_common_sense/material.py b/machine_common_sense/material.py
index 66ae00a17..92afe2a23 100644
--- a/machine_common_sense/material.py
+++ b/machine_common_sense/material.py
@@ -68,7 +68,7 @@ def verify_material_enum_string(enum_string):
         boolean
         """
         try:
-            enum_instance = Material[enum_string.upper()]  # noqa: F841
+            _ = Material[enum_string.upper()]
             return True
         except KeyError:
             return False
diff --git a/machine_common_sense/parameter.py b/machine_common_sense/parameter.py
new file mode 100644
index 000000000..e81040a98
--- /dev/null
+++ b/machine_common_sense/parameter.py
@@ -0,0 +1,274 @@
+import random
+from typing import Any, Dict, Optional, Tuple
+
+from .action import Action
+from .config_manager import ConfigManager, MetadataTier
+from .controller import DEFAULT_MOVE
+
+
+class Parameter:
+
+    # AI2-THOR creates a square grid across the scene that is
+    # uses for "snap-to-grid" movement. (This value may not
+    # really matter because we set continuous to True in
+    # the step input.)
+    GRID_SIZE = 0.1
+
+    DEFAULT_HORIZON = 0.0
+    DEFAULT_ROTATION = 0.0
+    DEFAULT_FORCE = 0.5
+    DEFAULT_AMOUNT = 0.5
+    DEFAULT_IMG_COORD = 0
+    DEFAULT_OBJECT_MOVE_AMOUNT = 1.0
+
+    MAX_FORCE = 1.0
+    MIN_FORCE = 0.0
+    MAX_AMOUNT = 1.0
+    MIN_AMOUNT = 0.0
+
+    MIN_NOISE = -0.5
+    MAX_NOISE = 0.5
+
+    ROTATION_KEY = 'rotation'
+    HORIZON_KEY = 'horizon'
+    FORCE_KEY = 'force'
+    AMOUNT_KEY = 'amount'
+
+    OBJECT_IMAGE_COORDS_X_KEY = 'objectImageCoordsX'
+    OBJECT_IMAGE_COORDS_Y_KEY = 'objectImageCoordsY'
+    RECEPTACLE_IMAGE_COORDS_X_KEY = 'receptacleObjectImageCoordsX'
+    RECEPTACLE_IMAGE_COORDS_Y_KEY = 'receptacleObjectImageCoordsY'
+
+    # used for EndHabituation teleport
+    TELEPORT_X_POS = 'xPosition'
+    TELEPORT_Z_POS = 'zPosition'
+    TELEPORT_Y_ROT = 'yRotation'
+
+    # # Hard coding actions that effect MoveMagnitude so the appropriate
+    # # value is set based off of the action
+    # # TODO: Move this to an enum or some place, so that you can determine
+    # # special move interactions that way
+    FORCE_ACTIONS = ["PushObject", "PullObject"]
+    OBJECT_MOVE_ACTIONS = ["CloseObject", "OpenObject"]
+    # DW: not used anywhere
+    # MOVE_ACTIONS = ["MoveAhead", "MoveLeft", "MoveRight", "MoveBack"]
+
+    def __init__(self, config: ConfigManager):
+        self.config = config
+
+    def wrap_step(self, **kwargs) -> Dict:
+        # whether or not to randomize segmentation mask colors
+        metadata_tier = self.config.get_metadata_tier()
+        consistent_colors = (metadata_tier == MetadataTier.ORACLE)
+        # Create the step data dict for the AI2-THOR step function.
+        return dict(
+            continuous=True,
+            gridSize=self.GRID_SIZE,
+            logs=True,
+            renderDepthImage=self.config.is_depth_maps_enabled(),
+            renderObjectImage=self.config.is_object_masks_enabled(),
+            snapToGrid=False,
+            consistentColors=consistent_colors,
+            **kwargs
+        )
+
+    def build_ai2thor_step(self, **kwargs) -> Tuple:
+        action, params = self._validate_and_convert_params(**kwargs)
+        action = self._mcs_action_to_ai2thor_action(action)
+        wrapped_step = self.wrap_step(action=action, **params)
+        return wrapped_step, params
+
+    def _convert_y_image_coord_for_unity(self, y_coord: int) -> int:
+        '''Pixel coordinates are expected to start at the top left,
+        but in Unity, (0,0) is the bottom left.
+        '''
+        if not isinstance(y_coord, int):
+            raise TypeError(f"{y_coord} y_coord is not an integer")
+        screen_height = self.config.get_screen_height()
+        if(0 <= y_coord < screen_height):
+            return screen_height - y_coord - 1
+        raise ValueError(f"{y_coord} is not in range 0-{screen_height-1}")
+
+    def _get_amount(self, **kwargs) -> float:
+        action = kwargs.get('action')
+        amount = kwargs.get(
+            self.AMOUNT_KEY,
+            self.DEFAULT_OBJECT_MOVE_AMOUNT
+            if action in self.OBJECT_MOVE_ACTIONS
+            else self.DEFAULT_AMOUNT
+        )
+        if amount is not None:
+            try:
+                amount = float(amount)
+            except ValueError as err:
+                raise ValueError(f"Amount {amount} is not a number") from err
+        else:
+            amount = self.DEFAULT_AMOUNT
+
+        if amount < self.MIN_AMOUNT or amount > self.MAX_AMOUNT:
+            raise ValueError(
+                "Amount not in acceptable range of "
+                f"({self.MIN_AMOUNT}-{self.MAX_AMOUNT})"
+            )
+        return amount
+
+    def _get_force(self, **kwargs) -> float:
+        force = kwargs.get(self.FORCE_KEY, self.DEFAULT_FORCE)
+        if force is not None:
+            try:
+                force = float(force)
+            except ValueError as err:
+                raise ValueError('Force is not a number') from err
+
+            if force < self.MIN_FORCE or force > self.MAX_FORCE:
+                raise ValueError(
+                    f'Force not in acceptable range of '
+                    f'({self.MIN_FORCE}-{self.MAX_FORCE})')
+        else:
+            force = self.DEFAULT_FORCE
+        return force
+
+    def _get_number(self, key: str, **kwargs) -> Optional[Any]:
+        val = kwargs.get(key)
+        if val is not None:
+            try:
+                val = float(val)
+            except ValueError as err:
+                raise ValueError(f"{key}") from err
+        return val
+
+    def _get_number_with_default(
+            self, key: str, default: Any, **kwargs) -> Any:
+        val = kwargs.get(key, default)
+        if val is not None:
+            try:
+                val = float(val)
+            except ValueError as err:
+                raise ValueError(f"{key}") from err
+        else:
+            val = float(default)
+        return val
+
+    def _get_move_magnitude(self, action: str, force: float,
+                            amount: float) -> float:
+        # Set the Move Magnitude to the appropriate amount based on the action
+        move_magnitude = DEFAULT_MOVE
+        if action in self.FORCE_ACTIONS:
+            move_magnitude = force * self.MAX_FORCE
+        elif action in self.OBJECT_MOVE_ACTIONS:
+            move_magnitude = amount
+        return move_magnitude
+
+    def _get_teleport(self, **kwargs) -> Tuple:
+        teleport_rotation = self._get_teleport_rotation(**kwargs)
+        teleport_position = self._get_teleport_position(**kwargs)
+        return (teleport_rotation, teleport_position)
+
+    def _get_teleport_position(self, **kwargs) -> Optional[Dict]:
+        '''Extract teleport xz position from kwargs if it exists.
+        Otherwise, position will be None.
+        '''
+        teleport_pos_x_input = self._get_number(self.TELEPORT_X_POS, **kwargs)
+        teleport_pos_z_input = self._get_number(self.TELEPORT_Z_POS, **kwargs)
+        teleport_position = None
+        if teleport_pos_x_input is not None and \
+                teleport_pos_z_input is not None:
+            teleport_position = {
+                'x': teleport_pos_x_input,
+                'z': teleport_pos_z_input}
+        return teleport_position
+
+    def _get_teleport_rotation(self, **kwargs) -> Optional[Dict]:
+        '''Extract teleport rotation from kwargs if it exists.
+        Otherwise, rotation will be None.
+        '''
+        teleport_rot_input = self._get_number(self.TELEPORT_Y_ROT, **kwargs)
+        return {'y': teleport_rot_input} \
+            if teleport_rot_input is not None else None
+
+    def _validate_and_convert_params(self, **kwargs) -> Tuple:
+        """Need a validation/conversion step for what ai2thor will accept as input
+        to keep parameters more simple for the user (in this case, wrapping
+        rotation degrees into an object)
+        """
+        action = kwargs.get('action')
+        amount = self._get_amount(**kwargs)
+        force = self._get_force(**kwargs)
+        object_image_coords_x = int(self._get_number_with_default(
+            self.OBJECT_IMAGE_COORDS_X_KEY, self.DEFAULT_IMG_COORD, **kwargs))
+        object_image_coords_y = int(self._get_number_with_default(
+                                    self.OBJECT_IMAGE_COORDS_Y_KEY,
+                                    self.DEFAULT_IMG_COORD, **kwargs))
+        receptable_image_coords_x = int(self._get_number_with_default(
+            self.RECEPTACLE_IMAGE_COORDS_X_KEY,
+            self.DEFAULT_IMG_COORD,
+            **kwargs))
+        receptacle_image_coords_y = int(self._get_number_with_default(
+            self.RECEPTACLE_IMAGE_COORDS_Y_KEY,
+            self.DEFAULT_IMG_COORD,
+            **kwargs))
+        # TODO Consider the current "head tilt" value while validating the
+        # input "horizon" value.
+        horizon = kwargs.get(self.HORIZON_KEY, self.DEFAULT_HORIZON)
+        rotation = kwargs.get(self.ROTATION_KEY, self.DEFAULT_ROTATION)
+
+        rotation_vector = {'y': rotation}
+        object_vector = {
+            'x': object_image_coords_x,
+            'y': self._convert_y_image_coord_for_unity(
+                object_image_coords_y),
+        }
+
+        receptacle_vector = {
+            'x': receptable_image_coords_x,
+            'y': self._convert_y_image_coord_for_unity(
+                receptacle_image_coords_y)
+        }
+        move_magnitude = self._get_move_magnitude(action, force, amount)
+        (teleport_rotation, teleport_position) = self._get_teleport(**kwargs)
+
+        # TODO is this a feature we need?
+        if self.config.is_noise_enabled():
+            rotation = rotation * (1 + self._generate_noise())
+            horizon = horizon * (1 + self._generate_noise())
+            move_magnitude = move_magnitude * (1 + self._generate_noise())
+
+        return action, dict(
+            objectId=kwargs.get("objectId"),
+            receptacleObjectId=kwargs.get("receptacleObjectId"),
+            rotation=rotation_vector,
+            horizon=horizon,
+            teleportRotation=teleport_rotation,
+            teleportPosition=teleport_position,
+            moveMagnitude=move_magnitude,
+            objectImageCoords=object_vector,
+            receptacleObjectImageCoords=receptacle_vector
+        )
+
+    def _mcs_action_to_ai2thor_action(self, action: str) -> str:
+        if action == Action.CLOSE_OBJECT.value:
+            # The AI2-THOR Python library has buggy error checking
+            # specifically for the CloseObject action,
+            # so just use our own custom action here.
+            return "MCSCloseObject"
+        elif action == Action.DROP_OBJECT.value:
+            return "DropHandObject"
+        elif action == Action.OPEN_OBJECT.value:
+            # The AI2-THOR Python library has buggy error checking
+            # specifically for the OpenObject action,
+            # so just use our own custom action here.
+            return "MCSOpenObject"
+
+        return action
+
+    def _generate_noise(self) -> float:
+        """
+        Returns a random value between -0.05 and 0.05 used to add noise to all
+        numerical action parameters noise_enabled is True.
+        Returns
+        -------
+        float
+            A value between -0.05 and 0.05 (using random.uniform).
+        """
+
+        return random.uniform(self.MIN_NOISE, self.MAX_NOISE)
diff --git a/machine_common_sense/plotter.py b/machine_common_sense/plotter.py
index 9bf724377..b590becc3 100644
--- a/machine_common_sense/plotter.py
+++ b/machine_common_sense/plotter.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import math
 from dataclasses import dataclass
 from typing import Dict, List
@@ -10,21 +12,43 @@
 import skimage.draw
 from shapely import geometry
 
-from machine_common_sense.config_manager import Vector3d
+from machine_common_sense.config_manager import (FloorTexturesConfig,
+                                                 SceneConfiguration, Vector3d)
+
+
+@dataclass
+class SceneCoord():
+    '''Unity scenes have floor dimensions of x and z'''
+    x: int
+    z: int
+
+    def __sub__(self, coord: SceneCoord):
+        return SceneCoord(self.x - coord.x, self.z - coord.z)
+
+    def __add__(self, coord: SceneCoord):
+        return SceneCoord(self.x + coord.x, self.z + coord.z)
+
+
+@dataclass
+class ImageCoord():
+    '''Image xy coordinates where y corresponds to z in Unity'''
+    x: int
+    y: int
 
 
 @dataclass
 class XZHeading():
+    '''Unity directional vector'''
     x: float
     z: float
 
 
 @dataclass
 class Robot():
+    '''AI robot/performer location and heading in Unity'''
     x: float
-    y: float
     z: float
-    rotation: float
+    heading: XZHeading
 
 
 @dataclass
@@ -36,6 +60,12 @@ class Object():
     bounds: list
 
 
+@dataclass
+class Texture():
+    material: str
+    positions: list
+
+
 class TopDownPlotter():
 
     DEFAULT_COLOR = colour.COLOR_NAME_TO_RGB['white']
@@ -50,44 +80,92 @@ class TopDownPlotter():
     HEADING_LENGTH = 0.2
     ROBOT_NOSE_RADIUS = 0.08
     PLOT_IMAGE_SIZE = 512
+    UNIT_CELL_WIDTH = 1
 
     FONT = cv2.FONT_HERSHEY_COMPLEX
     FONT_SCALE = 0.4
     FONT_THICKNESS = 1
     WALL_BUFFER = 50
 
-    def __init__(self, team: str, scene_name: str, room_size: Vector3d):
+    def __init__(self, team: str, scene_config: SceneConfiguration) -> None:
+        self._scene_name = scene_config.name.replace('json', '')
+        if '/' in self._scene_name:
+            self._scene_name = self._scene_name.rsplit('/', 1)[1]
+
+        self._room_size = (
+            # Room is automatically expanded in intuitive physics scenes.
+            Vector3d(14, 10, 10) if scene_config.intuitive_physics
+            else scene_config.room_dimensions
+        )
         self._team = team
-        if '/' in scene_name:
-            scene_name = scene_name.rsplit('/', 1)[1]
-        self._scene_name = scene_name
-        self._room_size = room_size
         # create the room once as it is computationally expensive
-        self.grid_img = self._initialize_plot()
+        self.base_room_img = self._initialize_plot(scene_config)
+
+    def _initialize_plot(self, scene_config: SceneConfiguration) -> np.ndarray:
+        '''Create the initial plot with grid lines'''
+        img = np.zeros(
+            (self.PLOT_IMAGE_SIZE, self.PLOT_IMAGE_SIZE, 3),
+            dtype=np.int8)
+
+        room = SceneCoord(
+            x=self._room_size.x,
+            z=self._room_size.z
+        )
+        largest_dimension = max(room.x, room.z)
+
+        x_dim, y_dim, _ = img.shape
+        self.image_center = ImageCoord(
+            x=int(x_dim / 2) - 1,  # Ex. 512/2-1 = 255
+            y=int(y_dim / 2) - 1
+        )
+
+        # determine the scale for each xy dimension
+        self.scale = ImageCoord(
+            x=(x_dim - self.WALL_BUFFER) / largest_dimension,
+            y=(y_dim - self.WALL_BUFFER) / largest_dimension
+        )
+
+        # get the buffer for both x and y especially for non-square rooms
+        buffer = ImageCoord(
+            x=(x_dim - self.scale.x * room.x) / 2,
+            y=(y_dim - self.scale.y * room.z) / 2
+        )
+
+        img = self._draw_grid_lines(img, buffer)
+        img = self._draw_holes(
+            img, scene_config.holes)
+        img = self._draw_floor_textures(
+            img,
+            scene_config.floor_textures)
+
+        return img
 
     def plot(self, scene_event: ai2thor.server.Event,
              step_number: int,
              goal_id: str = None
              ) -> PIL.Image.Image:
         '''Create a plot of the room, objects and robot'''
-        plt_img = self.grid_img.copy()
+        plt_img = self.base_room_img.copy()
+
         plt_objects = self._find_plottable_objects(scene_event)
         plt_img = self._draw_objects(plt_img,
                                      plt_objects,
                                      goal_id)
-        agent_metadata = scene_event.metadata.get('agent', None)
+
+        agent_metadata = scene_event.metadata.get('agent')
         plt_img = self._draw_robot(
             plt_img,
             agent_metadata)
+
         plt_img = self._draw_step_number(plt_img, step_number)
         return self._export_plot(img=plt_img)
 
-    def _initialize_plot(self) -> np.ndarray:
-        '''Create the initial plot with grid lines'''
-        img = np.zeros(
-            (self.PLOT_IMAGE_SIZE, self.PLOT_IMAGE_SIZE, 3),
-            dtype=np.int8)
-        return self._draw_grid_lines(img)
+    def _convert_to_image_coords(self, scene_pt: SceneCoord) -> ImageCoord:
+        '''converts scene xz coordinate point to an xy image coordinate'''
+        return ImageCoord(
+            x=int(self.image_center.x + scene_pt.x * self.scale.x),
+            y=int(self.image_center.y - scene_pt.z * self.scale.y)
+        )
 
     def _draw_step_number(self, img: np.ndarray,
                           step_number: int) -> np.ndarray:
@@ -106,84 +184,172 @@ def _draw_step_number(self, img: np.ndarray,
             lineType=self.FONT_THICKNESS)
         return img
 
-    def _draw_grid_lines(self, img: np.ndarray) -> np.ndarray:
+    def _draw_grid_lines(self, img: np.ndarray,
+                         buffer: ImageCoord) -> np.ndarray:
         '''Draw room coordinate grid aligned with Unity units'''
-        room_x, room_z = self._room_size.x, self._room_size.z
-        # treating y image dimension as z since this is an XZ planar plot
-        x_dim, z_dim, _ = img.shape
-        self.center_x = int(x_dim / 2) - 1  # ex: 512 /2 = 256 - 1 = 255
-        self.center_z = int(z_dim / 2) - 1  # ex: 256 /2 = 128 - 1 = 127
-        largest_dimension = max(room_x, room_z)
-
-        # Add a buffer into the scale to properly show the room's walls.
-        buffered_x_dim = x_dim - self.WALL_BUFFER
-        buffered_z_dim = z_dim - self.WALL_BUFFER
-        self.x_scale = (buffered_x_dim) / largest_dimension
-        self.z_scale = (buffered_z_dim) / largest_dimension
-
-        # outer buffer space to keep 1:1 aspect ratio
-        buffer_x = (x_dim - (buffered_x_dim * room_x / largest_dimension)) / 2
-        buffer_z = (z_dim - (buffered_z_dim * room_z / largest_dimension)) / 2
-
-        img = self._draw_vertical_grid_lines(img, buffer_z)
-        img = self._draw_horizontal_grid_lines(img, buffer_x)
-        img = self._draw_room_border(img, buffer_x, buffer_z)
-
-        return img
+        img = self._draw_vertical_grid_lines(img, buffer.y)
+        img = self._draw_horizontal_grid_lines(img, buffer.x)
+        return self._draw_room_border(img, buffer)
 
     def _draw_vertical_grid_lines(
-            self, img: np.ndarray, buffer_z: int) -> np.ndarray:
+            self, img: np.ndarray, buffer_y: int) -> np.ndarray:
+        '''Draw vertical lines at each x-coordinate'''
         x_units = math.floor(self._room_size.x / 2)
+        # draw vertical grid lines starting at the center and working out
         for x in range(x_units + 1):
-            img_x = int(self.center_x + x * self.x_scale)
-            rr, cc = skimage.draw.line(
-                c0=img_x,
-                r0=int(buffer_z),
-                c1=img_x,
-                r1=img.shape[0] - 1 - int(buffer_z))
-            img[rr, cc] = self.CENTER_COLOR if x == 0 else self.GRID_COLOR
-
-            img_x = int(self.center_x - x * self.x_scale)
-            rr, cc = skimage.draw.line(
-                c0=img_x,
-                r0=int(buffer_z),
-                c1=img_x,
-                r1=img.shape[0] - 1 - int(buffer_z))
-            img[rr, cc] = self.CENTER_COLOR if x == 0 else self.GRID_COLOR
+            # positive x values
+            img = self._draw_vertical_line(img, buffer_y, x)
+            # negative x values
+            img = self._draw_vertical_line(img, buffer_y, -x)
+        return img
+
+    def _draw_vertical_line(self, img: np.ndarray,
+                            buffer_y: int, x_coord: int) -> np.ndarray:
+        '''Draw a vertical grid line corresponding to the x-coordinate'''
+        img_x = int(self.image_center.x + x_coord * self.scale.x)
+        rr, cc = skimage.draw.line(
+            c0=img_x,
+            r0=int(buffer_y),
+            c1=img_x,
+            r1=img.shape[0] - 1 - int(buffer_y))
+        img[rr, cc] = self.CENTER_COLOR if x_coord == 0 else self.GRID_COLOR
         return img
 
     def _draw_horizontal_grid_lines(
             self, img: np.ndarray, buffer_x: int) -> np.ndarray:
+        '''Draw horizontal lines at each y-coordinate'''
         z_units = math.floor(self._room_size.z / 2)
-        for z in range(z_units + 1):
-            img_z = int(self.center_z + z * self.z_scale)
-            rr, cc = skimage.draw.line(
-                c0=int(buffer_x),
-                r0=img_z,
-                c1=img.shape[1] - 1 - int(buffer_x),
-                r1=img_z)
-            img[rr, cc] = self.CENTER_COLOR if z == 0 else self.GRID_COLOR
-
-            img_z = int(self.center_z - z * self.z_scale)
-            rr, cc = skimage.draw.line(
-                c0=int(buffer_x),
-                r0=img_z,
-                c1=img.shape[1] - 1 - int(buffer_x),
-                r1=img_z)
-            img[rr, cc] = self.CENTER_COLOR if z == 0 else self.GRID_COLOR
+        # draw horizontal grid lines starting at the center and working out
+        for y in range(z_units + 1):
+            img = self._draw_horizontal_line(img, buffer_x, y)
+            img = self._draw_horizontal_line(img, buffer_x, -y)
+        return img
+
+    def _draw_horizontal_line(self, img: np.ndarray,
+                              buffer_x: int, y_coord: int) -> np.ndarray:
+        '''Draw a horizontal grid line corresponding to the y-coordinate'''
+        img_y = int(self.image_center.y + y_coord * self.scale.y)
+        rr, cc = skimage.draw.line(
+            c0=int(buffer_x),
+            r0=img_y,
+            c1=img.shape[1] - 1 - int(buffer_x),
+            r1=img_y)
+        img[rr, cc] = self.CENTER_COLOR if y_coord == 0 else self.GRID_COLOR
         return img
 
     def _draw_room_border(self, img: np.ndarray,
-                          buffer_x: int, buffer_z: int) -> np.ndarray:
+                          buffer: ImageCoord) -> np.ndarray:
+        '''Outline the room border'''
         x_dim, z_dim, _ = img.shape
         rr, cc = skimage.draw.rectangle_perimeter(
-            start=(buffer_z, buffer_x),
-            end=(z_dim - buffer_z - 1, x_dim - buffer_x - 1),
+            start=(buffer.y, buffer.x),
+            end=(z_dim - buffer.y - 1, x_dim - buffer.x - 1),
             shape=img.shape[:2],
             clip=True)
         img[rr, cc] = self.BORDER_COLOR
         return img
 
+    def _draw_floor_textures(
+        self,
+        img: np.ndarray,
+        floor_textures: List[FloorTexturesConfig]
+    ) -> np.ndarray:
+        if floor_textures is None:
+            return img
+
+        for floor_texture in floor_textures:
+            # TODO MCS-1024 use floor_texture.material for color
+            texture_color = colour.COLOR_NAME_TO_RGB['red']
+
+            for position in floor_texture.positions:
+                texture_pos = SceneCoord(x=position.x, z=position.z)
+                half_cell_pos = SceneCoord(
+                    self.UNIT_CELL_WIDTH / 2, self.UNIT_CELL_WIDTH / 2)
+                tex_pos_ul = texture_pos - half_cell_pos
+                tex_pos_lr = texture_pos + half_cell_pos
+                tex_img_pos_ul = self._convert_to_image_coords(tex_pos_ul)
+                tex_img_pos_lr = self._convert_to_image_coords(tex_pos_lr)
+                img = self._draw_floor_texture(
+                    img=img,
+                    upper_left=tex_img_pos_ul,
+                    lower_right=tex_img_pos_lr,
+                    texture_color=texture_color)
+
+        return img
+
+    def _draw_floor_texture(self,
+                            img: np.ndarray,
+                            upper_left: ImageCoord,
+                            lower_right: ImageCoord,
+                            texture_color: colour.C_RGB) -> np.ndarray:
+        rr, cc = skimage.draw.rectangle(
+            start=(upper_left.y, upper_left.x),
+            end=(lower_right.y, lower_right.x),
+            shape=img.shape[:2])
+        img[rr, cc] = texture_color
+        return img
+
+    def _draw_holes(self, img: np.ndarray, holes: List) -> np.ndarray:
+        '''Draw a box with an X to illustrate a floor hole'''
+        if holes is None:
+            return img
+
+        for hole in holes:
+            hole_center = SceneCoord(x=hole.x, z=hole.z)
+            half_cell_pos = SceneCoord(
+                self.UNIT_CELL_WIDTH / 2,
+                self.UNIT_CELL_WIDTH / 2)
+            # calculate scene corners
+            hole_upper_left: SceneCoord = hole_center - half_cell_pos
+            hole_lower_right: SceneCoord = hole_center + half_cell_pos
+
+            # convert scene corners to image coordinates
+            hole_img_upper_left: ImageCoord = self._convert_to_image_coords(
+                hole_upper_left)
+            hole_img_lower_right: ImageCoord = self._convert_to_image_coords(
+                hole_lower_right)
+
+            img = self._draw_hole_perimeter(
+                img, hole_img_upper_left, hole_img_lower_right)
+            img = self._draw_hole_x(
+                img, hole_img_upper_left, hole_img_lower_right)
+        return img
+
+    def _draw_hole_perimeter(
+            self,
+            img: np.ndarray,
+            upper_left: ImageCoord,
+            lower_right: ImageCoord) -> np.ndarray:
+        '''Outline the floor hole'''
+        rr, cc = skimage.draw.rectangle_perimeter(
+            start=(upper_left.y, upper_left.x),
+            end=(lower_right.y - 1, lower_right.x - 1),
+            shape=img.shape[:2],
+            clip=True)
+        img[rr, cc] = self.DEFAULT_COLOR
+        return img
+
+    def _draw_hole_x(
+            self,
+            img: np.ndarray,
+            upper_left: ImageCoord,
+            lower_right: ImageCoord) -> np.ndarray:
+        '''Draw an X through the hole cell'''
+        rr, cc = skimage.draw.line(
+            r0=upper_left.y,
+            c0=upper_left.x,
+            r1=lower_right.y,
+            c1=lower_right.x)
+        img[rr, cc] = self.DEFAULT_COLOR
+
+        rr, cc = skimage.draw.line(
+            r0=lower_right.y,
+            c0=upper_left.x,
+            r1=upper_left.y,
+            c1=lower_right.x)
+        img[rr, cc] = self.DEFAULT_COLOR
+        return img
+
     def _export_plot(self, img: np.ndarray) -> PIL.Image.Image:
         '''Export the plot to a PIL Image'''
         return PIL.Image.fromarray(img, "RGB")
@@ -191,38 +357,64 @@ def _export_plot(self, img: np.ndarray) -> PIL.Image.Image:
     def _draw_robot(self, img: np.ndarray, robot_metadata: Dict) -> np.ndarray:
         '''Plot the robot position and heading'''
         if robot_metadata is None:
-            return None
+            return img
         robot = self._create_robot(robot_metadata)
         img = self._draw_robot_position(img, robot)
         img = self._draw_robot_heading(img, robot)
         return img
 
+    def _create_robot(self, robot_metadata: Dict) -> Robot:
+        '''Extract robot position and rotation information from the metadata'''
+        position = robot_metadata.get('position')
+        if position is not None:
+            x = position.get('x')
+            z = position.get('z')
+        else:
+            x = 0.0
+            z = 0.0
+        rotation = robot_metadata.get('rotation')
+        rotation_y = rotation.get('y') if rotation is not None else 0.0
+        heading = self._calculate_heading(
+            rotation_angle=360.0 - rotation_y,
+            heading_length=self.HEADING_LENGTH
+        )
+        return Robot(x, z, heading)
+
+    def _calculate_heading(self, rotation_angle: float,
+                           heading_length: float) -> XZHeading:
+        '''Calculate XZ heading vector from the rotation angle'''
+        s = math.sin(math.radians(rotation_angle))
+        c = math.cos(math.radians(rotation_angle))
+        vec_x = 0 * c - heading_length * s
+        vec_z = 0 * s + heading_length * c
+        return XZHeading(vec_x, vec_z)
+
     def _draw_robot_position(self, img: np.ndarray,
                              robot: Robot) -> np.ndarray:
         '''Draw the robot's scene XZ position in the plot'''
+        robot_position = self._convert_to_image_coords(
+            scene_pt=SceneCoord(robot.x, robot.z)
+        )
         rr, cc = skimage.draw.disk(
             center=(
-                self.center_z - robot.z * self.z_scale,
-                self.center_x + robot.x * self.x_scale),
-            radius=self.ROBOT_PLOT_WIDTH * self.x_scale,
+                robot_position.y,
+                robot_position.x
+            ),
+            radius=self.ROBOT_PLOT_WIDTH * self.scale.x,
             shape=img.shape[:2])
         img[rr, cc] = self.ROBOT_COLOR
         return img
 
     def _draw_robot_heading(self, img: np.ndarray, robot: Robot) -> np.ndarray:
         '''Draw the heading vector starting from the robot XZ position'''
-        heading = self._calculate_heading(
-            rotation_angle=360.0 - robot.rotation,
-            heading_length=self.HEADING_LENGTH
-        )
-        z_point = int(self.center_z - robot.z * self.z_scale)
-        x_point = int(self.center_x + robot.x * self.x_scale)
+        y_point = int(self.image_center.y - robot.z * self.scale.y)
+        x_point = int(self.image_center.x + robot.x * self.scale.x)
         rr, cc = skimage.draw.disk(
             center=(
-                z_point - int(heading.z * self.x_scale),
-                x_point + int(heading.x * self.x_scale)
+                y_point - int(robot.heading.z * self.scale.x),
+                x_point + int(robot.heading.x * self.scale.x)
             ),
-            radius=self.ROBOT_NOSE_RADIUS * self.x_scale,
+            radius=self.ROBOT_NOSE_RADIUS * self.scale.x,
             shape=img.shape[:2]
         )
         img[rr, cc] = self.ROBOT_COLOR
@@ -254,75 +446,13 @@ def _draw_objects(self, img: np.ndarray, objects: Dict,
                 polygon = geometry.MultiPoint(
                     obj_pts).convex_hull
                 pts = polygon.exterior.coords
-                img = self._draw_object_bounds(img, obj, pts)
+                img = self._draw_object(img, obj, pts)
                 if goal_id is not None and o['objectId'] == goal_id:
                     img = self._draw_goal(img, pts)
         return img
 
-    def _draw_goal(self, img: np.ndarray,
-                   pts: List) -> np.ndarray:
-        '''Draw the goal object of the scene'''
-        cs, rs = map(list, zip(*pts))
-        rr, cc = skimage.draw.polygon_perimeter(
-            rs,
-            cs,
-            shape=img.shape[:2])
-        img[rr, cc] = self.GOAL_COLOR
-        return img
-
-    def _draw_object_bounds(self, img: np.ndarray,
-                            obj: Object, points: List) -> np.ndarray:
-        '''Draw the scene object'''
-        # convert list of tuples to list of rows and list of columns
-        cs, rs = map(list, zip(*points))
-        # convert room coordinates to image coordinates
-        rs = list(map(lambda r: self.center_z - r * self.z_scale, rs))
-        cs = list(map(lambda c: self.center_x + c * self.x_scale, cs))
-        # use dictionary get method for color retrieval
-        # if no match, then None will resort to default color
-        clr = colour.COLOR_NAME_TO_RGB.get(obj.color.lower())
-
-        # draw filled polygon if visible to the robot
-        if(obj.visible):
-            rr, cc = skimage.draw.polygon(
-                rs,
-                cs,
-                shape=img.shape[:2])
-        else:
-            rr, cc = skimage.draw.polygon_perimeter(
-                rs,
-                cs,
-                shape=img.shape[:2])
-        img[rr, cc] = clr
-        return img
-
-    def _calculate_heading(self, rotation_angle: float,
-                           heading_length: float) -> XZHeading:
-        '''Calculate XZ heading vector from the rotation angle'''
-        s = math.sin(math.radians(rotation_angle))
-        c = math.cos(math.radians(rotation_angle))
-        vec_x = 0 * c - heading_length * s
-        vec_z = 0 * s + heading_length * c
-        return XZHeading(vec_x, vec_z)
-
-    def _create_robot(self, robot_metadata: Dict) -> Robot:
-        '''Extract robot position and rotation information from the metadata'''
-        position = robot_metadata.get('position')
-        if position is not None:
-            x = position.get('x')
-            y = position.get('y')
-            z = position.get('z')
-        else:
-            x = 0.0
-            y = 0.0
-            z = 0.0
-        rotation = robot_metadata.get('rotation')
-        rotation_y = rotation.get('y') if rotation is not None else 0.0
-        return Robot(x, y, z, rotation_y)
-
     def _create_object(self, object_metadata: Dict) -> Object:
         '''Create the scene object from its metadata'''
-
         held = object_metadata.get('isPickedUp')
         visible = object_metadata.get('visibleInCamera')
         uuid = object_metadata.get('objectId')
@@ -348,4 +478,45 @@ def _convert_color(self, color: str) -> str:
 
         if color == 'black':
             color = 'ivory'
+        if color == 'rose':
+            color = 'deeppink'
         return color
+
+    def _draw_object(self, img: np.ndarray,
+                     obj: Object, points: List) -> np.ndarray:
+        '''Draw the scene object'''
+        # convert list of tuples to list of rows and list of columns
+        cs, rs = map(list, zip(*points))
+        # convert room coordinates to image coordinates
+        rs = list(map(lambda r: self.image_center.y - r * self.scale.y, rs))
+        cs = list(map(lambda c: self.image_center.x + c * self.scale.x, cs))
+
+        # draw filled polygon if visible to the robot
+        if(obj.visible):
+            rr, cc = skimage.draw.polygon(
+                rs,
+                cs,
+                shape=img.shape[:2])
+        else:
+            rr, cc = skimage.draw.polygon_perimeter(
+                rs,
+                cs,
+                shape=img.shape[:2])
+        # use dictionary get method for color retrieval
+        # if no match for that color string, then resort to the default color
+        clr = colour.COLOR_NAME_TO_RGB.get(
+            obj.color.lower(), self.DEFAULT_COLOR)
+
+        img[rr, cc] = clr
+        return img
+
+    def _draw_goal(self, img: np.ndarray,
+                   pts: List) -> np.ndarray:
+        '''Draw the goal object of the scene'''
+        cs, rs = map(list, zip(*pts))
+        rr, cc = skimage.draw.polygon_perimeter(
+            rs,
+            cs,
+            shape=img.shape[:2])
+        img[rr, cc] = self.GOAL_COLOR
+        return img
diff --git a/machine_common_sense/pose.py b/machine_common_sense/pose.py
deleted file mode 100644
index e166374ea..000000000
--- a/machine_common_sense/pose.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from enum import Enum, unique
-
-
-@unique
-class Pose(Enum):
-    CRAWLING = "CRAWLING"
-    LYING = "LYING"
-    STANDING = "STANDING"
-    UNDEFINED = "UNDEFINED"
diff --git a/machine_common_sense/recorder.py b/machine_common_sense/recorder.py
index 2d54d3dce..e8f33b8d3 100644
--- a/machine_common_sense/recorder.py
+++ b/machine_common_sense/recorder.py
@@ -215,9 +215,9 @@ def add(self, frame: PIL.Image.Image) -> None:
 
         if self.writer is None:
             self.width, self.height = frame.size
-            logger.debug((
+            logger.debug(
                 f"Establishing video writer size"
-                f"({self.width},{self.height}) from first frame"))
+                f"({self.width},{self.height}) from first frame")
             self.writer = cv2.VideoWriter(str(self.path),
                                           cv2.VideoWriter_fourcc(*self.fourcc),
                                           self.fps,
diff --git a/machine_common_sense/return_status.py b/machine_common_sense/return_status.py
index 86e4c1fa9..8f571e33e 100644
--- a/machine_common_sense/return_status.py
+++ b/machine_common_sense/return_status.py
@@ -19,5 +19,4 @@ class ReturnStatus(Enum):
     SUCCESSFUL = "SUCCESSFUL"
     SUCCESSFUL_WITH_INVALID_PARAMETERS = "SUCCESSFUL_WITH_INVALID_PARAMETERS"
     UNDEFINED = "UNDEFINED"
-    WRONG_POSE = "WRONG_POSE"
     FAILED = "FAILED"
diff --git a/machine_common_sense/reward.py b/machine_common_sense/reward.py
index a338270d6..f43650d2d 100644
--- a/machine_common_sense/reward.py
+++ b/machine_common_sense/reward.py
@@ -1,5 +1,6 @@
-from typing import Dict, List
+from typing import Dict, List, Optional
 
+import typeguard
 from shapely import geometry
 
 from .controller import DEFAULT_MOVE
@@ -43,9 +44,7 @@ def _convert_object_to_planar_polygon(
         bbox3d = goal_object['objectBounds']['objectBoundsCorners']
         # project to XZ plane
         xz_pts = [(pt['x'], pt['z']) for pt in bbox3d]
-        polygon = geometry.MultiPoint(xz_pts).convex_hull
-
-        return polygon
+        return geometry.MultiPoint(xz_pts).convex_hull
 
     @staticmethod
     def _calc_retrieval_reward(
@@ -212,12 +211,13 @@ def _calculate_default_reward(
         return GOAL_NOT_ACHIEVED
 
     @staticmethod
+    @typeguard.typechecked
     def calculate_reward(
-            goal: GoalMetadata,
-            objects: Dict,
+            goal: Optional[GoalMetadata],
+            objects: List[Dict],
             agent: Dict,
             number_steps: int,
-            reach: float) -> float:
+            reach: Optional[float]) -> float:
         '''
         Determine if the agent achieved the objective/task/goal.
 
diff --git a/machine_common_sense/scripts/run_human_input.py b/machine_common_sense/scripts/run_human_input.py
index 90afb443f..dd1bbe6d2 100644
--- a/machine_common_sense/scripts/run_human_input.py
+++ b/machine_common_sense/scripts/run_human_input.py
@@ -7,7 +7,7 @@
 
 from .getch_helper import getch
 
-commandList = []
+commands = []
 
 
 def parse_args():
@@ -36,7 +36,7 @@ def parse_args():
     return parser.parse_args()
 
 
-class command:
+class Command:
     '''Class to contain possible commands and keys.'''
 
     def __init__(self, name, key, desc):
@@ -60,10 +60,10 @@ def __init__(self, input_controller, input_scene_data):
     def precmd(self, line):
         return line
 
-    def postcmd(self, stopFlag, line) -> bool:
+    def postcmd(self, stop_flag, line) -> bool:
         print('================================================='
               '==============================')
-        return stopFlag
+        return stop_flag
 
     def default(self, line):
         split_input = line.split(',')
@@ -146,7 +146,7 @@ def do_auto(self, args=None):
 
     def do_exit(self, args=None) -> bool:
         print("Exiting Human Input Mode\n")
-        self.controller.end_scene("", 1)
+        self.controller.end_scene()
         return True
 
     def do_print(self, args=None):
@@ -190,9 +190,9 @@ def show_available_actions(self, output):
 def build_commands():
     '''Define all the possible human input commands.'''
     for action in mcs.Action:
-        commandList.append(command(action.value,
-                                   action.key,
-                                   action.desc))
+        commands.append(Command(action.value,
+                                action.key,
+                                action.desc))
 
 
 def print_commands():
@@ -201,14 +201,14 @@ def print_commands():
     print("--------------- Available Commands ---------------")
     print(" ")
 
-    for commandListItem in commandList:
+    for command in commands:
         print(
             "- " +
-            commandListItem.name +
+            command.name +
             " (ShortcutKey=" +
-            commandListItem.key +
+            command.key +
             "): " +
-            commandListItem.desc)
+            command.desc)
 
     print(" ")
     print("---------------- Example Commands ----------------")
diff --git a/machine_common_sense/scripts/run_init_scenes.py b/machine_common_sense/scripts/run_init_scenes.py
index b308ff848..bfdabec9d 100644
--- a/machine_common_sense/scripts/run_init_scenes.py
+++ b/machine_common_sense/scripts/run_init_scenes.py
@@ -55,7 +55,7 @@ def main():
             scene_data['name'] +
             '.png')
 
-    controller.end_scene("", 1)
+    controller.end_scene()
 
 
 if __name__ == "__main__":
diff --git a/machine_common_sense/scripts/run_scene_timer.py b/machine_common_sense/scripts/run_scene_timer.py
index fe973e863..ff29f7ced 100644
--- a/machine_common_sense/scripts/run_scene_timer.py
+++ b/machine_common_sense/scripts/run_scene_timer.py
@@ -49,7 +49,7 @@ def run_scene(controller, file_name):
         end = time.perf_counter()
         step_time_list.append(end - start)
 
-    output = controller.end_scene("", "")
+    output = controller.end_scene()
 
     return step_time_list
 
diff --git a/machine_common_sense/scripts/run_scene_with_command_file.py b/machine_common_sense/scripts/run_scene_with_command_file.py
index bebb6fe39..a65c17ca6 100644
--- a/machine_common_sense/scripts/run_scene_with_command_file.py
+++ b/machine_common_sense/scripts/run_scene_with_command_file.py
@@ -2,7 +2,7 @@
 
 import machine_common_sense as mcs
 
-commandList = []
+commands = []
 
 
 def parse_args():
@@ -31,7 +31,7 @@ def run_commands(controller, config_data, command_data):
     for command in command_data:
         _ = controller.step(command)
 
-    controller.end_scene("", 1)
+    controller.end_scene()
 
 
 def main():
diff --git a/machine_common_sense/scripts/runner_script.py b/machine_common_sense/scripts/runner_script.py
index 936da382d..adbc53cd6 100644
--- a/machine_common_sense/scripts/runner_script.py
+++ b/machine_common_sense/scripts/runner_script.py
@@ -227,7 +227,7 @@ def run_scene(
                 break
             action, params = action_callback(scene_data, step_metadata, self)
 
-        controller.end_scene("", 1)
+        controller.end_scene()
 
         return scene_data['name']
 
diff --git a/machine_common_sense/serializer.py b/machine_common_sense/serializer.py
index 1f828ba39..b0775d03e 100644
--- a/machine_common_sense/serializer.py
+++ b/machine_common_sense/serializer.py
@@ -1,7 +1,5 @@
 import io
-import json
 from abc import ABCMeta, abstractmethod
-from typing import Dict, Union
 
 import msgpack
 import numpy as np
@@ -62,7 +60,7 @@ def _ext_pack(x):
                     x.action_list, x.camera_aspect_ratio,
                     x.camera_clipping_planes, x.camera_field_of_view,
                     x.camera_height, x.depth_map_list, x.goal, x.head_tilt,
-                    x.image_list, x.object_list, x.object_mask_list, x.pose,
+                    x.image_list, x.object_list, x.object_mask_list,
                     x.position, x.return_status, x.reward, x.rotation,
                     x.step_number, x.structural_object_list
                 ],
@@ -94,10 +92,10 @@ def _ext_pack(x):
             return msgpack.ExtType(
                 5,
                 msgpack.packb([
-                    x.uuid, x.color, x.dimensions, x.direction, x.distance,
+                    x.uuid, x.dimensions, x.direction, x.distance,
                     x.distance_in_steps, x.distance_in_world, x.held, x.mass,
-                    x.material_list, x.position, x.rotation, x.shape,
-                    x.state_list, x.texture_color_list, x.visible
+                    x.material_list, x.position, x.rotation, x.segment_color,
+                    x.shape, x.state_list, x.texture_color_list, x.visible
                 ],
                     default=SerializerMsgPack._ext_pack,
                     strict_types=True))
@@ -118,7 +116,7 @@ def _ext_unpack(code, data):
         if code == 1:
             action_list, camera_aspect_ratio, camera_clipping_planes, \
                 camera_field_of_view, camera_height, depth_map_list, goal, \
-                head_tilt, image_list, object_list, object_mask_list, pose, \
+                head_tilt, image_list, object_list, object_mask_list, \
                 position, return_status, reward, rotation, step_number, \
                 structural_object_list = \
                 msgpack.unpackb(data, ext_hook=SerializerMsgPack._ext_unpack)
@@ -133,7 +131,6 @@ def _ext_unpack(code, data):
                                 image_list=image_list,
                                 object_list=object_list,
                                 object_mask_list=object_mask_list,
-                                pose=pose,
                                 position=position,
                                 return_status=return_status,
                                 reward=reward,
@@ -191,171 +188,11 @@ def serialize(step_metadata: StepMetadata):
         Returns:
             Serialized version of step metadata in MsgPack format.
         """
-        serialized = msgpack.packb(step_metadata,
-                                   default=SerializerMsgPack._ext_pack,
-                                   strict_types=True)
-        return serialized
+        return msgpack.packb(step_metadata,
+                             default=SerializerMsgPack._ext_pack,
+                             strict_types=True)
 
     @staticmethod
     def deserialize(packed_step_metadata):
-        deserialized = msgpack.unpackb(packed_step_metadata,
-                                       ext_hook=SerializerMsgPack._ext_unpack)
-        return deserialized
-
-
-class SerializerJson(ISerializer):
-    """Serializer to (de)serialize StepMetadata into/from MsgPack format."""
-    class McsStepMetadataEncoder(json.JSONEncoder):
-        """
-        JSON Encoder Class for MCS Step Metadata, e.g.
-        json_dump = json.dumps(output, cls=McsStepMetadataEncoder, indent=4)
-        """
-
-        # Empty docstring here to override superclass function docstring.
-        def default(self, x):
-            """"""
-            if isinstance(x, StepMetadata):
-                return {
-                    'action_list': x.action_list,
-                    'camera_aspect_ratio': x.camera_aspect_ratio,
-                    'camera_clipping_planes': x.camera_clipping_planes,
-                    'camera_field_of_view': x.camera_field_of_view,
-                    'camera_height': x.camera_height,
-                    'depth_map_list': x.depth_map_list,
-                    'goal': x.goal,
-                    'head_tilt': x.head_tilt,
-                    'image_list': x.image_list,
-                    'object_list': x.object_list,
-                    'object_mask_list': x.object_mask_list,
-                    'pose': x.pose,
-                    'position': x.position,
-                    'return_status': x.return_status,
-                    'reward': x.reward,
-                    'rotation': x.rotation,
-                    'step_number': x.step_number,
-                    'structural_object_list': x.structural_object_list
-                }
-            elif isinstance(x, tuple):
-                return [x[0], x[1]]
-            elif isinstance(x, Image.Image):
-                # Could also use: return x.tobytes().decode("latin1")
-                # or: buffer = BytesIO() x.save(buff, format="PNG")
-                #     base64.b64encode(buffer.getvalue()).decode('ascii')
-                return np.array(x).tolist()
-            elif isinstance(x, GoalMetadata):
-                return {
-                    'action_list': x.action_list,
-                    'category': x.category,
-                    'description': x.description,
-                    'habituation_total': x.habituation_total,
-                    'last_preview_phase_step': x.last_preview_phase_step,
-                    'last_step': x.last_step,
-                    'metadata': x.metadata
-                }
-            elif isinstance(x, ObjectMetadata):
-                return {
-                    'uuid': x.uuid,
-                    'dimensions': x.dimensions,
-                    'direction': x.direction,
-                    'distance': x.distance,
-                    'distance_in_steps': x.distance_in_steps,
-                    'distance_in_world': x.distance_in_world,
-                    'held': x.held,
-                    'mass': x.mass,
-                    'material_list': x.material_list,
-                    'position': x.position,
-                    'rotation': x.rotation,
-                    'segment_color': x.segment_color,
-                    'shape': x.shape,
-                    'state_list': x.state_list,
-                    'texture_color_list': x.texture_color_list,
-                    'visible': x.visible
-                }
-            elif isinstance(x, np.ndarray):
-                return x.tolist()
-            return json.JSONEncoder.default(self, x)
-
-    @staticmethod
-    def convert_object_list(raw_list):
-        """Iterates over JSON object list and creates ObjectMetadata objects
-        for each JSON object.
-        """
-        object_list = []
-        for object_raw in raw_list:
-            obj = ObjectMetadata(
-                object_raw['uuid'],
-                object_raw['dimensions'],
-                object_raw['direction'],
-                object_raw['distance'],
-                object_raw['distance_in_steps'],
-                object_raw['distance_in_world'],
-                object_raw['held'],
-                object_raw['mass'],
-                object_raw['material_list'],
-                object_raw['position'],
-                object_raw['rotation'],
-                object_raw['segment_color'],
-                object_raw['shape'],
-                object_raw['state_list'],
-                object_raw['texture_color_list'],
-                object_raw['visible'])
-            object_list.append(obj)
-        return object_list
-
-    @staticmethod
-    def serialize(step_metadata: StepMetadata, indent: int = 4):
-        json_dump = json.dumps(step_metadata,
-                               cls=SerializerJson.McsStepMetadataEncoder,
-                               indent=indent)
-        return json_dump
-
-    @staticmethod
-    def deserialize(input_json: Union[Dict, str]):
-        if isinstance(input_json, str):
-            input_json = json.loads(input_json)
-        depth_map_list = []
-        for depth_map_raw in input_json['depth_map_list']:
-            depth_map_list.append(np.array(depth_map_raw, dtype='uint8'))
-
-        object_mask_list = []
-        for obj_mask_raw in input_json['object_mask_list']:
-            object_mask_list.append(np.array(obj_mask_raw, dtype='uint8'))
-
-        image_list = []
-        for img_raw in input_json['image_list']:  # PIL
-            image_list.append(Image.fromarray(np.array(img_raw,
-                                                       dtype='uint8')))
-
-        object_list_raw = input_json['object_list']
-        object_list = SerializerJson.convert_object_list(object_list_raw)
-
-        structural_object_list_raw = input_json['structural_object_list']
-        structural_object_list = SerializerJson.convert_object_list(
-            structural_object_list_raw)
-
-        goal_raw = input_json['goal']
-        goal = GoalMetadata(goal_raw['action_list'], goal_raw['category'],
-                            goal_raw['description'],
-                            goal_raw['habituation_total'],
-                            goal_raw['last_preview_phase_step'],
-                            goal_raw['last_step'], goal_raw['metadata'])
-
-        return StepMetadata(
-            action_list=input_json['action_list'],
-            camera_aspect_ratio=input_json['camera_aspect_ratio'],
-            camera_clipping_planes=input_json['camera_clipping_planes'],
-            camera_field_of_view=input_json['camera_field_of_view'],
-            camera_height=input_json['camera_height'],
-            depth_map_list=depth_map_list,
-            goal=goal,
-            head_tilt=input_json['head_tilt'],
-            image_list=image_list,
-            object_list=object_list,
-            object_mask_list=object_mask_list,
-            pose=input_json['pose'],
-            position=input_json['position'],
-            return_status=input_json['return_status'],
-            reward=input_json['reward'],
-            rotation=input_json['rotation'],
-            step_number=input_json['step_number'],
-            structural_object_list=structural_object_list)
+        return msgpack.unpackb(packed_step_metadata,
+                               ext_hook=SerializerMsgPack._ext_unpack)
diff --git a/machine_common_sense/step_metadata.py b/machine_common_sense/step_metadata.py
index e72852c70..c46ee91cf 100644
--- a/machine_common_sense/step_metadata.py
+++ b/machine_common_sense/step_metadata.py
@@ -1,7 +1,6 @@
 import copy
 
 from .goal_metadata import GoalMetadata
-from .pose import Pose
 from .return_status import ReturnStatus
 from .stringifier import Stringifier
 
@@ -46,8 +45,7 @@ class StepMetadata:
         The player camera's field of view. This will remain constant for
         the whole scene.
     camera_height : float
-        The player camera's height. This will change if the player uses
-        actions like "LieDown", "Stand", or "Crawl".
+        The player camera's height.
     depth_map_list : list of 2D numpy arrays
         The list of 2-dimensional numpy arrays of depth float data from the
         scene after the last action and physics simulation were run. This is
@@ -90,8 +88,6 @@ class StepMetadata:
         The radius of the performer.
     performer_reach: float
         The max reach of the performer.
-    pose : string
-        Your current pose. Either "STANDING", "CRAWLING", or "LYING".
     position : dict
         The "x", "y", and "z" coordinates for your global position.
         Will be set to 'None' if using a metadata level below the
@@ -113,7 +109,7 @@ class StepMetadata:
         The list of metadata for all the visible structural objects (like
         walls, occluders, and ramps) in the scene. This list will be empty
         if using a metadata level below the 'oracle' level.
-        Please note that occluders are composed of two separate objects,
+        Occluders are composed of two separate objects,
         the "wall" and the "pole", with corresponding object IDs
         (occluder_wall_<uuid> and occluder_pole_<uuid>), and ramps are
         composed of between one and three objects (depending on the type
@@ -137,7 +133,6 @@ def __init__(
         performer_radius=0.0,
         performer_reach=0.0,
         physics_frames_per_second=0,
-        pose=Pose.UNDEFINED.value,
         position=None,
         return_status=ReturnStatus.UNDEFINED.value,
         reward=0,
@@ -169,7 +164,6 @@ def __init__(
         self.performer_radius = performer_radius
         self.performer_reach = performer_reach
         self.physics_frames_per_second = physics_frames_per_second
-        self.pose = pose
         self.position = {} if position is None else position
         self.return_status = return_status
         self.reward = reward
@@ -210,7 +204,6 @@ def __iter__(self):
         yield 'performer_radius', self.performer_radius
         yield 'performer_reach', self.performer_reach
         yield 'physics_frames_per_second', self.physics_frames_per_second
-        yield 'pose', self.pose
         yield 'position', self.position
         yield 'return_status', self.return_status
         yield 'reward', self.reward
diff --git a/machine_common_sense/stringifier.py b/machine_common_sense/stringifier.py
index f5302ebf7..03e7ede2b 100644
--- a/machine_common_sense/stringifier.py
+++ b/machine_common_sense/stringifier.py
@@ -33,23 +33,20 @@ def class_to_str(input_class, depth=0):
         """
         this_indent = " " * Stringifier.NUMBER_OF_SPACES * depth
         next_indent = " " * Stringifier.NUMBER_OF_SPACES * (depth + 1)
-        text_list = []
         props = {
             prop_key: prop_value
             for prop_key, prop_value in vars(input_class).items()
             if not prop_key.startswith('_') or callable(prop_value)
         }
-        for prop_key, prop_value in props.items():
-            text_list.append(
-                next_indent +
-                "\"" +
-                prop_key +
-                "\": " +
-                Stringifier.value_to_str(
-                    prop_value,
-                    depth +
-                    1))
-        return "{}" if len(text_list) == 0 else "{\n" + \
+        text_list = [next_indent +
+                     "\"" +
+                     prop_key +
+                     "\": " +
+                     Stringifier.value_to_str(
+                         prop_value,
+                         depth +
+                         1) for prop_key, prop_value in props.items()]
+        return "{}" if not text_list else "{\n" + \
             (",\n").join(text_list) + "\n" + this_indent + "}"
 
     @staticmethod
@@ -85,26 +82,38 @@ def generate_pretty_object_output(object_list):
                 metadata.uuid,
                 metadata.shape,
                 ", ".join(metadata.texture_color_list)
-                if(metadata.texture_color_list is not None)
+                if (metadata.texture_color_list is not None)
                 else metadata.texture_color_list,
                 metadata.held,
                 metadata.visible,
                 ", ".join(metadata.state_list)
-                if(metadata.state_list is not None) else metadata.state_list,
+                if (metadata.state_list is not None)
+                else metadata.state_list,
                 Stringifier.vector_to_string(metadata.position),
                 metadata.distance_in_world,
                 Stringifier.vector_to_string(metadata.direction),
-                ("[" + ", ".join([
-                    Stringifier.vector_to_string(corner) for corner
-                    in metadata.dimensions
-                ]) + "]") if metadata.dimensions else None
+                (
+                    (
+                        "[" +
+                        ", ".join(
+                            Stringifier.vector_to_string(corner)
+                            for corner in metadata.dimensions
+                        )
+                    ) +
+                    "]"
+                )
+                if metadata.dimensions
+                else None,
             ]
             for metadata in object_list
         ]
+
         widths = [max(len(str(row[i])) for row in rows)
-                  for i in range(0, len(titles))]
-        return [("  ".join(str(row[i]).ljust(widths[i])
-                           for i in range(0, len(row)))) for row in rows]
+                  for i in range(len(titles))]
+        return [
+            "  ".join(str(row[i]).ljust(widths[i]) for i in range(len(row)))
+            for row in rows
+        ]
 
     @staticmethod
     def value_to_str(input_value, depth=0):
@@ -132,16 +141,15 @@ def value_to_str(input_value, depth=0):
                 Stringifier.value_to_str(dict_value, depth + 1)
                 for dict_key, dict_value in input_value.items()
             ]
-            return "{}" if len(text_list) == 0 else "{\n" + \
+            return "{}" if not text_list else "{\n" + \
                 (",\n").join(text_list) + "\n" + this_indent + "}"
         if isinstance(input_value, (list, tuple, numpy.ndarray)):
             input_value_as_list = list(input_value)
-            # Condense the list output unless it has any nested dicts or lists.
-            condense = True
-            for list_item in input_value_as_list:
-                if isinstance(list_item, (dict, list, tuple, numpy.ndarray)):
-                    condense = False
-                    break
+            condense = not any(
+                isinstance(list_item, (dict, list, tuple, numpy.ndarray))
+                for list_item in input_value_as_list
+            )
+
             if condense:
                 # To condense the list output, remove all of the whitespace.
                 text_list = [
@@ -154,7 +162,7 @@ def value_to_str(input_value, depth=0):
                 next_indent + Stringifier.value_to_str(list_item, depth + 1)
                 for list_item in input_value_as_list
             ]
-            return "[]" if len(text_list) == 0 else "[\n" + \
+            return "[]" if not text_list else "[\n" + \
                 (",\n").join(text_list) + "\n" + this_indent + "]"
         if isinstance(input_value, bool):
             return "true" if input_value else "false"
diff --git a/machine_common_sense/setup.py b/machine_common_sense/subscriber.py
similarity index 100%
rename from machine_common_sense/setup.py
rename to machine_common_sense/subscriber.py
diff --git a/machine_common_sense/unity_executable_provider.py b/machine_common_sense/unity_executable_provider.py
index eb77d52e4..4341bd7d9 100644
--- a/machine_common_sense/unity_executable_provider.py
+++ b/machine_common_sense/unity_executable_provider.py
@@ -119,8 +119,7 @@ def _get_version_dir(self, version: str) -> Path:
         '''returns a Path object for the path to the directory for the given
          version.
         '''
-        ver_path = self._base / version
-        return ver_path
+        return self._base / version
 
     def has_version(self, version: str) -> bool:
         '''Return whether the cache has the given version'''
diff --git a/machine_common_sense/validation.py b/machine_common_sense/validation.py
deleted file mode 100644
index 1caf0b00c..000000000
--- a/machine_common_sense/validation.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-class Validation():
-    '''
-    Support for tests and conditions on validation numbers and strings.
-    '''
-    @staticmethod
-    def is_in_range(value, min_value, max_value, default_value, label=None):
-        """
-        Returns if the given value is within the given min and max; if not,
-        returns the given default.
-
-        Parameters
-        ----------
-        value : number
-            The input value.
-        min_value : number
-            The min value.
-        max_value : number
-            The max value.
-        default_value : number
-            The default value.
-        label : string
-            A label for the input value.  If given, and if the input
-            value is not within the range, will print an error.
-
-        Returns
-        -------
-        number
-        """
-        if value > max_value or value < min_value:
-            if label is not None:
-                logger.debug(
-                    f'Value of {label} needs to be between '
-                    f'{min_value} and {max_value}. '
-                    f'Current value {value} '
-                    f'will be reset to {default_value}.')
-            return default_value
-        return value
-
-    @staticmethod
-    def is_number(value, label=None):
-        """
-        Returns if the given value is a number.
-
-        Parameters
-        ----------
-        value :
-            The input value.
-        label : string
-            A label for the input value.  If given, and if the
-            input value is not a number, will print an error.
-
-        Returns
-        -------
-        boolean
-        """
-        try:
-            float(value)
-            return True
-        except ValueError:
-            if label is not None:
-                logger.debug(f'Value of {label}'
-                             f' needs to be a number. Will be set to 0.')
-            return False
diff --git a/requirements.txt b/requirements.txt
index 8ee80d5d2..0177fadb2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,20 +2,23 @@ ai2thor==2.5.0
 autopep8==1.5.4
 colour==0.1.5
 commonmark==0.9.1
-dataclasses==0.8; python_version<"3.7"
-flake8==3.8.3
+flake8==3.9.1
 isort==5.8.0
 marshmallow==3.12.1
-matplotlib==3.3.2
+matplotlib==3.3.3
 msgpack==1.0.0
-numpy==1.19.4
-opencv-python==4.4.0.44
+numpyencoder==0.3.0
+opencv-python==4.4.0.46; python_version<="3.9"
+opencv-python==4.5.4.60; python_version>="3.10"
+pep8-naming==0.12.1
 pre-commit==2.12.1
 recommonmark==0.6.0
 requests==2.24.0
-scikit-image==0.17.2
-Shapely==1.7.1
+scikit-image==0.18.3
+Shapely==1.7.1; python_version<"3.10"
+Shapely==1.8.0; python_version>="3.10"
 Sphinx==3.2.1
 sphinxcontrib-websupport==1.2.4
 sphinx-markdown-builder==0.5.4
-git+git://github.com/sphinx-contrib/video.git
+typeguard==2.13.2
+git+https://github.com/sphinx-contrib/video.git
diff --git a/setup.py b/setup.py
index b5a82ffb7..9f247f7b8 100644
--- a/setup.py
+++ b/setup.py
@@ -25,12 +25,27 @@ def get_version():
     long_description=long_description,
     long_description_content_type='text/markdown',
     classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Programming Language :: Python',
         'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
         'License :: OSI Approved :: Apache Software License',
+        'Natural Language :: English',
         'Operating System :: OS Independent',
+        'Intended Audience :: Science/Research',
+        'Intended Audience :: Developers',
+        'Topic :: Software Development',
+        'Topic :: Scientific/Engineering',
+        'Topic :: Scientific/Engineering :: Artificial Intelligence',
+        'Topic :: Games/Entertainment :: Simulation',
+        'Topic :: Multimedia :: Graphics',
+        'Topic :: Multimedia :: Graphics :: 3D Rendering',
+        'Typing :: Typed'
     ],
     license='Apache-2',
-    python_requires=">3.6",
+    python_requires=">=3.7,<3.10",
     packages=setuptools.find_packages(),
     package_data={'': ['*.ini'], },
     install_requires=[
@@ -39,10 +54,11 @@ def get_version():
         'opencv-python>=4.0',
         'matplotlib>=3.3',
         'msgpack>=1.0.0',
+        'numpyencoder>=0.3.0',
         'ai2thor==2.5.0',
         'scikit-image>=0.17.1',
-        'dataclasses==0.8; python_version<"3.7"',
-        'marshmallow>=3.5.2'
+        'marshmallow>=3.5.2',
+        'typeguard>=2.11.1'
     ],
     entry_points={
         'console_scripts': [


Test patch:
diff --git a/integration_tests/README.md b/integration_tests/README.md
index b22e27ee4..175225436 100644
--- a/integration_tests/README.md
+++ b/integration_tests/README.md
@@ -62,7 +62,4 @@ While adding a new test scene, you can run with the `--dev` flag to run the whol
 - Self teleportation (MCS-540)
 - PullObject, various mass and force (when needed by an evaluation task)
 - PushObject, various mass and force (when needed by an evaluation task)
-- ThrowObject, various mass and force (when needed by an evaluation task)
-- Change pose between Stand, Crawl, and LieDown (when needed by an evaluation task)
-- Crawl under a table that is too short to be walked under (when needed by an evaluation task)
 - PickupObject on a "pickupable" object that is too large or heavy (when needed by an evaluation task)
diff --git a/integration_tests/additional_integration_tests.py b/integration_tests/additional_integration_tests.py
index 1b4c892ac..fbdfd991e 100644
--- a/integration_tests/additional_integration_tests.py
+++ b/integration_tests/additional_integration_tests.py
@@ -52,18 +52,18 @@ def run_depth_and_segmentation_test(controller, metadata_tier):
     step_metadata_1 = controller.step('Pass')
 
     # Verify the depth map list and object mask list from step 1.
-    if len(step_metadata_0.depth_map_list) != 1:
+    if len(step_metadata_1.depth_map_list) != 1:
         return (
             False,
             f'Step 1 {metadata_tier} failed: depth_map_list with length '
-            f'{len(step_metadata_0.depth_map_list)} should be length 1'
+            f'{len(step_metadata_1.depth_map_list)} should be length 1'
         )
     if metadata_tier == 'level2' or metadata_tier == 'oracle':
-        if len(step_metadata_0.object_mask_list) != 1:
+        if len(step_metadata_1.object_mask_list) != 1:
             return (
                 False,
                 f'Step 1 {metadata_tier} failed: object_mask_list with length '
-                f'{len(step_metadata_0.object_mask_list)} should be length 1'
+                f'{len(step_metadata_1.object_mask_list)} should be length 1'
             )
     # Verify the consistent segment_color of each object across both steps.
     if metadata_tier == 'oracle':
@@ -109,7 +109,7 @@ def run_depth_and_segmentation_test(controller, metadata_tier):
             )
 
     # Stop the test scene.
-    controller.end_scene("", 1)
+    controller.end_scene()
 
     # Validation successful!
     return True, ''
@@ -191,7 +191,7 @@ def run_habituation_trial_counts_test(controller, metadata_tier):
             )
 
     # Stop the test scene.
-    controller.end_scene("", 1)
+    controller.end_scene()
 
     # Validation successful!
     return True, ''
@@ -215,7 +215,7 @@ def run_numpy_array_data_test(controller, metadata_tier):
         step_metadata = controller.step('Pass')
 
     # Stop the test scene.
-    controller.end_scene("", 1)
+    controller.end_scene()
 
     if not step_metadata:
         return False, 'Failed to load scene with numpy array data'
@@ -248,7 +248,7 @@ def run_public_sample_scenes_test(controller, metadata_tier):
             failed_test_list.append(os.path.basename(scene_filename))
 
         # Stop the test scene.
-        controller.end_scene("", 1)
+        controller.end_scene()
 
     return (
         len(failed_test_list) == 0,
@@ -387,7 +387,7 @@ def run_restricted_action_list_test(controller, metadata_tier):
         )
 
     # Stop the test scene.
-    controller.end_scene("", 1)
+    controller.end_scene()
 
     # Validation successful!
     return True, ''
diff --git a/integration_tests/data/023.open_and_close_non_container.level1.outputs.json b/integration_tests/data/023.open_and_close_non_container.level1.outputs.json
index 26ff989ce..1e444aa01 100644
--- a/integration_tests/data/023.open_and_close_non_container.level1.outputs.json
+++ b/integration_tests/data/023.open_and_close_non_container.level1.outputs.json
@@ -12,7 +12,7 @@
     "head_tilt": 0,
     "position_x": null,
     "position_z": null,
-    "return_status": "NOT_RECEPTACLE",
+    "return_status": "NOT_OPENABLE",
     "rotation_y": null,
     "objects_count": 0,
     "structural_objects_count": 0
@@ -21,7 +21,7 @@
     "head_tilt": 0,
     "position_x": null,
     "position_z": null,
-    "return_status": "NOT_RECEPTACLE",
+    "return_status": "NOT_OPENABLE",
     "rotation_y": null,
     "objects_count": 0,
     "structural_objects_count": 0
diff --git a/integration_tests/data/023.open_and_close_non_container.level2.outputs.json b/integration_tests/data/023.open_and_close_non_container.level2.outputs.json
index 26ff989ce..1e444aa01 100644
--- a/integration_tests/data/023.open_and_close_non_container.level2.outputs.json
+++ b/integration_tests/data/023.open_and_close_non_container.level2.outputs.json
@@ -12,7 +12,7 @@
     "head_tilt": 0,
     "position_x": null,
     "position_z": null,
-    "return_status": "NOT_RECEPTACLE",
+    "return_status": "NOT_OPENABLE",
     "rotation_y": null,
     "objects_count": 0,
     "structural_objects_count": 0
@@ -21,7 +21,7 @@
     "head_tilt": 0,
     "position_x": null,
     "position_z": null,
-    "return_status": "NOT_RECEPTACLE",
+    "return_status": "NOT_OPENABLE",
     "rotation_y": null,
     "objects_count": 0,
     "structural_objects_count": 0
diff --git a/integration_tests/data/023.open_and_close_non_container.oracle.outputs.json b/integration_tests/data/023.open_and_close_non_container.oracle.outputs.json
index 4150dc0e2..a5c02ea13 100644
--- a/integration_tests/data/023.open_and_close_non_container.oracle.outputs.json
+++ b/integration_tests/data/023.open_and_close_non_container.oracle.outputs.json
@@ -56,7 +56,7 @@
         "objects_count": 1,
         "position_x": 0.0,
         "position_z": 0.0,
-        "return_status": "NOT_RECEPTACLE",
+        "return_status": "NOT_OPENABLE",
         "rotation_y": 0.0,
         "step_number": 1,
         "structural_objects_count": 6
@@ -87,7 +87,7 @@
         "objects_count": 1,
         "position_x": 0.0,
         "position_z": 0.0,
-        "return_status": "NOT_RECEPTACLE",
+        "return_status": "NOT_OPENABLE",
         "rotation_y": 0.0,
         "step_number": 2,
         "structural_objects_count": 6
diff --git a/integration_tests/data/071.pickup_container.actions.txt b/integration_tests/data/071.pickup_container.actions.txt
index 43aafd65f..4be83abf8 100644
--- a/integration_tests/data/071.pickup_container.actions.txt
+++ b/integration_tests/data/071.pickup_container.actions.txt
@@ -1,6 +1,6 @@
 ﻿LookDown
 MoveAhead
 PickupObject,objectId=bowl_a
-Crawl
+Pass
 DropObject
 Pass
\ No newline at end of file
diff --git a/integration_tests/data/071.pickup_container.oracle.outputs.json b/integration_tests/data/071.pickup_container.oracle.outputs.json
index f730ed6f6..cd1ca7f55 100644
--- a/integration_tests/data/071.pickup_container.oracle.outputs.json
+++ b/integration_tests/data/071.pickup_container.oracle.outputs.json
@@ -124,7 +124,7 @@
         "structural_objects_count": 6
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 30,
         "objects": [
             {
@@ -132,7 +132,7 @@
                 "id": "apple_a",
                 "mass": 0.25,
                 "position_x": 0.0,
-                "position_y": 0.231,
+                "position_y": 0.612,
                 "position_z": 0.24,
                 "visible": true
             },
@@ -141,7 +141,7 @@
                 "id": "bowl_a",
                 "mass": 0.25,
                 "position_x": -0.001,
-                "position_y": 0.194,
+                "position_y": 0.575,
                 "position_z": 0.24,
                 "visible": true
             }
@@ -155,21 +155,21 @@
         "structural_objects_count": 6
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 30,
         "objects": [
             {
                 "held": false,
                 "id": "apple_a",
                 "mass": 0.25,
-                "position_y": 0.216,
+                "position_y": 0.597,
                 "visible": true
             },
             {
                 "held": false,
                 "id": "bowl_a",
                 "mass": 0.25,
-                "position_y": 0.179,
+                "position_y": 0.56,
                 "visible": true
             }
         ],
@@ -182,21 +182,21 @@
         "structural_objects_count": 6
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 30,
         "objects": [
             {
                 "held": false,
                 "id": "apple_a",
                 "mass": 0.25,
-                "position_y": 0.178,
+                "position_y": 0.559,
                 "visible": true
             },
             {
                 "held": false,
                 "id": "bowl_a",
                 "mass": 0.25,
-                "position_y": 0.14,
+                "position_y": 0.521,
                 "visible": true
             }
         ],
diff --git a/integration_tests/data/076.ramps_obstructed_60deg.actions.txt b/integration_tests/data/076.ramps_obstructed_60deg.actions.txt
index 94fe53c69..aff746011 100644
--- a/integration_tests/data/076.ramps_obstructed_60deg.actions.txt
+++ b/integration_tests/data/076.ramps_obstructed_60deg.actions.txt
@@ -1,13 +1,13 @@
 MoveRight
 MoveRight
-Crawl
+Pass
 MoveAhead
 MoveAhead
 MoveRight
 MoveAhead
 MoveAhead
 MoveAhead
-LieDown
+Pass
 MoveAhead
 MoveAhead
 MoveAhead
diff --git a/integration_tests/data/076.ramps_obstructed_60deg.oracle.outputs.json b/integration_tests/data/076.ramps_obstructed_60deg.oracle.outputs.json
index 2f738117f..f575183c7 100644
--- a/integration_tests/data/076.ramps_obstructed_60deg.oracle.outputs.json
+++ b/integration_tests/data/076.ramps_obstructed_60deg.oracle.outputs.json
@@ -84,21 +84,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.3,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.4,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 3,
         "structural_objects": [
             {
-                "direction_x": 0.34,
-                "direction_y": -0.649,
-                "direction_z": -0.681,
-                "distance": 0.588,
+                "direction_x": 0.226,
+                "direction_y": -0.862,
+                "direction_z": -0.453,
+                "distance": 0.884,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -112,21 +112,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.3,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.3,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 4,
         "structural_objects": [
             {
-                "direction_x": 0.381,
-                "direction_y": -0.726,
-                "direction_z": -0.572,
-                "distance": 0.525,
+                "direction_x": 0.237,
+                "direction_y": -0.904,
+                "direction_z": -0.356,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -140,21 +140,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.3,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 5,
         "structural_objects": [
             {
-                "direction_x": 0.421,
-                "direction_y": -0.803,
-                "direction_z": -0.421,
-                "distance": 0.475,
+                "direction_x": 0.246,
+                "direction_y": -0.937,
+                "direction_z": -0.246,
+                "distance": 0.813,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -168,21 +168,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 6,
         "structural_objects": [
             {
-                "direction_x": 0.572,
-                "direction_y": -0.726,
-                "direction_z": -0.381,
-                "distance": 0.525,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -196,21 +196,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 7,
         "structural_objects": [
             {
-                "direction_x": 0.572,
-                "direction_y": -0.726,
-                "direction_z": -0.381,
-                "distance": 0.525,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -224,21 +224,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 8,
         "structural_objects": [
             {
-                "direction_x": 0.572,
-                "direction_y": -0.726,
-                "direction_z": -0.381,
-                "distance": 0.525,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -252,21 +252,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 9,
         "structural_objects": [
             {
-                "direction_x": 0.572,
-                "direction_y": -0.726,
-                "direction_z": -0.381,
-                "distance": 0.525,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -280,21 +280,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.1,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.1,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 10,
         "structural_objects": [
             {
-                "direction_x": 0.802,
-                "direction_y": -0.267,
-                "direction_z": -0.535,
-                "distance": 0.374,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -308,21 +308,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.1,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.1,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 11,
         "structural_objects": [
             {
-                "direction_x": 0.802,
-                "direction_y": -0.267,
-                "direction_z": -0.535,
-                "distance": 0.374,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -336,21 +336,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.1,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.1,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 12,
         "structural_objects": [
             {
-                "direction_x": 0.802,
-                "direction_y": -0.267,
-                "direction_z": -0.535,
-                "distance": 0.374,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -364,21 +364,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.1,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.1,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 13,
         "structural_objects": [
             {
-                "direction_x": 0.802,
-                "direction_y": -0.267,
-                "direction_z": -0.535,
-                "distance": 0.374,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -392,21 +392,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.1,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.1,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 14,
         "structural_objects": [
             {
-                "direction_x": 0.802,
-                "direction_y": -0.267,
-                "direction_z": -0.535,
-                "distance": 0.374,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -420,21 +420,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.1,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 4.2,
-        "position_y": 0.1,
+        "position_y": 0.762,
         "position_z": 0.2,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 15,
         "structural_objects": [
             {
-                "direction_x": 0.802,
-                "direction_y": -0.267,
-                "direction_z": -0.535,
-                "distance": 0.374,
+                "direction_x": 0.356,
+                "direction_y": -0.904,
+                "direction_z": -0.237,
+                "distance": 0.843,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -475,4 +475,4 @@
         ],
         "structural_objects_count": 7
     }
-]
+]
\ No newline at end of file
diff --git a/integration_tests/data/077.ramps_obstructed_from_side.actions.txt b/integration_tests/data/077.ramps_obstructed_from_side.actions.txt
index 83345fcdd..f4d862a38 100644
--- a/integration_tests/data/077.ramps_obstructed_from_side.actions.txt
+++ b/integration_tests/data/077.ramps_obstructed_from_side.actions.txt
@@ -9,7 +9,7 @@ MoveAhead
 MoveAhead
 MoveAhead
 MoveLeft
-Crawl
+Pass
 MoveAhead
 MoveAhead
 MoveLeft
diff --git a/integration_tests/data/077.ramps_obstructed_from_side.oracle.outputs.json b/integration_tests/data/077.ramps_obstructed_from_side.oracle.outputs.json
index e3c67ab9d..00bc30e0e 100644
--- a/integration_tests/data/077.ramps_obstructed_from_side.oracle.outputs.json
+++ b/integration_tests/data/077.ramps_obstructed_from_side.oracle.outputs.json
@@ -336,21 +336,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 3.9,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": -0.3,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 12,
         "structural_objects": [
             {
-                "direction_x": 0.778,
-                "direction_y": -0.494,
-                "direction_z": 0.389,
-                "distance": 0.771,
+                "direction_x": 0.591,
+                "direction_y": -0.751,
+                "direction_z": 0.296,
+                "distance": 1.015,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -364,21 +364,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 3.9,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": -0.4,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 13,
         "structural_objects": [
             {
-                "direction_x": 0.736,
-                "direction_y": -0.467,
-                "direction_z": 0.49,
-                "distance": 0.816,
+                "direction_x": 0.572,
+                "direction_y": -0.726,
+                "direction_z": 0.381,
+                "distance": 1.049,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -392,21 +392,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 3.9,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": -0.5,
         "return_status": "SUCCESSFUL",
         "rotation_y": 180.0,
         "step_number": 14,
         "structural_objects": [
             {
-                "direction_x": 0.69,
-                "direction_y": -0.438,
-                "direction_z": 0.575,
-                "distance": 0.869,
+                "direction_x": 0.55,
+                "direction_y": -0.698,
+                "direction_z": 0.458,
+                "distance": 1.091,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -420,21 +420,21 @@
         "structural_objects_count": 7
     },
     {
-        "camera_height": 0.381,
+        "camera_height": 0.762,
         "head_tilt": 0,
         "objects_count": 0,
         "position_x": 3.9,
-        "position_y": 0.381,
+        "position_y": 0.762,
         "position_z": -0.5,
         "return_status": "OBSTRUCTED",
         "rotation_y": 180.0,
         "step_number": 15,
         "structural_objects": [
             {
-                "direction_x": 0.69,
-                "direction_y": -0.438,
-                "direction_z": 0.575,
-                "distance": 0.869,
+                "direction_x": 0.55,
+                "direction_y": -0.698,
+                "direction_z": 0.458,
+                "distance": 1.091,
                 "id": "wall_7",
                 "position_x": 4.5,
                 "position_z": 0.0,
@@ -475,4 +475,4 @@
         ],
         "structural_objects_count": 7
     }
-]
+]
\ No newline at end of file
diff --git a/integration_tests/data/079.ramps_pose_change.actions.txt b/integration_tests/data/079.ramps_pose_change.actions.txt
deleted file mode 100644
index 1cb86c7fc..000000000
--- a/integration_tests/data/079.ramps_pose_change.actions.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-MoveAhead
-MoveAhead
-MoveAhead
-MoveAhead
-Crawl
-MoveBack
-LieDown
-MoveLeft
-MoveRight
-Crawl
-Stand
-MoveBack
-LieDown
-MoveAhead
-Crawl
diff --git a/integration_tests/data/079.ramps_pose_change.level1.outputs.json b/integration_tests/data/079.ramps_pose_change.level1.outputs.json
deleted file mode 100644
index 1d3b0109b..000000000
--- a/integration_tests/data/079.ramps_pose_change.level1.outputs.json
+++ /dev/null
@@ -1,172 +0,0 @@
-﻿[
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 0,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 1,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 2,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 3,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 4,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 5,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 6,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 7,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 8,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 9,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 10,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 11,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 12,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 13,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 14,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 15,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 16,
-        "structural_objects_count": 0
-    }
-]
diff --git a/integration_tests/data/079.ramps_pose_change.level2.outputs.json b/integration_tests/data/079.ramps_pose_change.level2.outputs.json
deleted file mode 100644
index 1d3b0109b..000000000
--- a/integration_tests/data/079.ramps_pose_change.level2.outputs.json
+++ /dev/null
@@ -1,172 +0,0 @@
-﻿[
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 0,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 1,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 2,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 3,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 4,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 5,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 6,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 7,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 8,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 9,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 10,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 11,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 12,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 13,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 14,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 15,
-        "structural_objects_count": 0
-    },
-    {
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": null,
-        "position_z": null,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": null,
-        "step_number": 16,
-        "structural_objects_count": 0
-    }
-]
diff --git a/integration_tests/data/079.ramps_pose_change.oracle.outputs.json b/integration_tests/data/079.ramps_pose_change.oracle.outputs.json
deleted file mode 100644
index 380b47ce0..000000000
--- a/integration_tests/data/079.ramps_pose_change.oracle.outputs.json
+++ /dev/null
@@ -1,478 +0,0 @@
-﻿[
-    {
-        "camera_height": 0.818,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.818,
-        "position_z": 0.1,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 0,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.992,
-                "direction_z": -0.122,
-                "distance": 0.824,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.917,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.917,
-        "position_z": 0.001,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 1,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -1.0,
-                "direction_z": -0.001,
-                "distance": 0.917,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 1.017,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 1.017,
-        "position_z": -0.099,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 2,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.995,
-                "direction_z": 0.097,
-                "distance": 1.022,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 1.116,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 1.116,
-        "position_z": -0.197,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 3,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.985,
-                "direction_z": 0.175,
-                "distance": 1.134,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 1.216,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 1.216,
-        "position_z": -0.297,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 4,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.971,
-                "direction_z": 0.238,
-                "distance": 1.251,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.833,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.833,
-        "position_z": -0.297,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 5,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.942,
-                "direction_z": 0.335,
-                "distance": 0.885,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.733,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.733,
-        "position_z": -0.197,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 6,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.965,
-                "direction_z": 0.259,
-                "distance": 0.759,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.452,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.452,
-        "position_z": -0.197,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 7,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.917,
-                "direction_z": 0.399,
-                "distance": 0.493,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.453,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.6,
-        "position_y": 0.453,
-        "position_z": -0.19,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 8,
-        "structural_objects": [
-            {
-                "direction_x": -0.2,
-                "direction_y": -0.904,
-                "direction_z": 0.379,
-                "distance": 0.501,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.445,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.445,
-        "position_z": -0.183,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 9,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.925,
-                "direction_z": 0.379,
-                "distance": 0.481,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.719,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.719,
-        "position_z": -0.183,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 10,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.969,
-                "direction_z": 0.246,
-                "distance": 0.742,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 1.1,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 1.1,
-        "position_z": -0.183,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 11,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.986,
-                "direction_z": 0.164,
-                "distance": 1.115,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 1.0,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 1.0,
-        "position_z": -0.082,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 12,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.997,
-                "direction_z": 0.082,
-                "distance": 1.004,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.338,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.338,
-        "position_z": -0.082,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 13,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.972,
-                "direction_z": 0.236,
-                "distance": 0.348,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.438,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.438,
-        "position_z": -0.174,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 14,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.929,
-                "direction_z": 0.37,
-                "distance": 0.471,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.711,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.711,
-        "position_z": -0.174,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 15,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.971,
-                "direction_z": 0.238,
-                "distance": 0.732,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    },
-    {
-        "camera_height": 0.981,
-        "head_tilt": 0,
-        "objects_count": 0,
-        "position_x": 4.5,
-        "position_y": 0.981,
-        "position_z": -0.3,
-        "return_status": "SUCCESSFUL",
-        "rotation_y": 180.0,
-        "step_number": 16,
-        "structural_objects": [
-            {
-                "direction_x": 0.0,
-                "direction_y": -0.956,
-                "direction_z": 0.293,
-                "distance": 1.026,
-                "id": "wall_7",
-                "position_x": 4.5,
-                "position_z": 0.0,
-                "shape": "structural",
-                "texture_color_list": [
-                    "yellow"
-                ],
-                "visible": true
-            }
-        ],
-        "structural_objects_count": 7
-    }
-]
\ No newline at end of file
diff --git a/integration_tests/data/079.ramps_pose_change.scene.json b/integration_tests/data/079.ramps_pose_change.scene.json
deleted file mode 100644
index a95116d01..000000000
--- a/integration_tests/data/079.ramps_pose_change.scene.json
+++ /dev/null
@@ -1,61 +0,0 @@
-﻿{
-    "name": "RAMPS4",
-    "ceilingMaterial": "AI2-THOR/Materials/Metals/Brass 1",
-    "floorMaterial": "AI2-THOR/Materials/Wood/WhiteWood",
-    "wallMaterial": "AI2-THOR/Materials/Metals/Brass 1",
-    "performerStart": {
-      "position": {
-        "x": 4.5,
-        "y": 0,
-        "z": 0.1
-      },
-      "rotation": {
-        "x": 0,
-        "y": 180,
-        "z": 0
-      }
-    },
-    "objects": [
-      {
-        "id": "wall_7",
-        "materials": ["AI2-THOR/Materials/Metals/Brass 1"],
-        "type": "cube",
-        "kinematic": "true",
-        "structure": "true",
-        "mass": 100,
-        "shows": [
-          {
-            "stepBegin": 0,
-            "scale": {
-              "x": 0.75,
-              "y": 3,
-              "z": 0.1
-            },
-            "rotation": {
-              "x": 45,
-              "y": 180,
-              "z": 0
-            },
-            "position": {
-              "x": 4.5,
-              "y": 0,
-              "z": 0
-            }
-          }
-        ]
-      }
-    ],
-    "goal": {
-      "category": "traversal",
-      "last_step": 600,
-      "metadata": {
-        "target": {
-          "id": "54616548-85ac-4a20-a0df-0610e5b67f3b",
-          "match_image": true,
-          "image": null,
-          "image_name": "box_4.png"
-        }
-      },
-      "description": "Find the small heavy paper grey box and move near it."
-    }
-}
diff --git a/integration_tests/data/091.door_open_close.actions.txt b/integration_tests/data/091.door_open_close.actions.txt
new file mode 100644
index 000000000..bf1b87505
--- /dev/null
+++ b/integration_tests/data/091.door_open_close.actions.txt
@@ -0,0 +1,7 @@
+MoveAhead
+MoveAhead
+OpenObject,objectId=testDoor
+MoveAhead
+MoveBack
+MoveBack
+CloseObject,objectId=testDoor
diff --git a/integration_tests/data/091.door_open_close.level1.outputs.json b/integration_tests/data/091.door_open_close.level1.outputs.json
new file mode 100644
index 000000000..8430ffdbc
--- /dev/null
+++ b/integration_tests/data/091.door_open_close.level1.outputs.json
@@ -0,0 +1,73 @@
+[{
+    "step_number": 0,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 1,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+},{
+    "step_number": 2,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "OBSTRUCTED",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 3,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+},{
+    "step_number": 4,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 5,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+},{
+    "step_number": 6,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 7,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}]
diff --git a/integration_tests/data/091.door_open_close.level2.outputs.json b/integration_tests/data/091.door_open_close.level2.outputs.json
new file mode 100644
index 000000000..8430ffdbc
--- /dev/null
+++ b/integration_tests/data/091.door_open_close.level2.outputs.json
@@ -0,0 +1,73 @@
+[{
+    "step_number": 0,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 1,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+},{
+    "step_number": 2,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "OBSTRUCTED",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 3,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+},{
+    "step_number": 4,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 5,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+},{
+    "step_number": 6,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}, {
+    "step_number": 7,
+    "head_tilt": 0,
+    "position_x": null,
+    "position_z": null,
+    "return_status": "SUCCESSFUL",
+    "rotation_y": null,
+    "objects_count": 0,
+    "structural_objects_count": 0
+}]
diff --git a/integration_tests/data/091.door_open_close.oracle.outputs.json b/integration_tests/data/091.door_open_close.oracle.outputs.json
new file mode 100644
index 000000000..ab5d6e1d3
--- /dev/null
+++ b/integration_tests/data/091.door_open_close.oracle.outputs.json
@@ -0,0 +1,202 @@
+﻿[
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.885,
+                "direction_z": 0.465,
+                "distance": 0.861,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.0,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 0,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.93,
+                "direction_z": 0.366,
+                "distance": 0.819,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.1,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 1,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.93,
+                "direction_z": 0.366,
+                "distance": 0.819,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.1,
+        "return_status": "OBSTRUCTED",
+        "rotation_y": 0.0,
+        "step_number": 2,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.93,
+                "direction_z": 0.366,
+                "distance": 0.819,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.1,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 3,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.967,
+                "direction_z": 0.254,
+                "distance": 0.788,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.2,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 4,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.93,
+                "direction_z": 0.366,
+                "distance": 0.819,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.1,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 5,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.885,
+                "direction_z": 0.465,
+                "distance": 0.861,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.0,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 6,
+        "structural_objects_count": 6
+    },
+    {
+        "head_tilt": 0,
+        "objects": [
+            {
+                "direction_x": 0.0,
+                "direction_y": -0.885,
+                "direction_z": 0.465,
+                "distance": 0.861,
+                "held": false,
+                "id": "testDoor",
+                "mass": 1,
+                "position_x": 0.0,
+                "position_z": 0.4,
+                "shape": "door_4",
+                "visible": true
+            }
+        ],
+        "objects_count": 1,
+        "position_x": 0.0,
+        "position_z": 0.0,
+        "return_status": "SUCCESSFUL",
+        "rotation_y": 0.0,
+        "step_number": 7,
+        "structural_objects_count": 6
+    }
+]
\ No newline at end of file
diff --git a/integration_tests/data/091.door_open_close.scene.json b/integration_tests/data/091.door_open_close.scene.json
new file mode 100644
index 000000000..0ad76589f
--- /dev/null
+++ b/integration_tests/data/091.door_open_close.scene.json
@@ -0,0 +1,36 @@
+﻿{
+    "name": "open door and go through",
+    "version": 2,
+    "ceilingMaterial": "AI2-THOR/Materials/Walls/Drywall",
+    "floorMaterial": "AI2-THOR/Materials/Fabrics/CarpetWhite 3",
+    "wallMaterial": "AI2-THOR/Materials/Walls/Drywall",
+    "performerStart": {
+        "position": {
+            "x": 0,
+            "z": 0
+        },
+        "rotation": {
+            "x": 0,
+            "y": 0
+        }
+    },
+    "objects": [{
+        "id": "testDoor",
+        "type": "door_4",
+        "mass": 1,
+        "openable": true,
+        "shows": [{
+            "stepBegin": 0,
+            "position": {
+                "x": 0,
+                "y": 0.0,
+                "z": 0.4
+            },
+            "scale": {
+                "x": 1,
+                "y": 1,
+                "z": 1
+            }
+        }]
+    }]
+}
diff --git a/integration_tests/integration_test_utils.py b/integration_tests/integration_test_utils.py
index 972a921d4..ebf7fa487 100644
--- a/integration_tests/integration_test_utils.py
+++ b/integration_tests/integration_test_utils.py
@@ -1,9 +1,11 @@
 import argparse
+import pathlib
 
+config_dir = pathlib.Path(__file__).parent
 METADATA_TIER_LIST = [
-    ('level1', './config_level1.ini'),
-    ('level2', './config_level2.ini'),
-    ('oracle', './config_oracle.ini')
+    ('level1', str(config_dir / 'config_level1.ini')),
+    ('level2', str(config_dir / 'config_level2.ini')),
+    ('oracle', str(config_dir / 'config_oracle.ini'))
 ]
 
 
diff --git a/integration_tests/run_handmade_tests.py b/integration_tests/run_handmade_tests.py
index 79111c04b..f558463d0 100644
--- a/integration_tests/run_handmade_tests.py
+++ b/integration_tests/run_handmade_tests.py
@@ -263,7 +263,7 @@ def run_single_scene(controller, scene_filename, metadata_tier, dev, autofix):
             print(f'SAVED {len(autofix_case_list)} FIXES: {output_filename}')
 
     # Stop the test scene.
-    controller.end_scene("", 1)
+    controller.end_scene()
 
     # Validation successful!
     return successful, ('' if successful else 'see above')
@@ -321,9 +321,12 @@ def start_handmade_tests(
                 successful_test_list.append((test_name, metadata_tier))
             else:
                 failed_test_list.append((test_name, metadata_tier, status))
+
         # Run each additional test at this metadata tier.
         for runner_function in (
-                [] if only_test_name else FUNCTION_LIST):
+                [func for func in FUNCTION_LIST
+                 if only_test_name is None or
+                 only_test_name in str(func)]):
             print(f'RUNNING TESTS: {runner_function.__name__}')
             successful, status = runner_function(controller, metadata_tier)
             test_name = runner_function.__name__
diff --git a/tests/README.md b/tests/README.md
index 38c131e70..5051a1657 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -2,7 +2,7 @@
 
 Unit tests for the Machine Common Sense (MCS) project are written using the unittest framework.
 
-https://docs.python.org/3.6/library/unittest.html
+https://docs.python.org/3.7/library/unittest.html
 
 ## Running All Tests
 
diff --git a/tests/mock_controller.py b/tests/mock_controller.py
index d6ea33f11..c860e26c3 100644
--- a/tests/mock_controller.py
+++ b/tests/mock_controller.py
@@ -3,12 +3,10 @@
 import ai2thor.server
 import numpy
 
-from machine_common_sense.action import Action
 from machine_common_sense.config_manager import ConfigManager
 from machine_common_sense.controller import Controller
 from machine_common_sense.controller_output_handler import \
     ControllerOutputHandler
-from machine_common_sense.pose import Pose
 
 MOCK_VARIABLES = {
     'event_count': 5,
@@ -27,7 +25,6 @@
                 'y': 0.0
             }
         },
-        'pose': Pose.STANDING.name,
         'lastActionStatus': 'SUCCESSFUL',
         'objects': [],
         'screenHeight': 400,
@@ -47,7 +44,6 @@ def __init__(self):
 
     def step(self, data):
         self.__last_step_data = data
-        self.update_metadata(data)
         metadata = self.__last_metadata
 
         event = ai2thor.server.Event(metadata)
@@ -63,16 +59,6 @@ def step(self, data):
     def get_last_step_data(self):
         return self.__last_step_data
 
-    def update_metadata(self, data: dict) -> dict:
-
-        if data['action'] == Action.CRAWL.value:
-            self.__last_metadata['pose'] = Pose.CRAWLING.name
-        elif (data['action'] == Action.STAND.value and
-              self.__last_metadata['pose'] != Pose.LYING.name):
-            self.__last_metadata['pose'] = Pose.STANDING.name
-        elif data['action'] == Action.LIE_DOWN.value:
-            self.__last_metadata['pose'] = Pose.LYING.name
-
     def subscribe(self, subscriber):
         if subscriber not in self._subscribers:
             self._subscribers.append(subscriber)
@@ -94,7 +80,8 @@ def __init__(self):
 
         self._subscribers = []
 
-        self._end_scene_not_registered = False  # atexit not needed for tests
+        self._failure_handler_registered = True  # atexit not needed for tests
+        self._end_scene_called = False
         self._controller = MockController()
         self._config = ConfigManager(config_file_or_dict={})
         self._config._config[
diff --git a/tests/test.msgpack b/tests/test.msgpack
deleted file mode 100644
index f1ee1653f..000000000
Binary files a/tests/test.msgpack and /dev/null differ
diff --git a/tests/test_action.py b/tests/test_action.py
index 862bc1cf7..8d82a41f1 100644
--- a/tests/test_action.py
+++ b/tests/test_action.py
@@ -34,16 +34,6 @@ def test_close_object(self):
         self.assertEqual(mcs.Action("CloseObject"), mcs.Action.CLOSE_OBJECT)
         self.assertEqual(mcs.Action("1"), mcs.Action.CLOSE_OBJECT)
 
-    def test_crawl(self):
-        self.assertEqual(mcs.Action.CRAWL.value, "Crawl")
-        self.assertEqual(mcs.Action.CRAWL.key, "c")
-        self.assertEqual(
-            mcs.Action.CRAWL.desc,
-            "Change pose to 'CRAWLING' (no params)"
-        )
-        self.assertEqual(mcs.Action("Crawl"), mcs.Action.CRAWL)
-        self.assertEqual(mcs.Action("c"), mcs.Action.CRAWL)
-
     def test_drop_object(self):
         self.assertEqual(mcs.Action.DROP_OBJECT.value, "DropObject")
         self.assertEqual(mcs.Action.DROP_OBJECT.key, "2")
@@ -54,16 +44,6 @@ def test_drop_object(self):
         self.assertEqual(mcs.Action("DropObject"), mcs.Action.DROP_OBJECT)
         self.assertEqual(mcs.Action("2"), mcs.Action.DROP_OBJECT)
 
-    def test_lie_down(self):
-        self.assertEqual(mcs.Action.LIE_DOWN.value, "LieDown")
-        self.assertEqual(mcs.Action.LIE_DOWN.key, "x")
-        self.assertEqual(
-            mcs.Action.LIE_DOWN.desc,
-            "Change pose to 'LYING' (rotation=float)"
-        )
-        self.assertEqual(mcs.Action("LieDown"), mcs.Action.LIE_DOWN)
-        self.assertEqual(mcs.Action("x"), mcs.Action.LIE_DOWN)
-
     def test_move_ahead(self):
         self.assertEqual(mcs.Action.MOVE_AHEAD.value, "MoveAhead")
         self.assertEqual(mcs.Action.MOVE_AHEAD.key, "w")
@@ -206,28 +186,6 @@ def test_look_down(self):
         self.assertEqual(mcs.Action("LookDown"), mcs.Action.LOOK_DOWN)
         self.assertEqual(mcs.Action("k"), mcs.Action.LOOK_DOWN)
 
-    def test_stand(self):
-        self.assertEqual(mcs.Action.STAND.value, "Stand")
-        self.assertEqual(mcs.Action.STAND.key, "u")
-        self.assertEqual(
-            mcs.Action.STAND.desc,
-            "Change pose to 'STANDING' (no params)"
-        )
-        self.assertEqual(mcs.Action("Stand"), mcs.Action.STAND)
-        self.assertEqual(mcs.Action("u"), mcs.Action.STAND)
-
-    def test_throw_object(self):
-        self.assertEqual(mcs.Action.THROW_OBJECT.value, "ThrowObject")
-        self.assertEqual(mcs.Action.THROW_OBJECT.key, "q")
-        self.assertEqual(
-            mcs.Action.THROW_OBJECT.desc,
-            "Throw an object you are holding. (objectId=string, " +
-            "objectImageCoordsX=float, objectImageCoordsY=float, " +
-            "force=float (default:0.5))"
-        )
-        self.assertEqual(mcs.Action("ThrowObject"), mcs.Action.THROW_OBJECT)
-        self.assertEqual(mcs.Action("q"), mcs.Action.THROW_OBJECT)
-
     def test_pass(self):
         self.assertEqual(mcs.Action.PASS.value, "Pass")
         self.assertEqual(mcs.Action.PASS.key, " ")
diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py
index 8de0b9a24..70c792158 100644
--- a/tests/test_config_manager.py
+++ b/tests/test_config_manager.py
@@ -8,6 +8,7 @@
                                                  MoveConfig, OpenCloseConfig,
                                                  PhysicsConfig,
                                                  SceneConfiguration,
+                                                 SceneConfigurationSchema,
                                                  SceneObjectSchema, ShowConfig,
                                                  SingleStepConfig, SizeConfig,
                                                  StepBeginEndConfig,
@@ -79,13 +80,11 @@ def test_nonexistent_file_from_env_variable(self):
                                     'config_level2_debug.ini'))
     def test_init_no_override_with_env_var_and_dict(self):
         config_options = {
-            'metadata': 'oracle',
-            'seed': 10
+            'metadata': 'oracle'
         }
         config_mngr = ConfigManager(config_options)
         self.assertEqual(config_mngr.get_metadata_tier(),
                          MetadataTier.LEVEL_2)
-        self.assertEqual(config_mngr.get_seed(), None)
 
     @mock_env(MCS_CONFIG_FILE_PATH=('machine_common_sense/scripts/'
                                     'config_level2_debug.ini'))
@@ -94,8 +93,7 @@ def test_init_no_override_with_env_var_and_dict(self):
     def test_init_env_var_and_dict_function_calls(
             self, _read_in_config_dict, _read_in_config_file):
         config_options = {
-            'metadata': 'oracle',
-            'seed': 10
+            'metadata': 'oracle'
         }
         _ = ConfigManager(config_options)
         _read_in_config_dict.assert_not_called()
@@ -107,7 +105,6 @@ def test_init_with_filepath(self):
         config_mngr = ConfigManager(config_file_or_dict=config_file)
         self.assertEqual(config_mngr.get_metadata_tier(),
                          MetadataTier.LEVEL_2)
-        self.assertEqual(config_mngr.get_seed(), None)
 
     def test_init_with_filepath_missing(self):
         '''Provided config file path must exist or an exception occurs'''
@@ -122,8 +119,7 @@ def test_init_with_dict(self):
         config_options = {
             'metadata': 'oracle',
             'video_enabled': 'false',
-            'save_debug_images': True,
-            'seed': 10
+            'save_debug_images': True
         }
 
         config_mngr = ConfigManager(config_options)
@@ -133,7 +129,6 @@ def test_init_with_dict(self):
                          MetadataTier.ORACLE)
         self.assertFalse(config_mngr.is_video_enabled())
         self.assertTrue(config_mngr.is_save_debug_images())
-        self.assertEqual(config_mngr.get_seed(), 10)
 
     def test_validate_screen_size(self):
         self.config_mngr._config[
@@ -187,17 +182,6 @@ def test_get_metadata_tier(self):
             self.config_mngr.get_metadata_tier().value,
             'oracle')
 
-    def test_get_seed(self):
-        self.assertEqual(self.config_mngr.get_seed(), None)
-
-        self.config_mngr._config[
-            self.config_mngr.CONFIG_DEFAULT_SECTION
-        ][
-            self.config_mngr.CONFIG_SEED
-        ] = '1'
-
-        self.assertEqual(self.config_mngr.get_seed(), 1)
-
     def test_get_size(self):
         self.assertEqual(self.config_mngr.get_size(), 600)
 
@@ -439,120 +423,120 @@ def test_objects(self):
 
         assert object_1.id == 'id_1'
         assert object_1.type == 'type_1'
-        assert object_1.centerOfMass is None
-        assert object_1.changeMaterials is None
+        assert object_1.center_of_mass is None
+        assert object_1.change_materials is None
         assert object_1.debug is None
         assert object_1.forces is None
         assert object_1.ghosts is None
         assert object_1.hides is None
         assert object_1.kinematic is None
-        assert object_1.locationParent is None
+        assert object_1.location_parent is None
         assert object_1.mass is None
         assert object_1.materials is None
         assert object_1.moveable is None
         assert object_1.moves is None
-        assert object_1.nullParent is None
+        assert object_1.null_parent is None
         assert object_1.openable is None
         assert object_1.opened is None
-        assert object_1.openClose is None
+        assert object_1.open_close is None
         assert object_1.physics is None
-        assert object_1.physicsProperties is None
+        assert object_1.physics_properties is None
         assert object_1.pickupable is None
         assert object_1.receptacle is None
-        assert object_1.resetCenterOfMass is None
+        assert object_1.reset_center_of_mass is None
         assert object_1.resizes is None
         assert object_1.rotates is None
-        assert object_1.salientMaterials is None
+        assert object_1.salient_materials is None
         assert object_1.shows is None
         assert object_1.shrouds is None
         assert object_1.states is None
         assert object_1.structure is None
         assert object_1.teleports is None
-        assert object_1.togglePhysics is None
+        assert object_1.toggle_physics is None
         assert object_1.torques is None
 
         assert object_2.id == 'id_2'
         assert object_2.type == 'type_2'
-        assert object_2.centerOfMass == Vector3d(x=0.01, y=0.02, z=0.03)
-        assert object_2.changeMaterials == [ChangeMaterialConfig(
-            stepBegin=10,
+        assert object_2.center_of_mass == Vector3d(x=0.01, y=0.02, z=0.03)
+        assert object_2.change_materials == [ChangeMaterialConfig(
+            step_begin=10,
             materials=['material_3', 'material_4']
         )]
         assert object_2.debug == {'key': 'value'}
         assert object_2.forces == [ForceConfig(
             relative=True,
-            stepBegin=11,
-            stepEnd=12,
+            step_begin=11,
+            step_end=12,
             vector=Vector3d(x=0.04, y=0.05, z=0.06)
         )]
         assert object_2.ghosts == [StepBeginEndConfig(
-            stepBegin=13,
-            stepEnd=14
+            step_begin=13,
+            step_end=14
         )]
-        assert object_2.hides == [SingleStepConfig(stepBegin=15)]
+        assert object_2.hides == [SingleStepConfig(step_begin=15)]
         assert object_2.kinematic is True
-        assert object_2.locationParent == 'parent_id'
+        assert object_2.location_parent == 'parent_id'
         assert object_2.mass == 12.34
         assert object_2.materials == ['material_1', 'material_2']
         assert object_2.moveable is True
         assert object_2.moves == [MoveConfig(
-            stepBegin=16,
-            stepEnd=17,
+            step_begin=16,
+            step_end=17,
             vector=Vector3d(x=0.07, y=0.08, z=0.09)
         )]
-        assert object_2.nullParent == TransformConfig(
+        assert object_2.null_parent == TransformConfig(
             position=Vector3d(x=0.11, y=0.12, z=0.13),
             rotation=Vector3d(x=0.14, y=0.15, z=0.16),
             scale=Vector3d(x=0.17, y=0.18, z=0.19)
         )
         assert object_2.openable is True
         assert object_2.opened is True
-        assert object_2.openClose == [OpenCloseConfig(open=True, step=18)]
+        assert object_2.open_close == [OpenCloseConfig(open=True, step=18)]
         assert object_2.physics is True
-        assert object_2.physicsProperties == PhysicsConfig(
+        assert object_2.physics_properties == PhysicsConfig(
             enable=True,
-            angularDrag=1,
+            angular_drag=1,
             bounciness=2,
             drag=3,
-            dynamicFriction=4,
-            staticFriction=5
+            dynamic_friction=4,
+            static_friction=5
         )
         assert object_2.pickupable is True
         assert object_2.receptacle is True
-        assert object_2.resetCenterOfMass is True
+        assert object_2.reset_center_of_mass is True
         assert object_2.resizes == [SizeConfig(
-            stepBegin=19,
-            stepEnd=20,
+            step_begin=19,
+            step_end=20,
             size=Vector3d(x=0.21, y=0.22, z=0.23)
         )]
         assert object_2.rotates == [MoveConfig(
-            stepBegin=21,
-            stepEnd=22,
+            step_begin=21,
+            step_end=22,
             vector=Vector3d(x=0.24, y=0.25, z=0.26)
         )]
-        assert object_2.salientMaterials == ['salient_1', 'salient_2']
+        assert object_2.salient_materials == ['salient_1', 'salient_2']
         assert object_2.shows == [ShowConfig(
-            stepBegin=23,
+            step_begin=23,
             position=Vector3d(x=0.31, y=0.32, z=0.33),
             rotation=Vector3d(x=0.34, y=0.35, z=0.36),
             scale=Vector3d(x=0.37, y=0.38, z=0.39)
         )]
         assert object_2.shrouds == [StepBeginEndConfig(
-            stepBegin=24,
-            stepEnd=25
+            step_begin=24,
+            step_end=25
         )]
         assert object_2.states == [
             ['state_1'], [], ['state_2'], ['state_1', 'state_3']
         ]
         assert object_2.structure is True
         assert object_2.teleports == [TeleportConfig(
-            stepBegin=26,
+            step_begin=26,
             position=Vector3d(x=0.41, y=0.42, z=0.43)
         )]
-        assert object_2.togglePhysics == [SingleStepConfig(stepBegin=27)]
+        assert object_2.toggle_physics == [SingleStepConfig(step_begin=27)]
         assert object_2.torques == [MoveConfig(
-            stepBegin=28,
-            stepEnd=29,
+            step_begin=28,
+            step_end=29,
             vector=Vector3d(x=0.44, y=0.45, z=0.46)
         )]
 
@@ -696,5 +680,50 @@ def test_update_goal_target_image_oracle_img_as_str(self):
         })
 
 
+class TestSchemeConfigurationSchema(unittest.TestCase):
+
+    def setUp(self):
+        self.scheme_config = SceneConfigurationSchema()
+
+    def test_remove_none(self):
+        actual = self.scheme_config.remove_none({})
+        self.assertEqual({}, actual)
+
+        actual = self.scheme_config.remove_none({"test": None})
+        self.assertEqual({}, actual)
+
+        actual = self.scheme_config.remove_none({"test": 1})
+        self.assertEqual({"test": 1}, actual)
+
+        actual = self.scheme_config.remove_none({"test": 1, "none": None})
+        self.assertEqual({"test": 1}, actual)
+
+        actual = self.scheme_config.remove_none({"test1": {"test2": 1}})
+        self.assertEqual({"test1": {"test2": 1}}, actual)
+
+        actual = self.scheme_config.remove_none(
+            {"test1": {"test2": 1, "none": None}})
+        self.assertEqual({"test1": {"test2": 1}}, actual)
+
+        actual = self.scheme_config.remove_none(
+            {"test1": {"test2": 1}, "none": None})
+        self.assertEqual({"test1": {"test2": 1}}, actual)
+
+        actual = self.scheme_config.remove_none(
+            {"test1": {"test2": 1, "none": None}, "none": None})
+        self.assertEqual({"test1": {"test2": 1}}, actual)
+
+        actual = self.scheme_config.remove_none({"test1": [{"test2": None}]})
+        self.assertEqual({"test1": [{}]}, actual)
+
+        actual = self.scheme_config.remove_none(
+            {"test1": [{"test2": None, "test3": "test"}]})
+        self.assertEqual({"test1": [{"test3": "test"}]}, actual)
+
+        actual = self.scheme_config.remove_none(
+            {"test1": [{"test2": None}], "test3": False})
+        self.assertEqual({"test1": [{}], "test3": False}, actual)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_controller.py b/tests/test_controller.py
index f705b1661..9acf9b655 100644
--- a/tests/test_controller.py
+++ b/tests/test_controller.py
@@ -5,13 +5,14 @@
 from types import SimpleNamespace
 from unittest.mock import ANY, MagicMock
 
-import numpy
+import numpy as np
 
 import machine_common_sense as mcs
 from machine_common_sense.config_manager import (ConfigManager, MetadataTier,
                                                  SceneConfiguration, Vector3d)
 from machine_common_sense.controller_events import EndScenePayload, EventType
 from machine_common_sense.goal_metadata import GoalMetadata
+from machine_common_sense.parameter import Parameter
 
 from .mock_controller import MOCK_VARIABLES, MockControllerAI2THOR
 
@@ -43,10 +44,41 @@ def create_mock_scene_event(self, mock_scene_event_data):
         # dict.
         return SimpleNamespace(**mock_scene_event_data)
 
+    def create_step_data(self, **kwargs):
+        data = dict(
+            consistentColors=False,
+            continuous=True,
+            gridSize=Parameter.GRID_SIZE,
+            horizon=0.0,
+            logs=True,
+            moveMagnitude=mcs.controller.DEFAULT_MOVE,
+            objectId=None,
+            objectImageCoords={
+                'x': 0,
+                'y': 399
+            },
+            receptacleObjectId=None,
+            receptacleObjectImageCoords={
+                'x': 0,
+                'y': 399
+            },
+            renderDepthImage=False,
+            renderObjectImage=False,
+            rotation={'y': 0.0},
+            snapToGrid=False,
+            teleportPosition=None,
+            teleportRotation=None
+        )
+
+        for key, value in kwargs.items():
+            data[key] = value
+
+        return data
+
     def create_wrap_output_scene_event(self):
-        image_data = numpy.array([[0]], dtype=numpy.uint8)
-        depth_data = numpy.array([[[128, 0, 0]]], dtype=numpy.uint8)
-        object_mask_data = numpy.array([[192]], dtype=numpy.uint8)
+        image_data = np.array([[0]], dtype=np.uint8)
+        depth_data = np.array([[[128, 0, 0]]], dtype=np.uint8)
+        object_mask_data = np.array([[192]], dtype=np.uint8)
 
         return {
             "events": [self.create_mock_scene_event({
@@ -78,7 +110,6 @@ def create_wrap_output_scene_event(self):
                 "clippingPlaneFar": 15,
                 "clippingPlaneNear": 0,
                 "fov": 42.5,
-                "pose": mcs.Pose.STANDING.name,
                 "lastActionStatus": "SUCCESSFUL",
                 "lastActionSuccess": True,
                 "objects": [{
@@ -242,45 +273,14 @@ def create_wrap_output_scene_event(self):
             }
         }, image_data, depth_data, object_mask_data
 
-    def create_step_data(self, **kwargs):
-        data = dict(
-            consistentColors=False,
-            continuous=True,
-            gridSize=mcs.Controller.GRID_SIZE,
-            horizon=0,
-            logs=True,
-            moveMagnitude=mcs.controller.DEFAULT_MOVE,
-            objectId=None,
-            objectImageCoords={
-                'x': 0.0,
-                'y': 0.0
-            },
-            receptacleObjectId=None,
-            receptacleObjectImageCoords={
-                'x': 0.0,
-                'y': 0.0
-            },
-            renderDepthImage=False,
-            renderObjectImage=False,
-            rotation={'y': 0.0},
-            snapToGrid=False,
-            teleportPosition=None,
-            teleportRotation=None
-        )
-
-        for key, value in kwargs.items():
-            data[key] = value
-
-        return data
-
     def test_end_scene(self):
         test_payload = self.controller._create_event_payload_kwargs()
 
-        test_payload["rating"] = "plausible"
+        test_payload["rating"] = 1.0
         test_payload["score"] = 0.5
         test_payload["report"] = {
             1: {
-                "rating": "plausible",
+                "rating": 1.0,
                 "score": .75,
                 "violations_xy_list": [
                     {
@@ -290,9 +290,9 @@ def test_end_scene(self):
                 ]}
         }
 
-        self.controller.end_scene("plausible", 0.5, {
+        self.controller.end_scene(1.0, 0.5, {
             1: {
-                "rating": "plausible",
+                "rating": 1.0,
                 "score": .75,
                 "violations_xy_list": [
                     {
@@ -307,6 +307,119 @@ def test_end_scene(self):
             EndScenePayload(**test_payload)
         )
 
+    def test_end_scene_with_numpy_float(self):
+        test_payload = self.controller._create_event_payload_kwargs()
+
+        test_payload["rating"] = 1.0
+        test_payload["score"] = np.float64(0.5)
+        test_payload["report"] = {
+            1: {
+                "rating": 1.0,
+                "score": np.float64(.75),
+                "violations_xy_list": [
+                    {
+                        "x": 1,
+                        "y": 1
+                    }
+                ]}
+        }
+
+        self.controller.end_scene(1.0, np.float64(0.5), {
+            1: {
+                "rating": 1.0,
+                "score": np.float64(.75),
+                "violations_xy_list": [
+                    {
+                        "x": np.int32(1),
+                        "y": np.int32(1)
+                    }
+                ]}
+        })
+
+        self.controller._publish_event.assert_called_with(
+            EventType.ON_END_SCENE,
+            EndScenePayload(**test_payload)
+        )
+
+    def test_end_scene_with_rating_string(self):
+        test_payload = self.controller._create_event_payload_kwargs()
+
+        test_payload["rating"] = 1.0
+        test_payload["score"] = np.float64(0.5)
+        test_payload["report"] = {
+            1: {
+                "rating": 1.0,
+                "score": np.float64(.75),
+                "violations_xy_list": [
+                    {
+                        "x": 1,
+                        "y": 1
+                    }
+                ]}
+        }
+
+        self.assertRaises(
+            TypeError,
+            lambda: self.controller.end_scene("1.0", np.float64(0.5), {
+                1: {
+                    "rating": 1.0,
+                    "score": np.float64(.75),
+                    "violations_xy_list": [
+                        {
+                            "x": np.int32(1),
+                            "y": np.int32(1)
+                        }
+                    ]}
+            })
+        )
+
+    def test_end_scene_twice(self):
+        test_payload = self.controller._create_event_payload_kwargs()
+
+        test_payload["rating"] = 1.0
+        test_payload["score"] = 0.5
+        test_payload["report"] = {
+            1: {
+                "rating": 1.0,
+                "score": .75,
+                "violations_xy_list": [
+                    {
+                        "x": 1,
+                        "y": 1
+                    }
+                ]}
+        }
+
+        self.controller.end_scene(1.0, 0.5, {
+            1: {
+                "rating": 1.0,
+                "score": .75,
+                "violations_xy_list": [
+                    {
+                        "x": 1,
+                        "y": 1
+                    }
+                ]}
+        })
+
+        # calling end_scene a second time raises an exception
+        self.assertRaises(
+            RuntimeError,
+            lambda: self.controller.end_scene(
+                1.0,
+                0.5,
+                {1: {
+                    "rating": 1.0,
+                    "score": .75,
+                    "violations_xy_list": [
+                        {
+                            "x": 1,
+                            "y": 1
+                        }
+                    ]}
+                 })
+        )
+
     def test_start_scene(self):
         self.controller.set_metadata_tier(
             MetadataTier.ORACLE.value)
@@ -459,191 +572,6 @@ def test_step_last_step(self):
         output = self.controller.step('MoveAhead')
         self.assertIsNone(output)
 
-    def test_step_validate_action(self):
-        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
-        output = self.controller.step('Foobar')
-        self.assertIsNone(output)
-
-        self.controller.set_goal(mcs.GoalMetadata(action_list=[
-            [('Pass', {})]
-        ]))
-        output = self.controller.step('MoveAhead')
-        self.assertIsNone(output)
-
-        self.controller.set_goal(mcs.GoalMetadata(action_list=[
-            [('MoveAhead', {})],
-            [('MoveBack', {})]]
-        ))
-        output = self.controller.step('MoveAhead')
-        self.assertIsNotNone(output)
-        output = self.controller.step('MoveAhead')
-        self.assertIsNone(output)
-
-    def test_step_validate_parameters_move(self):
-        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
-        self.controller.step('MoveAhead')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MoveAhead',
-                moveMagnitude=mcs.controller.DEFAULT_MOVE))
-
-        self.controller.step('MoveAhead')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MoveAhead',
-                moveMagnitude=mcs.controller.DEFAULT_MOVE))
-
-        self.controller.step('MoveAhead')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MoveAhead',
-                moveMagnitude=mcs.controller.DEFAULT_MOVE))
-
-        self.controller.step('MoveAhead')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MoveAhead',
-                moveMagnitude=mcs.controller.DEFAULT_MOVE))
-
-    def test_step_validate_parameters_rotate(self):
-        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
-        self.controller.step('RotateLeft')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(action='RotateLeft'))
-
-        self.controller.step('RotateLeft')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='RotateLeft'))
-
-    def test_step_validate_parameters_force_object(self):
-        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
-        self.controller.step('PushObject', force=1, objectId='test_id_1')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='PushObject',
-                moveMagnitude=mcs.Controller.MAX_FORCE,
-                objectId='test_id_1'))
-
-        self.controller.step('PushObject', force=0.1, objectId='test_id_1')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='PushObject',
-                moveMagnitude=0.1 *
-                mcs.Controller.MAX_FORCE,
-                objectId='test_id_1'))
-
-        self.controller.step('PushObject', force=1.5, objectId='test_id_1')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='PushObject',
-                moveMagnitude=mcs.Controller.DEFAULT_AMOUNT *
-                mcs.Controller.MAX_FORCE,
-                objectId='test_id_1'))
-
-        self.controller.step('PushObject', force=-1, objectId='test_id_1')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='PushObject',
-                moveMagnitude=mcs.Controller.DEFAULT_AMOUNT *
-                mcs.Controller.MAX_FORCE,
-                objectId='test_id_1'))
-
-        self.controller.step(
-            'PushObject',
-            force=1,
-            objectImageCoordsX=1,
-            objectImageCoordsY=2)
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='PushObject',
-                moveMagnitude=mcs.Controller.MAX_FORCE,
-                objectImageCoords={'x': 1, 'y': 398}))
-
-    def test_step_validate_parameters_open_close(self):
-        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
-        self.controller.step(
-            'OpenObject',
-            amount=1,
-            objectId='test_id_1',
-            receptacleObjectId='test_id_2')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MCSOpenObject',
-                moveMagnitude=1,
-                objectId='test_id_1',
-                receptacleObjectId='test_id_2'))
-
-        self.controller.step(
-            'OpenObject',
-            amount=0.1,
-            objectId='test_id_1',
-            receptacleObjectId='test_id_2')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MCSOpenObject',
-                moveMagnitude=0.1,
-                objectId='test_id_1',
-                receptacleObjectId='test_id_2'))
-
-        self.controller.step(
-            'OpenObject',
-            amount=1.5,
-            objectId='test_id_1',
-            receptacleObjectId='test_id_2')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MCSOpenObject',
-                moveMagnitude=mcs.Controller.DEFAULT_AMOUNT,
-                objectId='test_id_1',
-                receptacleObjectId='test_id_2'))
-
-        self.controller.step(
-            'OpenObject',
-            amount=-1,
-            objectId='test_id_1',
-            receptacleObjectId='test_id_2')
-        self.assertEqual(
-            self.controller.get_last_step_data(),
-            self.create_step_data(
-                action='MCSOpenObject',
-                moveMagnitude=mcs.Controller.DEFAULT_AMOUNT,
-                objectId='test_id_1',
-                receptacleObjectId='test_id_2'))
-
-        self.controller.step(
-            'OpenObject',
-            amount=1,
-            objectImageCoordsX=1,
-            objectImageCoordsY=2,
-            receptacleObjectImageCoordsX=4,
-            receptacleObjectImageCoordsY=5)
-        self.assertEqual(
-            self.controller.get_last_step_data(), self.create_step_data(
-                action='MCSOpenObject', moveMagnitude=1,
-                objectImageCoords={
-                    'x': 1, 'y': MOCK_VARIABLES['metadata']['screenHeight'] - 2
-                },
-                receptacleObjectImageCoords={
-                    'x': 4, 'y': MOCK_VARIABLES['metadata']['screenHeight'] - 5
-                }
-            )
-        )
-
     def test_retrieve_action_list_at_step(self):
         test_action_list = [
             ('Pass', {}),
@@ -734,109 +662,6 @@ def test_retrieve_action_list_at_step(self):
             []
         )
 
-    def test_retrieve_pose(self):
-        output = self.controller.start_scene({'name': TEST_FILE_NAME})
-
-        # Testing retrieving proper pose depending on action made
-        # Check basics
-        output = self.controller.step(action='Stand')
-        self.assertEqual(output.pose, mcs.Pose.STANDING.name)
-
-        output = self.controller.step(action='LieDown')
-        self.assertEqual(output.pose, mcs.Pose.LYING.name)
-
-        output = self.controller.step(action='Crawl')
-        self.assertEqual(output.pose, mcs.Pose.CRAWLING.name)
-
-        # Check movement within crawling pose
-        output = self.controller.step(action='MoveAhead')
-        self.assertEqual(output.pose, mcs.Pose.CRAWLING.name)
-        self.controller.step(action='MoveBack')
-
-        self.controller.step(action='Stand')
-        output = self.controller.step(action='MoveBack')
-        self.assertEqual(output.pose, mcs.Pose.STANDING.name)
-
-        # Check stand->lying->crawl->stand
-        output = self.controller.step(action='LieDown')
-        self.assertEqual(output.pose, mcs.Pose.LYING.name)
-        output = self.controller.step(action='Crawl')
-        self.assertEqual(output.pose, mcs.Pose.CRAWLING.name)
-        output = self.controller.step(action='Stand')
-        self.assertEqual(output.pose, mcs.Pose.STANDING.name)
-
-        # Check stand->crawl->Lying->crawl->lying->crawl->stand
-        self.controller.step(action='Crawl')
-        output = self.controller.step(action='LieDown')
-        self.assertEqual(output.pose, mcs.Pose.LYING.name)
-        self.controller.step(action='Crawl')
-        output = self.controller.step(action='LieDown')
-        self.assertEqual(output.pose, mcs.Pose.LYING.name)
-        self.controller.step(action='Crawl')
-        output = self.controller.step(action='Stand')
-        self.assertEqual(output.pose, mcs.Pose.STANDING.name)
-
-        # Check stand->Lying (!= ->) stand
-        self.controller.step(action='LieDown')
-        output = self.controller.step(action='Stand')
-        self.assertNotEqual(output.pose, mcs.Pose.STANDING.name)
-
-        self.controller.step(action='Crawl')
-        output = self.controller.step(action='Stand')
-        self.assertEqual(output.pose, mcs.Pose.STANDING.name)
-
-    def test_wrap_step(self):
-        actual = self.controller.wrap_step(
-            action="TestAction",
-            numberProperty=1234,
-            stringProperty="test_property")
-        expected = {
-            "action": "TestAction",
-            "continuous": True,
-            "gridSize": 0.1,
-            "logs": True,
-            "numberProperty": 1234,
-            "renderDepthImage": False,
-            "renderObjectImage": False,
-            "snapToGrid": False,
-            "stringProperty": "test_property",
-            "consistentColors": False
-        }
-        self.assertEqual(actual, expected)
-
-    def test_wrap_step_metadata_oracle(self):
-        self.controller.set_metadata_tier('oracle')
-        actual = self.controller.wrap_step(
-            action="TestAction",
-            numberProperty=1234,
-            stringProperty="test_property")
-        # Changed depth and object because oracle should result in both being
-        # true.
-        expected = {
-            "action": "TestAction",
-            "continuous": True,
-            "gridSize": 0.1,
-            "logs": True,
-            "numberProperty": 1234,
-            "renderDepthImage": True,
-            "renderObjectImage": True,
-            "snapToGrid": False,
-            "stringProperty": "test_property",
-            "consistentColors": True
-        }
-        self.assertEqual(actual, expected)
-
-    def test_generate_noise(self):
-        # Current noise range is -0.5 to 0.5
-        minValue = -0.5
-        maxValue = 0.5
-
-        currentNoise = self.controller.generate_noise()
-        self.assertTrue(minValue <= currentNoise <= maxValue)
-
-        currentNoise = self.controller.generate_noise()
-        self.assertTrue(minValue <= currentNoise <= maxValue)
-
     def test_get_metadata_level(self):
         self.assertEqual('default', self.controller.get_metadata_level())
 
@@ -856,65 +681,26 @@ def test_scene_configuration_deserialization(self):
         actual = self.controller._convert_scene_config(config)
         # should be same instance
         self.assertEqual(None, actual.name)
-        self.assertEqual(None, actual.floorMaterial)
+        self.assertEqual(None, actual.floor_material)
         self.assertEqual([], actual.objects)
         self.assertEqual(
             ConfigManager.DEFAULT_ROOM_DIMENSIONS,
-            actual.roomDimensions)
+            actual.room_dimensions)
         self.assertEqual(None, actual.goal)
 
         config = {"name": "name1", "roomDimensions": {"x": 1, "y": 2, "z": 3}}
         actual = self.controller._convert_scene_config(config)
         self.assertIsInstance(actual, SceneConfiguration)
         self.assertEqual("name1", actual.name)
-        self.assertEqual(None, actual.floorMaterial)
+        self.assertEqual(None, actual.floor_material)
         self.assertEqual([], actual.objects)
-        self.assertIsNotNone(actual.roomDimensions)
-        self.assertIsInstance(actual.roomDimensions, Vector3d)
-        self.assertEqual(1, actual.roomDimensions.x)
-        self.assertEqual(2, actual.roomDimensions.y)
-        self.assertEqual(3, actual.roomDimensions.z)
+        self.assertIsNotNone(actual.room_dimensions)
+        self.assertIsInstance(actual.room_dimensions, Vector3d)
+        self.assertEqual(1, actual.room_dimensions.x)
+        self.assertEqual(2, actual.room_dimensions.y)
+        self.assertEqual(3, actual.room_dimensions.z)
         self.assertEqual(None, actual.goal)
 
-    def test_remove_none(self):
-        actual = self.controller._remove_none({})
-        self.assertEqual({}, actual)
-
-        actual = self.controller._remove_none({"test": None})
-        self.assertEqual({}, actual)
-
-        actual = self.controller._remove_none({"test": 1})
-        self.assertEqual({"test": 1}, actual)
-
-        actual = self.controller._remove_none({"test": 1, "none": None})
-        self.assertEqual({"test": 1}, actual)
-
-        actual = self.controller._remove_none({"test1": {"test2": 1}})
-        self.assertEqual({"test1": {"test2": 1}}, actual)
-
-        actual = self.controller._remove_none(
-            {"test1": {"test2": 1, "none": None}})
-        self.assertEqual({"test1": {"test2": 1}}, actual)
-
-        actual = self.controller._remove_none(
-            {"test1": {"test2": 1}, "none": None})
-        self.assertEqual({"test1": {"test2": 1}}, actual)
-
-        actual = self.controller._remove_none(
-            {"test1": {"test2": 1, "none": None}, "none": None})
-        self.assertEqual({"test1": {"test2": 1}}, actual)
-
-        actual = self.controller._remove_none({"test1": [{"test2": None}]})
-        self.assertEqual({"test1": [{}]}, actual)
-
-        actual = self.controller._remove_none(
-            {"test1": [{"test2": None, "test3": "test"}]})
-        self.assertEqual({"test1": [{"test3": "test"}]}, actual)
-
-        actual = self.controller._remove_none(
-            {"test1": [{"test2": None}], "test3": False})
-        self.assertEqual({"test1": [{}], "test3": False}, actual)
-
     def test_set_config(self):
         first_config = self.controller._config
         new_config = ConfigManager(config_file_or_dict={})
@@ -929,6 +715,198 @@ def flip():
         self.assertNotEqual(previous_noise,
                             self.controller._config.is_noise_enabled())
 
+    def test_retrieve_object_states(self):
+        config = ConfigManager(config_file_or_dict={})
+        self.controller._set_config(config)
+        scene_config = self.controller._convert_scene_config(
+            SceneConfiguration())
+        self.controller._scene_config = scene_config
+        states = self.controller.retrieve_object_states('')
+        self.assertIsInstance(states, list)
+        self.assertEqual(len(states), 0)
+
+    def test_step_open_action_magnitude(self):
+        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
+        self.controller.step(
+            'OpenObject',
+            amount=1,
+            objectId='test_id_1',
+            receptacleObjectId='test_id_2')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MCSOpenObject',
+                moveMagnitude=1.0,
+                objectId='test_id_1',
+                receptacleObjectId='test_id_2'))
+
+        self.controller.step(
+            'OpenObject',
+            amount=0.1,
+            objectId='test_id_1',
+            receptacleObjectId='test_id_2')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MCSOpenObject',
+                moveMagnitude=0.1,
+                objectId='test_id_1',
+                receptacleObjectId='test_id_2'))
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.controller.step(
+                'OpenObject',
+                amount=1.5,
+                objectId='test_id_1',
+                receptacleObjectID='test_id_2')
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.controller.step(
+                'OpenObject',
+                amount=-1,
+                objectId='test_id_1',
+                receptacleObjectId='test_id_2')
+        )
+
+        self.controller.step(
+            'OpenObject',
+            amount=1,
+            objectImageCoordsX=1,
+            objectImageCoordsY=2,
+            receptacleObjectImageCoordsX=4,
+            receptacleObjectImageCoordsY=5)
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MCSOpenObject', moveMagnitude=1.0,
+                objectImageCoords={
+                    'x': 1, 'y': MOCK_VARIABLES['metadata']['screenHeight'] - 3
+                },
+                receptacleObjectImageCoords={
+                    'x': 4, 'y': MOCK_VARIABLES['metadata']['screenHeight'] - 6
+                }
+            )
+        )
+
+    def test_step_validate_action(self):
+        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
+        output = self.controller.step('Foobar')
+        self.assertIsNone(output)
+
+        self.controller.set_goal(mcs.GoalMetadata(action_list=[
+            [('Pass', {})]
+        ]))
+        output = self.controller.step('MoveAhead')
+        self.assertIsNone(output)
+
+        self.controller.set_goal(mcs.GoalMetadata(action_list=[
+            [('MoveAhead', {})],
+            [('MoveBack', {})]]
+        ))
+        output = self.controller.step('MoveAhead')
+        self.assertIsNotNone(output)
+        output = self.controller.step('MoveAhead')
+        self.assertIsNone(output)
+
+    def test_step_validate_parameters_move(self):
+        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
+        self.controller.step('MoveAhead')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MoveAhead',
+                moveMagnitude=mcs.controller.DEFAULT_MOVE))
+
+        self.controller.step('MoveAhead')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MoveAhead',
+                moveMagnitude=mcs.controller.DEFAULT_MOVE))
+
+        self.controller.step('MoveAhead')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MoveAhead',
+                moveMagnitude=mcs.controller.DEFAULT_MOVE))
+
+        self.controller.step('MoveAhead')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='MoveAhead',
+                moveMagnitude=mcs.controller.DEFAULT_MOVE))
+
+    def test_step_validate_parameters_rotate(self):
+        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
+        self.controller.step('RotateLeft')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(action='RotateLeft'))
+
+        self.controller.step('RotateLeft')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='RotateLeft'))
+
+    def test_step_validate_parameters_force_object(self):
+        _ = self.controller.start_scene({'name': TEST_FILE_NAME})
+        self.controller.step(
+            'PushObject',
+            force=1,
+            objectId='test_id_1')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='PushObject',
+                horizon=0.0,
+                moveMagnitude=Parameter.MAX_FORCE,
+                objectId='test_id_1'))
+
+        self.controller.step(
+            'PushObject',
+            force=0.1,
+            objectId='test_id_1')
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='PushObject',
+                moveMagnitude=0.1 *
+                Parameter.MAX_FORCE,
+                objectId='test_id_1'))
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.controller.step(
+                'PushObject',
+                force=1.5,
+                objectId='test_id_1')
+        )
+        self.assertRaises(
+            ValueError,
+            lambda: self.controller.step(
+                'PushObject',
+                force=-1,
+                objectId='test_id_1')
+        )
+
+        self.controller.step(
+            'PushObject',
+            force=1,
+            objectImageCoordsX=1,
+            objectImageCoordsY=2)
+        self.assertEqual(
+            self.controller.get_last_step_data(),
+            self.create_step_data(
+                action='PushObject',
+                moveMagnitude=Parameter.MAX_FORCE,
+                objectImageCoords={'x': 1, 'y': 397}))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_controller_output_handler.py b/tests/test_controller_output_handler.py
index f18a909e7..6c1bb11bf 100644
--- a/tests/test_controller_output_handler.py
+++ b/tests/test_controller_output_handler.py
@@ -10,6 +10,10 @@
 from machine_common_sense.controller_output_handler import (
     ControllerOutputHandler, SceneEvent)
 from machine_common_sense.goal_metadata import GoalMetadata
+from machine_common_sense.logging_config import LoggingConfig
+
+# ignore printing of errors for unit testing
+LoggingConfig.init_logging(LoggingConfig.get_no_logging_config())
 
 
 class TestControllerOutputHandler(unittest.TestCase):
@@ -66,7 +70,6 @@ def create_wrap_output_scene_event(self):
                 "clippingPlaneFar": 15,
                 "clippingPlaneNear": 0,
                 "fov": 42.5,
-                "pose": mcs.Pose.STANDING.name,
                 "lastActionStatus": "SUCCESSFUL",
                 "lastActionSuccess": True,
                 "objects": [{
@@ -349,45 +352,6 @@ def create_retrieve_object_list_scene_event(self):
             }
         }
 
-    def test_retrieve_pose(self):
-        # Check function calls
-        mock_scene_event_data = {
-            "metadata": {
-                "pose": mcs.Pose.STANDING.name
-            },
-            "events": []
-        }
-        mock_event = self.create_mock_scene_event(mock_scene_event_data)
-
-        scene_event = SceneEvent(
-            self._config, {}, mock_event, 0)
-        ret_status = scene_event.pose
-        self.assertEqual(ret_status, mcs.Pose.STANDING.name)
-
-        mock_scene_event_data = {
-            "metadata": {
-                "pose": mcs.Pose.CRAWLING.name
-            },
-            "events": []
-        }
-        mock_event = self.create_mock_scene_event(mock_scene_event_data)
-        scene_event = SceneEvent(
-            self._config, {}, mock_event, 0)
-        ret_status = scene_event.pose
-        self.assertEqual(ret_status, mcs.Pose.CRAWLING.name)
-
-        mock_scene_event_data = {
-            "metadata": {
-                "pose": mcs.Pose.LYING.name
-            },
-            "events": []
-        }
-        mock_event = self.create_mock_scene_event(mock_scene_event_data)
-        scene_event = SceneEvent(
-            self._config, {}, mock_event, 0)
-        ret_status = scene_event.pose
-        self.assertEqual(ret_status, mcs.Pose.LYING.name)
-
     def test_retrieve_return_status(self):
         mock_scene_event_data = {
             "metadata": {
@@ -498,7 +462,6 @@ def test_wrap_output(self):
         self.assertEqual(str(actual.goal), str(mcs.GoalMetadata()))
         self.assertEqual(actual.habituation_trial, None)
         self.assertEqual(actual.head_tilt, 12.34)
-        self.assertEqual(actual.pose, mcs.Pose.STANDING.value)
         self.assertEqual(actual.position, {'x': 0.12, 'y': -0.23, 'z': 4.5})
         self.assertEqual(actual.rotation, 2.222)
         self.assertEqual(
@@ -615,7 +578,6 @@ def test_wrap_output_with_config_metadata_level2(self):
         self.assertEqual(actual.camera_height, 0.1234)
         self.assertEqual(actual.habituation_trial, None)
         self.assertEqual(actual.head_tilt, 12.34)
-        self.assertEqual(actual.pose, mcs.Pose.STANDING.value)
         self.assertEqual(actual.position, None)
         self.assertEqual(actual.rotation, None)
         self.assertEqual(
@@ -666,7 +628,6 @@ def test_wrap_output_with_config_metadata_level1(self):
         self.assertEqual(str(actual.goal), str(mcs.GoalMetadata()))
         self.assertEqual(actual.habituation_trial, None)
         self.assertEqual(actual.head_tilt, 12.34)
-        self.assertEqual(actual.pose, mcs.Pose.STANDING.value)
         self.assertEqual(actual.position, None)
         self.assertEqual(actual.rotation, None)
         self.assertEqual(
@@ -705,7 +666,6 @@ def test_wrap_output_with_config_metadata_none(self):
         self.assertEqual(str(actual.goal), str(mcs.GoalMetadata()))
         self.assertEqual(actual.habituation_trial, None)
         self.assertEqual(actual.head_tilt, 12.34)
-        self.assertEqual(actual.pose, mcs.Pose.STANDING.value)
         self.assertEqual(actual.position, None)
         self.assertEqual(actual.rotation, None)
         self.assertEqual(
@@ -744,7 +704,6 @@ def test_wrap_output_with_config_metadata_oracle_non_restrict(self):
         self.assertEqual(actual.camera_height, 0.1234)
         self.assertEqual(actual.habituation_trial, None)
         self.assertEqual(actual.head_tilt, 12.34)
-        self.assertEqual(actual.pose, mcs.Pose.STANDING.value)
         self.assertEqual(actual.position, {'x': 0.12, 'y': -0.23, 'z': 4.5})
         self.assertEqual(actual.rotation, 2.222)
         self.assertEqual(
@@ -793,7 +752,6 @@ def test_wrap_output_with_config_metadata_oracle(self):
         self.assertEqual(actual.camera_height, 0.1234)
         self.assertEqual(actual.habituation_trial, None)
         self.assertEqual(actual.head_tilt, 12.34)
-        self.assertEqual(actual.pose, mcs.Pose.STANDING.value)
         self.assertEqual(actual.position, {'x': 0.12, 'y': -0.23, 'z': 4.5})
         self.assertEqual(actual.rotation, 2.222)
         self.assertEqual(
@@ -1128,6 +1086,59 @@ def test_retrieve_object_list_with_config_metadata_none(self):
         actual = scene_event.object_list
         self.assertEqual(len(actual), 3)
 
+    def test_get_restrictions_none(self):
+        self._config.set_metadata_tier('none')
+        coh = ControllerOutputHandler(self._config)
+        restricted = False
+        restrictions = coh.get_restrictions(restricted, MetadataTier.NONE)
+        self.assertFalse(any(restrictions))
+
+        restricted = True
+        restrictions = coh.get_restrictions(restricted, MetadataTier.NONE)
+        self.assertTrue(all(restrictions))
+
+    def test_get_restrictions_level1(self):
+        self._config.set_metadata_tier('level1')
+        coh = ControllerOutputHandler(self._config)
+        restricted = False
+        restrictions = coh.get_restrictions(restricted, MetadataTier.LEVEL_1)
+        self.assertFalse(any(restrictions))
+
+        restricted = True
+        restrictions = coh.get_restrictions(restricted, MetadataTier.LEVEL_1)
+        self.assertTrue(any(restrictions))
+        self.assertFalse(restrictions[0])
+        self.assertTrue(restrictions[1])
+        self.assertTrue(restrictions[2])
+
+    def test_get_restrictions_level2(self):
+        self._config.set_metadata_tier('level2')
+        coh = ControllerOutputHandler(self._config)
+        restricted = False
+        restrictions = coh.get_restrictions(restricted, MetadataTier.LEVEL_2)
+        self.assertFalse(any(restrictions))
+
+        restricted = True
+        restrictions = coh.get_restrictions(restricted, MetadataTier.LEVEL_2)
+        self.assertTrue(any(restrictions))
+        self.assertFalse(restrictions[0])
+        self.assertFalse(restrictions[1])
+        self.assertTrue(restrictions[2])
+
+    def test_get_restrictions_oracle(self):
+        self._config.set_metadata_tier('oracle')
+        coh = ControllerOutputHandler(self._config)
+        restricted = False
+        restrictions = coh.get_restrictions(restricted, MetadataTier.ORACLE)
+        self.assertFalse(any(restrictions))
+
+        restricted = True
+        restrictions = coh.get_restrictions(restricted, MetadataTier.ORACLE)
+        self.assertFalse(any(restrictions))
+        self.assertFalse(restrictions[0])
+        self.assertFalse(restrictions[1])
+        self.assertFalse(restrictions[2])
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_history_event_handler.py b/tests/test_history_event_handler.py
index 222d4dd62..83886fdff 100644
--- a/tests/test_history_event_handler.py
+++ b/tests/test_history_event_handler.py
@@ -101,14 +101,14 @@ def test_on_before_step_zero(self):
         hist_writer.add_step.assert_not_called()
 
     def test_on_before_step_one(self):
-        histItem = SceneHistory(
+        hist = SceneHistory(
             step=1,
             action="MoveAhead",
             args=None,
             params=None,
             output=None,
             delta_time_millis=0)
-        self.histEvents._HistoryEventHandler__history_item = histItem
+        self.histEvents._HistoryEventHandler__history_item = hist
         self.histEvents._HistoryEventHandler__history_writer = MagicMock()
         test_payload = {
             "step_number": 1,
@@ -124,7 +124,7 @@ def test_on_before_step_one(self):
         hist_writer = self.histEvents._HistoryEventHandler__history_writer
 
         hist_writer.init_timer.assert_not_called()
-        hist_writer.add_step.assert_called_with(histItem)
+        hist_writer.add_step.assert_called_with(hist)
 
     def test_on_after_step(self):
         test_payload = {
@@ -147,25 +147,25 @@ def test_on_after_step(self):
 
         self.histEvents.on_after_step(after_step_payload)
 
-        histItem = self.histEvents._HistoryEventHandler__history_item
+        hist = self.histEvents._HistoryEventHandler__history_item
 
         after_step_payload.step_output.copy_without_depth_or_images.assert_called()  # noqa: E501
-        self.assertEqual(histItem.step, 1)
-        self.assertEqual(histItem.action, "MoveAhead")
-        self.assertEqual(histItem.delta_time_millis, 0)
+        self.assertEqual(hist.step, 1)
+        self.assertEqual(hist.action, "MoveAhead")
+        self.assertEqual(hist.delta_time_millis, 0)
 
     def test_on_end_scene(self):
         self.histEvents._HistoryEventHandler__history_writer = HistoryWriter(
             self.scene_config, {}, "20210831-202203")
         self.histEvents._HistoryEventHandler__history_writer.write_history_file = MagicMock()  # noqa: E501
-        histItem = SceneHistory(
+        hist = SceneHistory(
             step=1,
             action="MoveAhead",
             args=None,
             params=None,
             output=None,
             delta_time_millis=0)
-        self.histEvents._HistoryEventHandler__history_item = histItem
+        self.histEvents._HistoryEventHandler__history_item = hist
         test_payload = {}
         test_payload["step_number"] = 1
         test_payload["config"] = self.config_mngr
diff --git a/tests/test_history_writer.py b/tests/test_history_writer.py
index 12e8baedc..17bb12a19 100644
--- a/tests/test_history_writer.py
+++ b/tests/test_history_writer.py
@@ -3,6 +3,8 @@
 import shutil
 import unittest
 
+import numpy as np
+
 import machine_common_sense as mcs
 from machine_common_sense.config_manager import SceneConfiguration
 
@@ -102,7 +104,7 @@ def test_add_step_timer(self):
         # set start time back 500ms as if the run took 500ms.
         writer.last_step_time_millis -= 500
         # Save the time so we can check that it changes
-        priorToStep1 = writer.last_step_time_millis
+        initial_time = writer.last_step_time_millis
         history_item = mcs.SceneHistory(
             step=1,
             action="MoveAhead")
@@ -112,7 +114,7 @@ def test_add_step_timer(self):
         # we give some delta here because commands do take some time to run
         self.assertAlmostEqual(
             writer.current_steps[0]["delta_time_millis"], 500, delta=1)
-        self.assertNotEqual(priorToStep1, writer.last_step_time_millis)
+        self.assertNotEqual(initial_time, writer.last_step_time_millis)
 
         writer.last_step_time_millis -= 300
         history_item = mcs.SceneHistory(
@@ -191,8 +193,7 @@ def test_write_step_removes_some_output(self):
         writer = mcs.HistoryWriter(self.prefix_config_data)
 
         output = mcs.StepMetadata(
-            action_list=["CloseObject", "Crawl"],
-            pose="STANDING",
+            action_list=["CloseObject", "MoveAhead"],
             return_status="SUCCESSFUL",
             step_number=2,
             object_list={
@@ -212,7 +213,6 @@ def test_write_step_removes_some_output(self):
 
         self.assertEqual(len(writer.current_steps), 1)
         self.assertEqual(writer.current_steps[0]["action"], "MoveAhead")
-        self.assertEqual(writer.current_steps[0]["output"]["pose"], "STANDING")
         self.assertIsNone(writer.current_steps[0]["output"]["action_list"])
         self.assertIsNone(writer.current_steps[0]["output"]["object_list"])
         self.assertIsNone(
@@ -227,12 +227,39 @@ def test_write_step_removes_some_output(self):
 
         self.assertEqual(len(writer.current_steps), 2)
         self.assertEqual(writer.current_steps[1]["action"], "MoveLeft")
-        self.assertEqual(writer.current_steps[1]["output"]["pose"], "STANDING")
         self.assertIsNone(writer.current_steps[1]["output"]["action_list"])
         self.assertIsNone(writer.current_steps[1]["output"]["object_list"])
         self.assertIsNone(
             writer.current_steps[1]["output"]["structural_object_list"])
 
+    def test_write_history_file_with_numpy(self):
+        writer = mcs.HistoryWriter(self.prefix_config_data)
+
+        history_item = mcs.SceneHistory(
+            step=np.int32(1),
+            action="MoveAhead")
+        writer.add_step(history_item)
+
+        history_item = mcs.SceneHistory(
+            step=2,
+            action="MoveLeft")
+        writer.add_step(history_item)
+
+        writer.write_history_file("Plausible", np.float64(0.75))
+
+        self.assertEqual(writer.end_score["classification"], "Plausible")
+        self.assertEqual(writer.end_score["confidence"], "0.75")
+
+        self.assertEqual(
+            writer.history_obj["info"]["name"],
+            "prefix/test_scene_file")
+        self.assertEqual(len(writer.history_obj["steps"]), 2)
+        self.assertEqual(
+            writer.history_obj["score"]["classification"], "Plausible")
+        self.assertEqual(writer.history_obj["score"]["confidence"], "0.75")
+
+        self.assertTrue(os.path.exists(writer.scene_history_file))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_mcs.py b/tests/test_mcs.py
index 29ca8669a..f9ac5264b 100644
--- a/tests/test_mcs.py
+++ b/tests/test_mcs.py
@@ -8,7 +8,7 @@
     ObjectMaskImageEventHandler, SceneImageEventHandler,
     SegmentationVideoEventHandler, TopdownVideoEventHandler)
 from machine_common_sense.history_writer import HistoryEventHandler
-from machine_common_sense.setup import add_subscribers
+from machine_common_sense.subscriber import add_subscribers
 
 from .mock_controller import MockController
 
diff --git a/tests/test_parameter.py b/tests/test_parameter.py
new file mode 100644
index 000000000..469dc577d
--- /dev/null
+++ b/tests/test_parameter.py
@@ -0,0 +1,467 @@
+import unittest
+
+from machine_common_sense.config_manager import ConfigManager
+from machine_common_sense.controller import DEFAULT_MOVE
+from machine_common_sense.parameter import Parameter
+
+
+class TestParameter(unittest.TestCase):
+
+    sc = {'screenshot': False,
+          'version': 2,
+          'floorTextures': [],
+          'isometric': False,
+          'performerStart': {'rotation': {'y': 0.0,
+                                          'z': 0.0,
+                                          'x': 0.0},
+                             'position': {'y': 0.0,
+                                          'z': 0.0,
+                                          'x': 0.0}},
+          'observation': False,
+          'intuitivePhysics': False,
+          'floorMaterial': 'AI2-THOR/Materials/Fabrics/CarpetWhite 3',
+          'ceilingMaterial': 'AI2-THOR/Materials/Walls/Drywall',
+          'holes': [],
+          'objects': [{'shows': [{'position': {'y': 0.25,
+                                               'z': 1.0,
+                                               'x': 0.25},
+                                  'boundingBox': [],
+                                  'stepBegin': 0,
+                                  'scale': {'y': 0.5,
+                                            'z': 0.5,
+                                            'x': 0.5},
+                                  'rotation': {'y': 0.0,
+                                               'z': 0.0,
+                                               'x': 0.0}}],
+                       'pickupable': False,
+                       'mass': 1.0,
+                       'materials': ['AI2-THOR/Materials/Plastics/BlueRubber'],
+                       'salientMaterials': ['rubber'],
+                       'moveable': False,
+                       'type': 'ball',
+                       'id': 'testBall1'},
+                      {'shows': [{'position': {'y': 0.25,
+                                               'z': 2.0,
+                                               'x': -0.25},
+                                  'boundingBox': [],
+                                  'stepBegin': 0,
+                                  'scale': {'y': 0.5,
+                                            'z': 0.5,
+                                            'x': 0.5},
+                                  'rotation': {'y': 0.0,
+                                               'z': 0.0,
+                                               'x': 0.0}}],
+                       'pickupable': False,
+                       'mass': 1.0,
+                       'materials': ['AI2-THOR/Materials/Plastics/BlueRubber'],
+                       'salientMaterials': ['rubber'],
+                       'moveable': False,
+                       'type': 'ball',
+                       'id': 'testBall2'}],
+          'wallMaterial': 'AI2-THOR/Materials/Walls/Drywall',
+          'name': 'depth and segmentation test',
+          'roomDimensions': {'y': 3.0,
+                             'z': 10.0,
+                             'x': 10.0}}
+
+    def setUp(self):
+        config = ConfigManager(config_file_or_dict={})
+        self.parameter_converter = Parameter(config)
+
+    def test_initialization_wrap_step(self):
+        wrapped_step = self.parameter_converter.wrap_step(
+            action='Initialize', sceneConfig=self.sc)
+        # sceneConfig does not get removed
+        self.assertIsNotNone(wrapped_step.get('sceneConfig'))
+
+    def test_initialization_build_ai2thor_step(self):
+        wrapped_step, params = self.parameter_converter.build_ai2thor_step(
+            action='Initialize', sceneConfig=self.sc)
+        # sceneConfig gets removed
+        self.assertIsNone(wrapped_step.get('sceneConfig'))
+
+    def test_wrap_step_action(self):
+        actual = self.parameter_converter.wrap_step(
+            action="TestAction",
+            numberProperty=1234,
+            stringProperty="test_property")
+        expected = {
+            "action": "TestAction",
+            "continuous": True,
+            "gridSize": 0.1,
+            "logs": True,
+            "numberProperty": 1234,
+            "renderDepthImage": False,
+            "renderObjectImage": False,
+            "snapToGrid": False,
+            "stringProperty": "test_property",
+            "consistentColors": False
+        }
+        self.assertEqual(actual, expected)
+
+    def test_wrap_step_metadata_oracle(self):
+        config = ConfigManager(config_file_or_dict={'metadata': 'oracle'})
+        parameter_converter = Parameter(config)
+        actual = parameter_converter.wrap_step(
+            action="TestAction",
+            numberProperty=1234,
+            stringProperty="test_property")
+        # Changed depth and object because oracle should result in both being
+        # true.
+        expected = {
+            "action": "TestAction",
+            "continuous": True,
+            "gridSize": 0.1,
+            "logs": True,
+            "numberProperty": 1234,
+            "renderDepthImage": True,
+            "renderObjectImage": True,
+            "snapToGrid": False,
+            "stringProperty": "test_property",
+            "consistentColors": True
+        }
+        self.assertEqual(actual, expected)
+
+    def test_wrap_step_metadata_level2(self):
+        config = ConfigManager(config_file_or_dict={'metadata': 'level2'})
+        parameter_converter = Parameter(config)
+        actual = parameter_converter.wrap_step(
+            action="TestAction",
+            numberProperty=1234,
+            stringProperty="test_property")
+        # Changed depth and object because oracle should result in both being
+        # true.
+        expected = {
+            "action": "TestAction",
+            "continuous": True,
+            "gridSize": 0.1,
+            "logs": True,
+            "numberProperty": 1234,
+            "renderDepthImage": True,
+            "renderObjectImage": True,
+            "snapToGrid": False,
+            "stringProperty": "test_property",
+            "consistentColors": False
+        }
+        self.assertEqual(actual, expected)
+
+    def test_wrap_step_metadata_level1(self):
+        config = ConfigManager(config_file_or_dict={'metadata': 'level1'})
+        parameter_converter = Parameter(config)
+        actual = parameter_converter.wrap_step(
+            action="TestAction",
+            numberProperty=1234,
+            stringProperty="test_property")
+        # Changed depth and object because oracle should result in both being
+        # true.
+        expected = {
+            "action": "TestAction",
+            "continuous": True,
+            "gridSize": 0.1,
+            "logs": True,
+            "numberProperty": 1234,
+            "renderDepthImage": True,
+            "renderObjectImage": False,
+            "snapToGrid": False,
+            "stringProperty": "test_property",
+            "consistentColors": False
+        }
+        self.assertEqual(actual, expected)
+
+    def test_generate_noise(self):
+        min_noise = self.parameter_converter.MIN_NOISE
+        max_noise = self.parameter_converter.MAX_NOISE
+
+        current_noise = self.parameter_converter._generate_noise()
+        self.assertTrue(min_noise <= current_noise <= max_noise)
+
+    def test_get_amount(self):
+        amount = self.parameter_converter._get_amount(action="", amount=1)
+        self.assertIsInstance(amount, float)
+        self.assertAlmostEqual(amount, 1.0)
+
+        amount = self.parameter_converter._get_amount()
+        self.assertIsInstance(amount, float)
+        self.assertAlmostEqual(amount, Parameter.DEFAULT_AMOUNT)
+
+        amount = self.parameter_converter._get_amount(
+            action=Parameter.OBJECT_MOVE_ACTIONS[0])
+        self.assertIsInstance(amount, float)
+        self.assertAlmostEqual(amount, Parameter.DEFAULT_OBJECT_MOVE_AMOUNT)
+
+        amount = self.parameter_converter._get_amount(amount=None)
+        self.assertIsInstance(amount, float)
+        self.assertAlmostEqual(amount, Parameter.DEFAULT_AMOUNT)
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_amount(amount="string")
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_amount(amount=1.1)
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_amount(amount=-0.1)
+        )
+
+    def test_get_force(self):
+        force = self.parameter_converter._get_force(force=1)
+        self.assertIsInstance(force, float)
+        self.assertAlmostEqual(force, 1.0)
+
+        force = self.parameter_converter._get_force()
+        self.assertIsInstance(force, float)
+        self.assertAlmostEqual(force, Parameter.DEFAULT_FORCE)
+
+        force = self.parameter_converter._get_force(force=None)
+        self.assertIsInstance(force, float)
+        self.assertAlmostEqual(force, Parameter.DEFAULT_FORCE)
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_force(force="string")
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_force(force=1.1)
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_force(force=-0.1)
+        )
+
+    def test_get_number(self):
+        number = self.parameter_converter._get_number(key="val", val=7)
+        self.assertEqual(number, 7)
+        self.assertIsInstance(number, float)
+
+        number = self.parameter_converter._get_number(key="val", not_val=7)
+        self.assertIsNone(number)
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_number(
+                key="string", string="invalid"
+            )
+        )
+
+    def test_get_number_with_default(self):
+        number = self.parameter_converter._get_number_with_default(
+            key="val", default=5, val=7)
+        self.assertEqual(number, 7)
+        self.assertIsInstance(number, float)
+
+        number = self.parameter_converter._get_number_with_default(
+            key="val", default=5, not_val=7)
+        self.assertEqual(number, 5)
+        self.assertIsInstance(number, float)
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_number_with_default(
+                key="string", default=5, string="invalid")
+        )
+
+        number = self.parameter_converter._get_number_with_default(
+            key="val", default=5, val=None
+        )
+        self.assertEqual(number, 5)
+        self.assertIsInstance(number, float)
+
+    def test_get_move_magnitude(self):
+        magnitude = self.parameter_converter._get_move_magnitude(
+            action="",
+            force=0.5,
+            amount=0.7
+        )
+        self.assertIsInstance(magnitude, float)
+        self.assertEqual(magnitude, DEFAULT_MOVE)
+
+        magnitude = self.parameter_converter._get_move_magnitude(
+            action=Parameter.FORCE_ACTIONS[0],
+            force=0.5,
+            amount=0.7
+        )
+        self.assertIsInstance(magnitude, float)
+        self.assertEqual(magnitude, 0.5)
+
+        magnitude = self.parameter_converter._get_move_magnitude(
+            action=Parameter.OBJECT_MOVE_ACTIONS[0],
+            force=0.5,
+            amount=0.7
+        )
+        self.assertIsInstance(magnitude, float)
+        self.assertEqual(magnitude, 0.7)
+
+    def test_get_teleport(self):
+        (teleport_rot, teleport_pos) = self.parameter_converter._get_teleport()
+        self.assertIsNone(teleport_rot)
+        self.assertIsNone(teleport_pos)
+
+        (teleport_rot, teleport_pos) = self.parameter_converter._get_teleport(
+            yRotation=90
+        )
+        self.assertEqual(teleport_rot, {'y': 90.0})
+        self.assertIsNone(teleport_pos)
+
+        (teleport_rot, teleport_pos) = self.parameter_converter._get_teleport(
+            xPosition=1, zPosition=2)
+        self.assertIsNone(teleport_rot)
+        self.assertEqual(teleport_pos, {'x': 1.0, 'z': 2.0})
+
+        (teleport_rot, teleport_pos) = self.parameter_converter._get_teleport(
+            yRotation=90, xPosition=1, zPosition='2')
+        self.assertEqual(teleport_rot, {'y': 90.0})
+        self.assertEqual(teleport_pos, {'x': 1.0, 'z': 2.0})
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_teleport(
+                yRotation='invalid',
+                xPosition='1',
+                zPosition='2')
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_teleport(
+                yRotation='90',
+                xPosition='invalid',
+                zPosition='2'
+            )
+        )
+
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._get_teleport(
+                yRotation=90,
+                xPosition=1,
+                zPosition='invalid'
+            )
+        )
+
+    def test_teleport_position(self):
+        position = self.parameter_converter._get_teleport_position(
+            xPosition=1, zPosition=2)
+        self.assertIsInstance(position, dict)
+        self.assertIn('x', position)
+        self.assertIn('z', position)
+        self.assertIsInstance(position['x'], float)
+        self.assertIsInstance(position['z'], float)
+        self.assertEqual(position['x'], 1)
+        self.assertEqual(position['z'], 2)
+
+        position = self.parameter_converter._get_teleport_position(
+            xPosition=None, zPosition=None)
+        self.assertIsNone(position)
+
+        position = self.parameter_converter._get_teleport_position(
+            xPosition=None, zPosition=2)
+        self.assertIsNone(position)
+
+        position = self.parameter_converter._get_teleport_position(
+            xPosition=1, zPosition=None)
+        self.assertIsNone(position)
+
+    def test_teleport_rotation(self):
+        rotation = self.parameter_converter._get_teleport_rotation(
+            yRotation=45)
+        self.assertIsInstance(rotation, dict)
+        self.assertIn('y', rotation)
+        self.assertIsInstance(rotation['y'], float)
+        self.assertEqual(rotation['y'], 45)
+
+        rotation = self.parameter_converter._get_teleport_rotation(
+            yRotation=None)
+        self.assertIsNone(rotation)
+
+    def test_convert_y_image_coord_for_unity(self):
+        image_coord = 0
+        unity_coord = \
+            self.parameter_converter._convert_y_image_coord_for_unity(
+                image_coord)
+        screen_height = self.parameter_converter.config.get_screen_height()
+        self.assertEqual(unity_coord, screen_height - 1)
+
+        image_coord = screen_height - 1  # screen height is out of bounds
+        unity_coord = \
+            self.parameter_converter._convert_y_image_coord_for_unity(
+                image_coord)
+        self.assertEqual(unity_coord, 0)
+
+        image_coord = screen_height
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._convert_y_image_coord_for_unity(
+                y_coord=image_coord
+            )
+        )
+
+        # value much greater than the screen height
+        image_coord = 1000000
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._convert_y_image_coord_for_unity(
+                y_coord=image_coord
+            )
+        )
+
+        # value less than the origin
+        image_coord = -1
+        self.assertRaises(
+            ValueError,
+            lambda: self.parameter_converter._convert_y_image_coord_for_unity(
+                y_coord=image_coord
+            )
+        )
+
+        # Invalid image coordinate
+        image_coord = None
+        self.assertRaises(
+            TypeError,
+            lambda: self.parameter_converter._convert_y_image_coord_for_unity(
+                y_coord=image_coord
+            )
+        )
+
+        # Invalid image coordinate value
+        image_coord = "one"
+        self.assertRaises(
+            TypeError,
+            lambda: self.parameter_converter._convert_y_image_coord_for_unity(
+                y_coord=image_coord
+            )
+        )
+
+    def test_mcs_action_to_ai2thor_action(self):
+        # TODO any string is passed through
+        # should we leave an Action enum?
+        ai2thor_action = \
+            self.parameter_converter._mcs_action_to_ai2thor_action(
+                "")
+        self.assertIsInstance(ai2thor_action, str)
+        self.assertEqual(ai2thor_action, "")
+
+        ai2thor_action = \
+            self.parameter_converter._mcs_action_to_ai2thor_action(
+                "OpenObject")
+        self.assertIsInstance(ai2thor_action, str)
+        self.assertEqual(ai2thor_action, "MCSOpenObject")
+
+        ai2thor_action = \
+            self.parameter_converter._mcs_action_to_ai2thor_action(
+                "CloseObject")
+        self.assertIsInstance(ai2thor_action, str)
+        self.assertEqual(ai2thor_action, "MCSCloseObject")
+
+        ai2thor_action = \
+            self.parameter_converter._mcs_action_to_ai2thor_action(
+                "DropObject")
+        self.assertIsInstance(ai2thor_action, str)
+        self.assertEqual(ai2thor_action, "DropHandObject")
diff --git a/tests/test_plotter.py b/tests/test_plotter.py
index c23c5fa7f..9ae2e17f0 100644
--- a/tests/test_plotter.py
+++ b/tests/test_plotter.py
@@ -1,19 +1,24 @@
 import unittest
+from unittest.case import skip
 
 import ai2thor
 import PIL
 
-from machine_common_sense.config_manager import Vector3d
+from machine_common_sense.config_manager import (FloorHolesAndTexturesXZConfig,
+                                                 FloorTexturesConfig,
+                                                 SceneConfiguration, Vector3d)
 from machine_common_sense.plotter import TopDownPlotter, XZHeading
 
 
 class TestTopDownPlotter(unittest.TestCase):
 
     def setUp(self):
+        scene_config = SceneConfiguration(
+            name="test", room_dimensions=Vector3d(
+                x=10, y=4, z=10))
         self.plotter = TopDownPlotter(
             team="test",
-            scene_name="scene",
-            room_size=Vector3d(x=10, y=4, z=10)
+            scene_config=scene_config
         )
 
     def test_convert_color_empty(self):
@@ -169,9 +174,9 @@ def test_create_robot(self):
         robot = self.plotter._create_robot(robot_metadata)
 
         self.assertAlmostEqual(robot.x, 2.70)
-        self.assertAlmostEqual(robot.y, 1.23)
         self.assertAlmostEqual(robot.z, 3.14)
-        self.assertAlmostEqual(robot.rotation, 78.0)
+        self.assertAlmostEqual(robot.heading.x, 0.19562952)
+        self.assertAlmostEqual(robot.heading.z, 0.04158233)
 
     def test_create_robot_missing_position(self):
         robot_metadata = {
@@ -184,9 +189,9 @@ def test_create_robot_missing_position(self):
         robot = self.plotter._create_robot(robot_metadata)
 
         self.assertAlmostEqual(robot.x, 0.0)
-        self.assertAlmostEqual(robot.y, 0.0)
         self.assertAlmostEqual(robot.z, 0.0)
-        self.assertAlmostEqual(robot.rotation, 78.0)
+        self.assertAlmostEqual(robot.heading.x, 0.19562952)
+        self.assertAlmostEqual(robot.heading.z, 0.04158233)
 
     def test_create_robot_missing_rotation(self):
         robot_metadata = {
@@ -199,9 +204,9 @@ def test_create_robot_missing_rotation(self):
         robot = self.plotter._create_robot(robot_metadata)
 
         self.assertAlmostEqual(robot.x, 2.70)
-        self.assertAlmostEqual(robot.y, 1.23)
         self.assertAlmostEqual(robot.z, 3.14)
-        self.assertAlmostEqual(robot.rotation, 0.0)
+        self.assertAlmostEqual(robot.heading.x, 0.0)
+        self.assertAlmostEqual(robot.heading.z, 0.2)
 
     def test_create_object(self):
         object_metadata = {
@@ -324,16 +329,54 @@ def test_find_plottable_objects_filtered(self):
         self.assertEqual(filtered_objects[6]['objectId'], 'test-uuid3')
 
     def test_scene_name(self):
-        self.assertEqual(self.plotter._scene_name, "scene")
+        self.assertEqual(self.plotter._scene_name, "test")
 
     def test_scene_name_prefix(self):
+        scene_config = SceneConfiguration(
+            name="prefix/test", room_dimensions=Vector3d(
+                x=10, y=4, z=10))
         plotter = TopDownPlotter(
             team="test",
-            scene_name="prefix/scene",
-            room_size=Vector3d(x=10, y=3, z=10)
+            scene_config=scene_config
         )
 
-        self.assertEqual(plotter._scene_name, "scene")
+        self.assertEqual(plotter._scene_name, "test")
+
+    @skip("ResourceWarning")
+    def test_draw_holes(self):
+        holes = [
+            FloorHolesAndTexturesXZConfig(**{"x": 0, "z": 0}),
+            FloorHolesAndTexturesXZConfig(**{"x": 1, "z": 0}),
+            FloorHolesAndTexturesXZConfig(**{"x": 2, "z": 2})
+        ]
+        goal = {'metadata': {
+            'target': {'image': [0]},
+            'target_1': {'image': [1]},
+            'target_2': {'image': [2]}
+        }}
+        scene_config = SceneConfiguration(
+            name="testscene",
+            version=1,
+            goal=goal,
+            holes=holes,
+            room_dimensions=Vector3d(
+                x=10,
+                y=3,
+                z=10),
+            floor_textures=[FloorTexturesConfig(material="Lava", positions=[
+                FloorHolesAndTexturesXZConfig(x=-2, z=-2),
+                FloorHolesAndTexturesXZConfig(x=-1, z=-2),
+                FloorHolesAndTexturesXZConfig(x=-3, z=-3)
+            ])])
+
+        plotter = TopDownPlotter(
+            team="test",
+            scene_config=scene_config)
+        img = plotter.base_room_img.copy()
+        img = plotter._draw_holes(img, scene_config.holes)
+        img = plotter._draw_floor_textures(img, scene_config.floor_textures)
+        pil_img = plotter._export_plot(img)
+        pil_img.show()
 
 
 if __name__ == '__main__':
diff --git a/tests/test_reward.py b/tests/test_reward.py
index 5eaab6f1a..5ccf286b5 100644
--- a/tests/test_reward.py
+++ b/tests/test_reward.py
@@ -11,21 +11,21 @@ class TestReward(unittest.TestCase):
     def test_default_reward(self):
         goal = mcs.GoalMetadata()
         reward = mcs.Reward.calculate_reward(
-            goal, objects={}, agent={}, number_steps=1, reach=1.0)
+            goal, objects=[{}], agent={}, number_steps=1, reach=1.0)
         self.assertEqual(reward, -0.001)
         self.assertIsInstance(reward, float)
 
     def test_none_goal(self):
         goal = None
         reward = mcs.Reward.calculate_reward(
-            goal, objects={}, agent={}, number_steps=5, reach=1.0)
+            goal, objects=[{}], agent={}, number_steps=5, reach=1.0)
         self.assertEqual(reward, -0.005)
         self.assertIsInstance(reward, float)
 
     def test_penalty_step_calcuation(self):
         goal = None
         reward = mcs.Reward.calculate_reward(
-            goal, objects={}, agent={}, number_steps=456, reach=1.0)
+            goal, objects=[{}], agent={}, number_steps=456, reach=1.0)
         penalty = 0 - ((456) * 0.001)
         self.assertEqual(reward, penalty)
         self.assertIsInstance(reward, float)
@@ -50,18 +50,14 @@ def test_target_in_object_list(self):
         self.assertEqual(results['objectId'], target_id)
 
     def test_target_not_in_object_list(self):
-        obj_list = []
-        for i in range(10):
-            obj_list.append({"objectId": str(i)})
+        obj_list = [{"objectId": str(i)} for i in range(10)]
         target_id = '111'  # not in list
         results = mcs.Reward._Reward__get_object_from_list(
             obj_list, target_id)
         self.assertIsNone(results)
 
     def test_duplicate_target_ids_in_list(self):
-        obj_list = []
-        for i in range(10):
-            obj_list.append({"objectId": str(i)})
+        obj_list = [{"objectId": str(i)} for i in range(10)]
         # add duplicate object
         obj_list.append({"objectId": '7', "duplicate": True})
         target_id = '7'
@@ -213,9 +209,7 @@ def test_traversal_reward_large_object_inside(self):
         obj['objectBounds']['objectBoundsCorners'].append(
             {'x': 0.0, 'y': 1.0, 'z': 10.0})
         obj['position'] = {'x': 0.0, 'z': 0.0}
-        obj_list = []
-        obj_list.append(obj)
-
+        obj_list = [obj]
         agent = {'position': {'x': 5.0, 'y': 0.5, 'z': 5.0}}
         reward = mcs.Reward._calc_traversal_reward(goal, obj_list, agent,
                                                    performer_reach=1.0)
@@ -245,9 +239,7 @@ def test_traversal_reward_large_object_long_side(self):
         obj['objectBounds']['objectBoundsCorners'].append(
             {'x': 0.0, 'y': 1.0, 'z': 1.0})
         obj['position'] = {'x': 0.0, 'z': 0.0}
-        obj_list = []
-        obj_list.append(obj)
-
+        obj_list = [obj]
         agent = {'position': {'x': 10.1, 'y': 0.5, 'z': 1.1}}
         reward = mcs.Reward._calc_traversal_reward(goal, obj_list, agent,
                                                    performer_reach=1.0)
@@ -277,9 +269,7 @@ def test_traversal_reward_large_object_long_side_out_of_reach(self):
         obj['objectBounds']['objectBoundsCorners'].append(
             {'x': 0.0, 'y': 1.0, 'z': 1.0})
         obj['position'] = {'x': 0.0, 'z': 0.0}
-        obj_list = []
-        obj_list.append(obj)
-
+        obj_list = [obj]
         agent = {'position': {'x': 11.1, 'y': 0.5, 'z': 1.1}}
         reward = mcs.Reward._calc_traversal_reward(goal, obj_list, agent,
                                                    performer_reach=1.0)
@@ -309,9 +299,7 @@ def test_traversal_reward_large_object_short_side(self):
         obj['objectBounds']['objectBoundsCorners'].append(
             {'x': 0.0, 'y': 1.0, 'z': 1.0})
         obj['position'] = {'x': 0.0, 'z': 0.0}
-        obj_list = []
-        obj_list.append(obj)
-
+        obj_list = [obj]
         agent = {'position': {'x': -0.5, 'y': 0.5, 'z': 0.0}}
         reward = mcs.Reward._calc_traversal_reward(goal, obj_list, agent,
                                                    performer_reach=1.0)
@@ -341,9 +329,7 @@ def test_traversal_reward_large_object_short_side_out_of_reach(self):
         obj['objectBounds']['objectBoundsCorners'].append(
             {'x': 0.0, 'y': 1.0, 'z': 1.0})
         obj['position'] = {'x': 0.0, 'z': 0.0}
-        obj_list = []
-        obj_list.append(obj)
-
+        obj_list = [obj]
         agent = {'position': {'x': -1.5, 'y': 0.5, 'z': 0.0}}
         reward = mcs.Reward._calc_traversal_reward(goal, obj_list, agent,
                                                    performer_reach=1.0)
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index fda20f1a1..bef6354dd 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -1,44 +1,210 @@
 import unittest
 
 import numpy as np
+import PIL
 
-import machine_common_sense as mcs
-
-
-class TestSerializer(unittest.TestCase):
-
-    @staticmethod
-    def _helper_get_step_metadata():
-        with open('tests/test.msgpack', 'rb') as file:
-            packed_bytes = file.read()
-
-        serializer = mcs.SerializerMsgPack()
-        return serializer.deserialize(packed_bytes)
-
-    def test_serialization_msgpack(self):
-        unpacked_metadata = TestSerializer._helper_get_step_metadata()
-        assert abs(unpacked_metadata.reward - (-0.036000000000000004)
-                   ) < 1e-04, 'Reward unexpected.'
-        assert abs(unpacked_metadata.rotation -
-                   0.0) < 1e-04, 'Rotation unexpected.'
-        assert isinstance(unpacked_metadata.object_list[-1].shape, str
-                          ), 'Shape type unexpected.'
-
-    def test_serialization_json(self):
-        unpacked_metadata = TestSerializer._helper_get_step_metadata()
-
-        serializer = mcs.SerializerJson()
-        json_dump = serializer.serialize(unpacked_metadata)
-        unpacked_metadata = serializer.deserialize(json_dump)
-        self.assertEqual(len(unpacked_metadata.depth_map_list), 1)
-        self.assertIsInstance(unpacked_metadata.depth_map_list[0], np.ndarray)
-        self.assertEqual(unpacked_metadata.depth_map_list[0].shape, (400, 600))
-        self.assertIsInstance(unpacked_metadata, mcs.StepMetadata)
-        self.assertAlmostEqual(unpacked_metadata.reward, -0.036000000000000004)
-        self.assertAlmostEqual(unpacked_metadata.rotation, 0.0, delta=1e-04)
-        self.assertIsInstance(unpacked_metadata.object_list[-1].shape, str)
-        self.assertIsInstance(
-            unpacked_metadata.object_list[0].segment_color, dict)
+from machine_common_sense.goal_metadata import GoalMetadata
+from machine_common_sense.object_metadata import ObjectMetadata
+from machine_common_sense.serializer import ISerializer, SerializerMsgPack
+from machine_common_sense.step_metadata import StepMetadata
+
+
+class TestISerializer(unittest.TestCase):
+
+    def test_version(self):
+        version = "0.1.0"
+        self.assertEqual(ISerializer.version(), version)
+
+    def test_serialize(self):
+        self.assertRaises(NotImplementedError, ISerializer.serialize, None)
+
+    def test_deserialize(self):
+        self.assertRaises(NotImplementedError, ISerializer.deserialize, None)
+
+    def test_image_to_bytes(self):
+        size = (50, 100)
+        black = (0, 0, 0)
+        img = PIL.Image.new("RGB", size=size, color=black)
+        image_bytes = ISerializer.image_to_bytes(img)
+        self.assertIsInstance(image_bytes, bytes)
+        image_str = image_bytes.decode('ISO-8859-1')
+        self.assertTrue("PNG" in image_str)
+        self.assertTrue("IHDR" in image_str)
+        self.assertTrue("IDAT" in image_str)
+        self.assertTrue("IEND" in image_str)
+
+    def test_bytes_to_image(self):
+        # bytes representing a black (0,0,0) 50x100 image
+        image_bytes = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x002\x00' \
+            b'\x00\x00d\x08\x02\x00\x00\x00K\x8a\xf4\x0c\x00\x00\x00' \
+            b'&IDATx\x9c\xed\xc11\x01\x00\x00\x00\xc2\xa0\xf5Om\r\x0f' \
+            b'\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
+            b'\x00\x00\xe0\xc3\x00:\xfc\x00\x01\xc0gM\x95\x00\x00\x00' \
+            b'\x00IEND\xaeB`\x82'
+        image = ISerializer.bytes_to_image(image_bytes)
+        self.assertIsInstance(image, PIL.Image.Image)
+        self.assertEqual(image.size, (50, 100))
+        self.assertTrue(all(pixel == (0, 0, 0) for pixel in image.getdata()))
+
+
+class TestSerializerMsgPack(unittest.TestCase):
+
+    # allow larger unit tests comparisons for large dictionaries
+    maxDiff = None
+
+    @classmethod
+    def setUpClass(cls) -> None:
+        cls.serializer = SerializerMsgPack()
+
+    @classmethod
+    def tearDownClass(cls) -> None:
+        return super().tearDownClass()
+
+    def test_serialize(self):
+        packed_bytes = self.serializer.serialize(StepMetadata())
+        self.assertIsInstance(packed_bytes, bytes)
+
+    def test_deserializer(self):  # sourcery skip: class-extract-method
+        # serialize a default StepMetadata()
+        step_metadata = StepMetadata()
+        packed_bytes = self.serializer.serialize(step_metadata)
+        self.assertIsInstance(packed_bytes, bytes)
+
+        # deserialize bytes back to a StepMetadata
+        unpacked_step_metadata = self.serializer.deserialize(packed_bytes)
+        self.assertIsInstance(unpacked_step_metadata, StepMetadata)
+
+        # compare the two default StepMetadata objects
+        self.assertEqual(
+            unpacked_step_metadata.goal.__dict__,
+            step_metadata.goal.__dict__)
+        del step_metadata.goal
+        del unpacked_step_metadata.goal
+        self.assertEqual(
+            unpacked_step_metadata.__dict__,
+            step_metadata.__dict__)
+
+    def test_ext_pack_with_stepmetadata(self):
+        step_metadata = StepMetadata()
+        step_exttype = self.serializer._ext_pack(step_metadata)
+        self.assertEqual(step_exttype.code, 1)
+        self.assertIsInstance(step_exttype.data, bytes)
+
+    def test_ext_pack_with_tuple(self):
+        test_tuple = (1, 'a')
+        tuple_exttype = self.serializer._ext_pack(test_tuple)
+        self.assertEqual(tuple_exttype.code, 2)
+        self.assertIsInstance(tuple_exttype.data, bytes)
+        self.assertEqual(tuple_exttype.data, b'\x92\x01\xa1a')
+
+    def test_ext_pack_with_image(self):
+        size = (50, 100)
+        black = (0, 0, 0)
+        img = PIL.Image.new("RGB", size=size, color=black)
+        image_exttype = self.serializer._ext_pack(img)
+        self.assertEqual(image_exttype.code, 3)
+        self.assertIsInstance(image_exttype.data, bytes)
+        image_str = image_exttype.data.decode('ISO-8859-1')
+        self.assertTrue("PNG" in image_str)
+        self.assertTrue("IHDR" in image_str)
+        self.assertTrue("IDAT" in image_str)
+        self.assertTrue("IEND" in image_str)
+
+    def test_ext_pack_with_goalmetadata(self):
+        goal_md = GoalMetadata()
+        goal_exttype = self.serializer._ext_pack(goal_md)
+        self.assertEqual(goal_exttype.code, 4)
+        self.assertIsInstance(goal_exttype.data, bytes)
+
+    def test_ext_pack_with_objectmetadata(self):
+        obj_md = ObjectMetadata()
+        obj_exttype = self.serializer._ext_pack(obj_md)
+        self.assertEqual(obj_exttype.code, 5)
+        self.assertIsInstance(obj_exttype.data, bytes)
+
+    def test_ext_pack_with_ndarray(self):
+        array = np.zeros(10)
+        np_exttype = self.serializer._ext_pack(array)
+        self.assertEqual(np_exttype.code, 6)
+        self.assertIsInstance(np_exttype.data, bytes)
+
+    def test_ext_unpack_with_stepmetadata(self):
+        # from default StepMetadata()
+        test_data = b'\xdc\x00\x11\x90\xc7\x13\x02\x92\xcb\x00\x00\x00\x00' \
+            b'\x00\x00\x00\x00\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xc7\x13' \
+            b'\x02\x92\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xcb\x00\x00\x00' \
+            b'\x00\x00\x00\x00\x00\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xcb' \
+            b'\x00\x00\x00\x00\x00\x00\x00\x00\x90\xd7\x04\x97\xc0\xa0\xa0' \
+            b'\x00\x00\xc0\x80\xcb\x00\x00\x00\x00\x00\x00\x00\x00\x90\x90' \
+            b'\x90\x80\xa9UNDEFINED\x00\xcb\x00\x00\x00\x00\x00\x00\x00' \
+            b'\x00\x00\x90'
+        unpacked_step = self.serializer._ext_unpack(code=1, data=test_data)
+        self.assertIsInstance(unpacked_step, StepMetadata)
+        step_metadata = StepMetadata()
+        # Removing default goal metadata as they don't compare well
+        # as nested dictionaries with differing ids. Individual goal elements
+        # are the same however.
+        self.assertEqual(
+            unpacked_step.goal.__dict__,
+            step_metadata.goal.__dict__)
+        del step_metadata.goal
+        del unpacked_step.goal
+        self.assertEqual(unpacked_step.__dict__, step_metadata.__dict__)
+
+    def test_ext_unpack_with_tuple(self):
+        test_tuple = (1, 'a')
+        test_data = b'\x92\x01\xa1a'
+        unpacked_tuple = self.serializer._ext_unpack(code=2, data=test_data)
+        self.assertIsInstance(unpacked_tuple, tuple)
+        self.assertEqual(unpacked_tuple, test_tuple)
+
+    def test_ext_unpack_with_image(self):
+        # from (50,100) image of all black pixels (0,0,0)
+        test_data = b'\xc4_\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x002' \
+            b'\x00\x00\x00d\x08\x02\x00\x00\x00K\x8a\xf4\x0c\x00\x00\x00' \
+            b'&IDATx\x9c\xed\xc11\x01\x00\x00\x00\xc2\xa0\xf5Om\r\x0f' \
+            b'\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
+            b'\x00\x00\xe0\xc3\x00:\xfc\x00\x01\xc0gM\x95\x00\x00\x00' \
+            b'\x00IEND\xaeB`\x82'
+        unpacked_image = self.serializer._ext_unpack(3, test_data)
+        self.assertIsInstance(unpacked_image, PIL.Image.Image)
+        self.assertEqual(unpacked_image.size, (50, 100))
+        self.assertTrue(all(pixel == (0, 0, 0)
+                            for pixel in unpacked_image.getdata()))
+
+    def test_ext_unpack_with_goalmetadata(self):
+        # from default GoalMetadata()
+        test_data = b'\x97\xc0\xa0\xa0\x00\x00\xc0\x80'
+        unpacked_goal = self.serializer._ext_unpack(4, test_data)
+        self.assertIsInstance(unpacked_goal, GoalMetadata)
+        self.assertEqual(unpacked_goal.__dict__, GoalMetadata().__dict__)
+
+    def test_ext_unpack_with_objectmetadata(self):
+        # from default ObjectMetadata()
+        obj_data = b'\xdc\x00\x10\xa0\x90\x80\xcb\xbf\xf0\x00\x00\x00\x00' \
+            b'\x00\x00\xcb\xbf\xf0\x00\x00\x00\x00\x00\x00\xcb\xbf\xf0\x00' \
+            b'\x00\x00\x00\x00\x00\xc2\xcb\x00\x00\x00\x00\x00\x00\x00\x00' \
+            b'\x90\x80\x80\x80\xa0\x90\x90\xc2'
+        unpacked_objectmetadata = self.serializer._ext_unpack(
+            code=5, data=obj_data)
+        self.assertIsInstance(unpacked_objectmetadata, ObjectMetadata)
+        self.assertEqual(
+            unpacked_objectmetadata.__dict__,
+            ObjectMetadata().__dict__)
+
+    def test_ext_unpack_with_ndarray(self):
+        # from [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
+        test_data = b'\x9a\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xcb\x00\x00' \
+            b'\x00\x00\x00\x00\x00\x00\xcb\x00\x00\x00\x00\x00\x00\x00\x00' \
+            b'\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xcb\x00\x00\x00\x00\x00' \
+            b'\x00\x00\x00\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xcb\x00\x00' \
+            b'\x00\x00\x00\x00\x00\x00\xcb\x00\x00\x00\x00\x00\x00\x00\x00' \
+            b'\xcb\x00\x00\x00\x00\x00\x00\x00\x00\xcb\x00\x00\x00\x00\x00' \
+            b'\x00\x00\x00'
+        unpacked_ndarray = self.serializer._ext_unpack(6, test_data)
+        self.assertIsInstance(unpacked_ndarray, np.ndarray)
+        self.assertFalse(np.all(unpacked_ndarray))
+        self.assertEqual(unpacked_ndarray.size, 10)
 
 
 if __name__ == '__main__':
diff --git a/tests/test_step_metadata.py b/tests/test_step_metadata.py
index ed96831b0..7a1344c2d 100644
--- a/tests/test_step_metadata.py
+++ b/tests/test_step_metadata.py
@@ -31,7 +31,6 @@ class TestStepMetadata(unittest.TestCase):
         "performer_radius": 0.0,
         "performer_reach": 0.0,
         "physics_frames_per_second": 0,
-        "pose": "UNDEFINED",
         "position": {},
         "return_status": "UNDEFINED",
         "reward": 0,
@@ -64,7 +63,6 @@ class TestStepMetadata(unittest.TestCase):
         "performer_radius": 0.0,
         "performer_reach": 0.0,
         "physics_frames_per_second": 0,
-        "pose": "UNDEFINED",
         "position": {},
         "return_status": "UNDEFINED",
         "reward": 0,
@@ -140,10 +138,6 @@ def test_performer_reach(self):
         self.assertEqual(self.step_metadata.performer_reach, 0.0)
         self.assertIsInstance(self.step_metadata.performer_reach, float)
 
-    def test_pose(self):
-        self.assertEqual(self.step_metadata.pose, mcs.Pose.UNDEFINED.value)
-        self.assertIsInstance(self.step_metadata.pose, str)
-
     def test_position(self):
         self.assertIsInstance(self.step_metadata.position, dict)
 
@@ -206,7 +200,6 @@ def test_copy_without_depth_or_images(self):
             performer_radius=0.5,
             performer_reach=1,
             physics_frames_per_second=20,
-            pose=mcs.Pose.CRAWLING.value,
             position={'x': 1, 'z': 2},
             return_status=mcs.ReturnStatus.SUCCESSFUL.value,
             reward=0,
@@ -243,7 +236,6 @@ def test_copy_without_depth_or_images(self):
             data.physics_frames_per_second,
             copy.physics_frames_per_second
         )
-        self.assertEqual(data.pose, copy.pose)
         self.assertEqual(data.position, copy.position)
         self.assertEqual(data.return_status, copy.return_status)
         self.assertEqual(data.reward, copy.reward)
diff --git a/tests/test_validation.py b/tests/test_validation.py
deleted file mode 100644
index 4ea1714cb..000000000
--- a/tests/test_validation.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import unittest
-
-import machine_common_sense as mcs
-
-
-class TestValidation(unittest.TestCase):
-
-    def test_is_in_range(self):
-        self.assertEqual(mcs.Validation.is_in_range(0, 0, 1, 1234), 0)
-        self.assertEqual(mcs.Validation.is_in_range(0.5, 0, 1, 1234), 0.5)
-        self.assertEqual(mcs.Validation.is_in_range(1, 0, 1, 1234), 1)
-
-        self.assertEqual(mcs.Validation.is_in_range(-1, 0, 1, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(1.01, 0, 1, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(100, 0, 1, 1234), 1234)
-
-        self.assertEqual(mcs.Validation.is_in_range(2, 2, 4, 1234), 2)
-        self.assertEqual(mcs.Validation.is_in_range(2.1, 2, 4, 1234), 2.1)
-        self.assertEqual(mcs.Validation.is_in_range(4, 2, 4, 1234), 4)
-
-        self.assertEqual(mcs.Validation.is_in_range(1.9, 2, 4, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(-3, 2, 4, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(4.1, 2, 4, 1234), 1234)
-
-        self.assertEqual(mcs.Validation.is_in_range(-2, -2, 2, 1234), -2)
-        self.assertEqual(mcs.Validation.is_in_range(0, -2, 2, 1234), 0)
-        self.assertEqual(mcs.Validation.is_in_range(2, -2, 2, 1234), 2)
-
-        self.assertEqual(mcs.Validation.is_in_range(-2.1, -2, 2, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(2.1, -2, 2, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(200, -2, 2, 1234), 1234)
-
-        self.assertEqual(mcs.Validation.is_in_range(-4, -4, -2, 1234), -4)
-        self.assertEqual(mcs.Validation.is_in_range(-2.1, -4, -2, 1234), -2.1)
-        self.assertEqual(mcs.Validation.is_in_range(-2, -4, -2, 1234), -2)
-
-        self.assertEqual(mcs.Validation.is_in_range(-5, -4, -2, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(-1, -4, -2, 1234), 1234)
-        self.assertEqual(mcs.Validation.is_in_range(3, -4, -2, 1234), 1234)
-
-    def test_is_number(self):
-        self.assertEqual(mcs.Validation.is_number('0'), True)
-        self.assertEqual(mcs.Validation.is_number('1'), True)
-        self.assertEqual(mcs.Validation.is_number('12.34'), True)
-        self.assertEqual(mcs.Validation.is_number('01'), True)
-        self.assertEqual(mcs.Validation.is_number(''), False)
-        self.assertEqual(mcs.Validation.is_number('asdf'), False)
-
-
-if __name__ == '__main__':
-    unittest.main()