diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml
index d1ecb1bb02..4b3d66da50 100644
--- a/.github/workflows/pr_build.yml
+++ b/.github/workflows/pr_build.yml
@@ -9,7 +9,8 @@ env:
   BUILD_TYPE: Release
   CI_BUILD: Github
   GIT_COMMIT: ${{ github.sha }}
-  HIFI_VCPKG_BOOTSTRAP: true
+  # VCPKG did not build well on OSX disabling HIFI_VCPKG_BOOTSTRAP, which invokes a download to a working version of vcpkg
+  # HIFI_VCPKG_BOOTSTRAP: true
   RELEASE_TYPE: PR
   RELEASE_NUMBER: ${{ github.event.number }}
   VERSION_CODE: ${{ github.event.number }}
@@ -28,8 +29,12 @@ jobs:
   build:
     strategy:
         matrix:
-          os: [windows-latest, macOS-latest]
+          os: [windows-latest, macOS-latest, ubuntu-18.04]
           build_type: [full]
+          include:
+            - os: ubuntu-18.04
+              build_type: full
+              apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
         fail-fast: false
     runs-on: ${{matrix.os}}
     if: github.event.action != 'labeled' || github.event.label.name == 'rebuild'
@@ -39,10 +44,14 @@ jobs:
       id: buildenv1
       run: |
         echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
+        echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
+
         # Linux build variables
-        if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
+        if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
           echo ::set-env name=PYTHON_EXEC::python3
-          echo ::set-env name=INSTALLER_EXT::tgz
+          echo ::set-env name=INSTALLER_EXT::*
+          echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
+          echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE"
         fi
         # Mac build variables
         if [ "${{ matrix.os }}" = "macOS-latest" ]; then
@@ -61,11 +70,11 @@ jobs:
       shell: bash
       run: |
         echo "${{ steps.buildenv1.outputs.symbols_archive }}"
-        echo ::set-env name=ARTIFACT_PATTERN::HighFidelity-Beta-*.$INSTALLER_EXT
+        echo ::set-env name=ARTIFACT_PATTERN::ProjectAthena-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT
         # Build type variables
         echo ::set-env name=INSTALLER::HighFidelity-Beta-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
     - name: Clear Working Directory
-      if: matrix.os[1] == 'windows'
+      if: startsWith(matrix.os, 'windows')
       shell: bash
       working-directory: ${{runner.workspace}}
       run: rm -rf ./*
@@ -73,61 +82,83 @@ jobs:
       with: 
         submodules: true
         fetch-depth: 1
+    - name: Install dependencies
+      shell: bash
+      if: startsWith(matrix.os, 'ubuntu')
+      run: |
+        echo "Installing Python Modules:"
+        pip3 install distro || exit 1
+
+        echo "Updating apt repository index"
+        sudo apt update || exit 1
+
+        echo "Installing apt packages"
+        sudo apt install -y ${{ matrix.apt-dependencies }} || exit 1
     - name: Create Build Environment
       shell: bash
       run: cmake -E make_directory "${{runner.workspace}}/build"
     - name: Configure CMake
       working-directory: ${{runner.workspace}}/build
       shell: bash
-      run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE $CMAKE_EXTRA
+      run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release $CMAKE_EXTRA
     - name: Build Application
       working-directory: ${{runner.workspace}}/build
       shell: bash
-      run: cmake --build . --config $BUILD_TYPE --target $APP_NAME
+      run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
     - name: Build Domain Server
       working-directory: ${{runner.workspace}}/build
       shell: bash
-      run: cmake --build . --config $BUILD_TYPE --target domain-server
+      run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
     - name: Build Assignment Client
       working-directory: ${{runner.workspace}}/build
       shell: bash
-      run: cmake --build . --config $BUILD_TYPE --target assignment-client
+      run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
     - name: Build Console
       working-directory: ${{runner.workspace}}/build
       shell: bash
-      run: cmake --build . --config $BUILD_TYPE --target packaged-server-console
+      run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
     - name: Build Installer
       working-directory: ${{runner.workspace}}/build
       shell: bash
-      run: cmake --build . --config $BUILD_TYPE --target package
+      run: |
+        echo "Retry code from https://unix.stackexchange.com/a/137639"
+        function fail {
+          echo $1 >&2
+          exit 1
+        }
+
+        function retry {
+          local n=1
+          local max=5
+          local delay=15
+          while true; do
+            "$@" && break || {
+              if [[ $n -lt $max ]]; then
+                ((n++))
+                echo "Command failed. Attempt $n/$max:"
+                sleep $delay;
+              else
+                fail "The command has failed after $n attempts."
+              fi
+            }
+          done
+        }
+        retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
+    - name: Output system stats
+      if: ${{ always() }}
+      working-directory: ${{runner.workspace}}/build
+      shell: bash
+      run: |
+        echo "Disk usage:"
+        df -h
     - name: Output Installer Logs
       if: failure() && matrix.os == 'windows-latest'
       shell: bash
       working-directory: ${{runner.workspace}}/build
       run: cat ./_CPack_Packages/win64/NSIS/NSISOutput.log
-
-  build_full_linux:
-    runs-on: ubuntu-latest
-    if: github.event.action != 'labeled' || github.event.label.name == 'rebuild'
-    steps:
-    - uses: actions/checkout@v1
-      with: 
-        submodules: true
-        fetch-depth: 1
-    - name: Update apt repository index
-      run: sudo apt update
-    - name: Install apt packages
-      run: sudo apt install -y mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
-    - name: Install python modules
-      shell: bash
-      run: pip install boto3 distro PyGithub
-    - name: Create Build Environment
-      run: cmake -E make_directory ${{runner.workspace}}/build
-    - name: Configure CMake
-      working-directory: ${{runner.workspace}}/build
-      shell: bash
-      run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_TOOLS:BOOLEAN=FALSE
-    - name:
+    - name: Upload Artifact
       shell: bash
       working-directory: ${{runner.workspace}}/build
-      run: cmake --build . -- -j3
+      env:
+        GITHUB_CONTEXT: ${{ toJson(github) }}
+      run: $PYTHON_EXEC "$GITHUB_WORKSPACE/tools/ci-scripts/upload_to_publish_server.py"
diff --git a/BUILD.md b/BUILD.md
index bc032d4ec7..1cad8724b3 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -73,6 +73,18 @@ Create a build directory in the root of your checkout and then run the CMake bui
 
 If cmake gives you the same error message repeatedly after the build fails, try removing `CMakeCache.txt`.
 
+##### Generating a release/debug only vcpkg build
+
+In order to generate a release or debug only vcpkg package, you could use the use the `VCPKG_BUILD_TYPE` define in your cmake generate command. Building a release only vcpkg can drastically decrease the total build time.
+
+For release only vcpkg:
+
+`cmake .. -DVCPKG_BUILD_TYPE=release`
+
+For debug only vcpkg:
+
+`cmake .. -DVCPKG_BUILD_TYPE=debug`
+
 #### Variables
 
 Any variables that need to be set for CMake to find dependencies can be set as ENV variables in your shell profile, or passed directly to CMake with a `-D` flag appended to the `cmake ..` command.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4bf7b8bc56..ecbfd2eed9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,8 +67,12 @@ if (HIFI_ANDROID)
         WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     )
 else()
+    set(VCPKG_BUILD_TYPE_PARAM "")
+    if (VCPKG_BUILD_TYPE)
+      set(VCPKG_BUILD_TYPE_PARAM --vcpkg-build-type ${VCPKG_BUILD_TYPE})
+    endif()
     execute_process(
-        COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --release-type ${RELEASE_TYPE} --build-root ${CMAKE_BINARY_DIR}
+        COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --release-type ${RELEASE_TYPE} --build-root ${CMAKE_BINARY_DIR} ${VCPKG_BUILD_TYPE_PARAM}
         WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     )
     # squelch the Policy CMP0074 warning without requiring an update to cmake 3.12.
diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h
index 500c3f44fb..1e6046ba7e 100644
--- a/assignment-client/src/avatars/ScriptableAvatar.h
+++ b/assignment-client/src/avatars/ScriptableAvatar.h
@@ -82,15 +82,15 @@
  *     <p><strong>Note:</strong> This property will automatically be set to true if the Controller system has valid facial 
  *     blend shape actions.</p>
  * @property {boolean} hasProceduralBlinkFaceMovement=true - <code>true</code> if avatars blink automatically by animating 
- *     facial blend shapes, <code>false</code> if automatic blinking is disabled. Set this property to <code>false</code> if 
- *     you wish to fully control the blink facial blend shapes via the {@link MyAvatar.setBlendshape} method.
+ *     facial blend shapes, <code>false</code> if automatic blinking is disabled. Set to <code>false</code> to fully control 
+ *     the blink facial blend shapes via the {@link MyAvatar.setBlendshape} method.
  * @property {boolean} hasProceduralEyeFaceMovement=true - <code>true</code> if the facial blend shapes for an avatar's eyes 
  *     adjust automatically as the eyes move, <code>false</code> if this automatic movement is disabled. Set this property 
- *     to <code>true</code> to prevent the iris from being obscured by the upper or lower lids. Set this property to  
- *     <code>false</code> if you wish to fully control the eye blend shapes via the {@link MyAvatar.setBlendshape} method.
+ *     to <code>true</code> to prevent the iris from being obscured by the upper or lower lids. Set to <code>false</code> to 
+ *     fully control the eye blend shapes via the {@link MyAvatar.setBlendshape} method.
  * @property {boolean} hasAudioEnabledFaceMovement=true - <code>true</code> if the avatar's mouth blend shapes animate 
  *     automatically based on detected microphone input, <code>false</code> if this automatic movement is disabled. Set 
- *     this property to <code>false</code> if you wish to fully control the mouth facial blend shapes via the 
+ *     this property to <code>false</code> to fully control the mouth facial blend shapes via the 
  *     {@link MyAvatar.setBlendshape} method.
  *
  * @example <caption>Create a scriptable avatar.</caption>
diff --git a/cmake/macros/TargetDraco.cmake b/cmake/macros/TargetDraco.cmake
index 520786d4c3..e23069d1d3 100755
--- a/cmake/macros/TargetDraco.cmake
+++ b/cmake/macros/TargetDraco.cmake
@@ -1,6 +1,5 @@
 macro(TARGET_DRACO)
     set(LIBS draco dracodec dracoenc)
-    find_library(LIBPATH ${LIB} PATHS )
     if (ANDROID)
         set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/draco)
         set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
@@ -12,6 +11,8 @@ macro(TARGET_DRACO)
     else()
         set(LIB_SEARCH_PATH_RELEASE ${VCPKG_INSTALL_ROOT}/lib/)
         set(LIB_SEARCH_PATH_DEBUG ${VCPKG_INSTALL_ROOT}/debug/lib/)
+        set(DRACO_LIBRARY_RELEASE "")
+        set(DRACO_LIBRARY_DEBUG "")
         foreach(LIB ${LIBS}) 
             find_library(${LIB}_LIBPATH ${LIB} PATHS ${LIB_SEARCH_PATH_RELEASE} NO_DEFAULT_PATH)
             list(APPEND DRACO_LIBRARY_RELEASE ${${LIB}_LIBPATH})
diff --git a/cmake/macros/TargetOpenEXR.cmake b/cmake/macros/TargetOpenEXR.cmake
index 8d61f216e7..9d63ba3ef4 100644
--- a/cmake/macros/TargetOpenEXR.cmake
+++ b/cmake/macros/TargetOpenEXR.cmake
@@ -28,6 +28,8 @@ macro(TARGET_OPENEXR)
             string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP})
         endif()
 
+        set(OPENEXR_LIBRARY_RELEASE "")
+        set(OPENEXR_LIBRARY_DEBUG "")
         foreach(OPENEXR_LIB
             IlmImf
             IlmImfUtil
diff --git a/cmake/ports/glad/portfile.cmake b/cmake/ports/glad/portfile.cmake
index 56ee59e0d4..54b1d91c89 100644
--- a/cmake/ports/glad/portfile.cmake
+++ b/cmake/ports/glad/portfile.cmake
@@ -1,17 +1,19 @@
 include(vcpkg_common_functions)
 vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
 
+file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
+
 if (ANDROID)
     vcpkg_download_distfile(
         SOURCE_ARCHIVE
-        URLS https://athena-public.s3.amazonaws.com/dependencies/glad/glad32es.zip
+        URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad32es.zip
         SHA512 2e02ac633eed8f2ba2adbf96ea85d08998f48dd2e9ec9a88ec3c25f48eaf1405371d258066327c783772fcb3793bdb82bd7375fdabb2ba5e2ce0835468b17f65
     )
 else()
     # else Linux desktop
     vcpkg_download_distfile(
         SOURCE_ARCHIVE
-        URLS https://athena-public.s3.amazonaws.com/dependencies/glad/glad45.zip
+        URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/glad/glad45.zip
         SHA512 653a7b873f9fbc52e0ab95006cc3143bc7b6f62c6e032bc994e87669273468f37978525c9af5efe36f924cb4acd221eb664ad9af0ce4bf711b4f1be724c0065e
         FILENAME glad45.zip
     )
@@ -33,4 +35,3 @@ vcpkg_install_cmake()
 
 file(COPY ${CMAKE_CURRENT_LIST_DIR}/copyright DESTINATION ${CURRENT_PACKAGES_DIR}/share/glad)
 file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
-
diff --git a/cmake/ports/glm/disable_warnings_as_error.patch b/cmake/ports/glm/disable_warnings_as_error.patch
index f87616b1ec..5dabaa6323 100644
--- a/cmake/ports/glm/disable_warnings_as_error.patch
+++ b/cmake/ports/glm/disable_warnings_as_error.patch
@@ -1,7 +1,16 @@
 diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 756673a3..5fbc8906 100644
+index 756673a..9b3aa07 100644
 --- a/CMakeLists.txt
 +++ b/CMakeLists.txt
+@@ -194,7 +194,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ 		message("GLM: Clang - ${CMAKE_CXX_COMPILER_ID} compiler")
+ 	endif()
+ 
+-	add_compile_options(-Werror -Weverything)
++	add_compile_options(-Weverything)
+ 	add_compile_options(-Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++11-long-long -Wno-padded -Wno-gnu-anonymous-struct -Wno-nested-anon-types)
+ 	add_compile_options(-Wno-undefined-reinterpret-cast -Wno-sign-conversion -Wno-unused-variable -Wno-missing-prototypes -Wno-unreachable-code -Wno-missing-variable-declarations -Wno-sign-compare -Wno-global-constructors -Wno-unused-macros -Wno-format-nonliteral)
+ 
 @@ -216,7 +216,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
  		message("GLM: Visual C++ - ${CMAKE_CXX_COMPILER_ID} compiler")
  	endif()
diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL
index d4b4acd4c5..ff689c7a2a 100644
--- a/cmake/ports/hifi-deps/CONTROL
+++ b/cmake/ports/hifi-deps/CONTROL
@@ -1,4 +1,4 @@
 Source: hifi-deps
-Version: 0.1.4-github-actions
+Version: 0.1.5-github-actions
 Description: Collected dependencies for High Fidelity applications
 Build-Depends: bullet3, draco, etc2comp, glad, glm, nvtt, openexr (!android), openssl (windows), opus, polyvox, tbb (!android), vhacd, webrtc (!android), zlib
diff --git a/cmake/ports/polyvox/portfile.cmake b/cmake/ports/polyvox/portfile.cmake
index 54cc74d4dd..9204b26dbd 100644
--- a/cmake/ports/polyvox/portfile.cmake
+++ b/cmake/ports/polyvox/portfile.cmake
@@ -1,9 +1,11 @@
 include(vcpkg_common_functions)
 
+file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
+
 # else Linux desktop
 vcpkg_download_distfile(
     SOURCE_ARCHIVE
-    URLS https://athena-public.s3.amazonaws.com/dependencies/polyvox-master-2015-7-15.zip
+    URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/polyvox-master-2015-7-15.zip
     SHA512 cc04cd43ae74b9c7bb065953540c0048053fcba6b52dc4218b3d9431fba178d65ad4f6c53cc1122ba61d0ab4061e99a7ebbb15db80011d607c5070ebebf8eddc
     FILENAME polyvox.zip
 )
@@ -23,33 +25,45 @@ vcpkg_install_cmake()
 
 file(INSTALL ${SOURCE_PATH}/LICENSE.TXT DESTINATION ${CURRENT_PACKAGES_DIR}/share/polyvox RENAME copyright)
 file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/include)
-file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/lib)
-file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/lib)
+if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
+    file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/lib)
+endif()
+if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
+    file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/lib)
+endif()
 if(WIN32)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxCore/lib/Release/PolyVoxCore.lib ${CURRENT_PACKAGES_DIR}/lib/PolyVoxCore.lib)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/debug/PolyVoxCore/lib/Debug/PolyVoxCore.lib ${CURRENT_PACKAGES_DIR}/debug/lib/PolyVoxCore.lib)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxUtil/lib/PolyVoxUtil.lib ${CURRENT_PACKAGES_DIR}/lib/PolyVoxUtil.lib)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/debug/PolyVoxUtil/lib/PolyVoxUtil.lib ${CURRENT_PACKAGES_DIR}/debug/lib/PolyVoxUtil.lib)
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
+        file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxCore/lib/Release/PolyVoxCore.lib ${CURRENT_PACKAGES_DIR}/lib/PolyVoxCore.lib)
+        file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxUtil/lib/PolyVoxUtil.lib ${CURRENT_PACKAGES_DIR}/lib/PolyVoxUtil.lib)
+    endif()
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
+        file(RENAME ${CURRENT_PACKAGES_DIR}/debug/PolyVoxCore/lib/Debug/PolyVoxCore.lib ${CURRENT_PACKAGES_DIR}/debug/lib/PolyVoxCore.lib)
+        file(RENAME ${CURRENT_PACKAGES_DIR}/debug/PolyVoxUtil/lib/PolyVoxUtil.lib ${CURRENT_PACKAGES_DIR}/debug/lib/PolyVoxUtil.lib)
+    endif()
     file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxCore/include/PolyVoxCore ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore)
     file(RENAME ${CURRENT_PACKAGES_DIR}/PolyVoxUtil/include/PolyVoxUtil ${CURRENT_PACKAGES_DIR}/include/PolyVoxUtil)
     file(RENAME ${CURRENT_PACKAGES_DIR}/cmake/PolyVoxConfig.cmake ${CURRENT_PACKAGES_DIR}/share/polyvox/polyvoxConfig.cmake)
     file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/cmake)
-    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/cmake)
     file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/PolyVoxCore)
     file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/PolyVoxUtil)
-    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/PolyVoxUtil)
+    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/cmake)
     file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/PolyVoxCore)
+    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/PolyVoxUtil)
 else()
-    file(GLOB LIBS ${CURRENT_PACKAGES_DIR}/lib/Release/*)
-    foreach(_lib ${LIBS})
-        file(RELATIVE_PATH _libName ${CURRENT_PACKAGES_DIR}/lib/Release ${_lib})
-        file(RENAME ${_lib} ${CURRENT_PACKAGES_DIR}/lib/${_libName})
-    endforeach()
-    file(GLOB LIBS ${CURRENT_PACKAGES_DIR}/debug/lib/Debug/*)
-    foreach(_lib ${LIBS})
-        file(RELATIVE_PATH _libName ${CURRENT_PACKAGES_DIR}/debug/lib/Debug ${_lib})
-        file(RENAME ${_lib} ${CURRENT_PACKAGES_DIR}/debug/lib/${_libName})
-    endforeach()
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
+        file(GLOB LIBS ${CURRENT_PACKAGES_DIR}/lib/Release/*)
+        foreach(_lib ${LIBS})
+            file(RELATIVE_PATH _libName ${CURRENT_PACKAGES_DIR}/lib/Release ${_lib})
+            file(RENAME ${_lib} ${CURRENT_PACKAGES_DIR}/lib/${_libName})
+        endforeach()
+    endif()
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
+        file(GLOB LIBS ${CURRENT_PACKAGES_DIR}/debug/lib/Debug/*)
+        foreach(_lib ${LIBS})
+            file(RELATIVE_PATH _libName ${CURRENT_PACKAGES_DIR}/debug/lib/Debug ${_lib})
+            file(RENAME ${_lib} ${CURRENT_PACKAGES_DIR}/debug/lib/${_libName})
+        endforeach()
+    endif()
     file(RENAME ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore.temp)
     file(RENAME ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore.temp/PolyVoxCore ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore)
     file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/include/PolyVoxCore.temp)
diff --git a/cmake/ports/quazip/portfile.cmake b/cmake/ports/quazip/portfile.cmake
index 07a8ebc3c3..0789062892 100644
--- a/cmake/ports/quazip/portfile.cmake
+++ b/cmake/ports/quazip/portfile.cmake
@@ -1,10 +1,11 @@
 include(vcpkg_common_functions)
 
 file(READ "${VCPKG_ROOT_DIR}/_env/QT_CMAKE_PREFIX_PATH.txt" QT_CMAKE_PREFIX_PATH)
+file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
 
 vcpkg_download_distfile(
     SOURCE_ARCHIVE
-    URLS https://athena-public.s3.amazonaws.com/dependencies/quazip-0.7.3.zip
+    URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/quazip-0.7.3.zip
     SHA512 b2d812b6346317fd6d8f4f1344ad48b721d697c429acc8b7e7cb776ce5cba15a59efd64b2c5ae1f31b5a3c928014f084aa1379fd55d8a452a6cf4fd510b3afcc
     FILENAME quazip.zip
 )
@@ -24,10 +25,14 @@ vcpkg_configure_cmake(
 vcpkg_install_cmake()
 
 if (WIN32)
-    file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/bin)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/lib/quazip5.dll ${CURRENT_PACKAGES_DIR}/bin/quazip5.dll)
-    file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/bin)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/quazip5d.dll ${CURRENT_PACKAGES_DIR}/debug/bin/quazip5.dll)
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
+        file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/bin)
+        file(RENAME ${CURRENT_PACKAGES_DIR}/lib/quazip5.dll ${CURRENT_PACKAGES_DIR}/bin/quazip5.dll)
+    endif()
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
+        file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/bin)
+        file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/quazip5d.dll ${CURRENT_PACKAGES_DIR}/debug/bin/quazip5.dll)
+    endif()
 elseif(DEFINED VCPKG_TARGET_IS_LINUX)
     # We only want static libs.
     file(GLOB QUAZIP5_DYNAMIC_LIBS ${CURRENT_PACKAGES_DIR}/lib/libquazip5.so* ${CURRENT_PACKAGES_DIR}/debug/lib/libquazip5d.so*)
diff --git a/cmake/ports/tbb/CONTROL b/cmake/ports/tbb/CONTROL
index da29e48794..73569fe661 100644
--- a/cmake/ports/tbb/CONTROL
+++ b/cmake/ports/tbb/CONTROL
@@ -1,4 +1,4 @@
 Source: tbb
 Version: 2019_U8-1
-Homepage: https://github.com/01org/tbb
+Homepage: https://github.com/oneapi-src/oneTBB
 Description: Intel's Threading Building Blocks.
diff --git a/cmake/ports/vhacd/portfile.cmake b/cmake/ports/vhacd/portfile.cmake
index 02d90cab18..4c74bb6532 100644
--- a/cmake/ports/vhacd/portfile.cmake
+++ b/cmake/ports/vhacd/portfile.cmake
@@ -1,10 +1,12 @@
 include(vcpkg_common_functions)
 vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
 
+file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
+
 # else Linux desktop
 vcpkg_download_distfile(
     SOURCE_ARCHIVE
-    URLS https://athena-public.s3.amazonaws.com/dependencies/v-hacd-master.zip
+    URLS ${EXTERNAL_BUILD_ASSETS}/dependencies/v-hacd-master.zip
     SHA512 5d9bd4872ead9eb3574e4806d6c4f490353a04036fd5f571e1e44f47cb66b709e311abcd53af30bae0015a690152170aeed93209a626c28ebcfd6591f3bb036f
     FILENAME vhacd.zip
 )
@@ -22,10 +24,17 @@ vcpkg_configure_cmake(
 vcpkg_install_cmake()
 
 file(COPY ${CMAKE_CURRENT_LIST_DIR}/copyright DESTINATION ${CURRENT_PACKAGES_DIR}/share/vhacd)
-file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
+if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
+    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
+endif()
+
 if (WIN32)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/lib/Release/VHACD_LIB.lib ${CURRENT_PACKAGES_DIR}/lib/VHACD.lib)
-    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/Release)
-    file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/Debug/VHACD_LIB.lib ${CURRENT_PACKAGES_DIR}/debug/lib/VHACD.lib)
-    file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/Debug)
-endif()
\ No newline at end of file
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
+        file(RENAME ${CURRENT_PACKAGES_DIR}/lib/Release/VHACD_LIB.lib ${CURRENT_PACKAGES_DIR}/lib/VHACD.lib)
+        file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/Release)
+    endif()
+    if (NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
+        file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/Debug/VHACD_LIB.lib ${CURRENT_PACKAGES_DIR}/debug/lib/VHACD.lib)
+        file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/lib/Debug)
+    endif()
+endif()
diff --git a/cmake/ports/zlib/CONTROL b/cmake/ports/zlib/CONTROL
index aa7c7b6e92..601fb1bc0e 100644
--- a/cmake/ports/zlib/CONTROL
+++ b/cmake/ports/zlib/CONTROL
@@ -1,4 +1,4 @@
 Source: zlib
-Version: 1.2.11-5
+Version: 1.2.11-6
 Homepage: https://www.zlib.net/
 Description: A compression library
diff --git a/cmake/ports/zlib/cmake_dont_build_more_than_needed.patch b/cmake/ports/zlib/cmake_dont_build_more_than_needed.patch
index 229a2d055b..a374f76d62 100644
--- a/cmake/ports/zlib/cmake_dont_build_more_than_needed.patch
+++ b/cmake/ports/zlib/cmake_dont_build_more_than_needed.patch
@@ -1,5 +1,5 @@
 diff --git a/CMakeLists.txt b/CMakeLists.txt
-index 0fe939d..8d2f5f1 100644
+index 0fe939d..a1291d5 100644
 --- a/CMakeLists.txt
 +++ b/CMakeLists.txt
 @@ -7,6 +7,7 @@ set(VERSION "1.2.11")
@@ -10,24 +10,56 @@ index 0fe939d..8d2f5f1 100644
  
  set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables")
  set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries")
-@@ -211,7 +212,15 @@ elseif(BUILD_SHARED_LIBS AND WIN32)
+@@ -124,9 +125,11 @@ set(ZLIB_SRCS
+ )
+ 
+ if(NOT MINGW)
+-    set(ZLIB_DLL_SRCS
+-        win32/zlib1.rc # If present will override custom build rule below.
+-    )
++    if(BUILD_SHARED_LIBS)
++        set(ZLIB_DLL_SRCS
++            win32/zlib1.rc # If present will override custom build rule below.
++        )
++    endif()
+ endif()
+ 
+ if(CMAKE_COMPILER_IS_GNUCC)
+@@ -180,11 +183,12 @@ if(MINGW)
+                             -I ${CMAKE_CURRENT_BINARY_DIR}
+                             -o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj
+                             -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc)
+-    set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj)
++    if(BUILD_SHARED_LIBS)
++        set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj)
++    endif()
+ endif(MINGW)
+ 
+-add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
+-add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
++add_library(zlib ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
+ set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)
+ set_target_properties(zlib PROPERTIES SOVERSION 1)
+ 
+@@ -201,7 +205,7 @@ endif()
+ 
+ if(UNIX)
+     # On unix-like platforms the library is almost always called libz
+-   set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z)
++   set_target_properties(zlib PROPERTIES OUTPUT_NAME z)
+    if(NOT APPLE)
+      set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"")
+    endif()
+@@ -211,7 +215,7 @@ elseif(BUILD_SHARED_LIBS AND WIN32)
  endif()
  
  if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL )
 -    install(TARGETS zlib zlibstatic
-+    if (BUILD_SHARED_LIBS)
-+        set(ZLIB_TARGETS zlib)
-+        set_target_properties(zlibstatic PROPERTIES EXCLUDE_FROM_ALL ON)
-+    else()
-+        set(ZLIB_TARGETS zlibstatic)
-+        set_target_properties(zlib PROPERTIES EXCLUDE_FROM_ALL ON)
-+    endif()
-+
-+    install(TARGETS ${ZLIB_TARGETS}
++    install(TARGETS zlib
          RUNTIME DESTINATION "${INSTALL_BIN_DIR}"
          ARCHIVE DESTINATION "${INSTALL_LIB_DIR}"
          LIBRARY DESTINATION "${INSTALL_LIB_DIR}" )
-@@ -230,6 +239,7 @@ endif()
+@@ -230,6 +234,7 @@ endif()
  # Example binaries
  #============================================================================
  
@@ -35,7 +67,7 @@ index 0fe939d..8d2f5f1 100644
  add_executable(example test/example.c)
  target_link_libraries(example zlib)
  add_test(example example)
-@@ -247,3 +257,4 @@ if(HAVE_OFF64_T)
+@@ -247,3 +252,4 @@ if(HAVE_OFF64_T)
      target_link_libraries(minigzip64 zlib)
      set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64")
  endif()
diff --git a/hifi_qt.py b/hifi_qt.py
index 6cae3bf59d..10708e4bc9 100644
--- a/hifi_qt.py
+++ b/hifi_qt.py
@@ -29,7 +29,7 @@ endif()
         self.configFilePath = os.path.join(args.build_root, 'qt.cmake')
         self.version = os.getenv('VIRCADIA_USE_QT_VERSION', '5.12.3')
 
-        self.assets_url = self.readVar('EXTERNAL_BUILD_ASSETS')
+        self.assets_url = hifi_utils.readEnviromentVariableFromFile(args.build_root, 'EXTERNAL_BUILD_ASSETS')
 
         defaultBasePath = os.path.expanduser('~/hifi/qt')
         self.basePath = os.getenv('HIFI_QT_BASE', defaultBasePath)
@@ -74,10 +74,10 @@ endif()
                     self.qtUrl = self.assets_url + '/dependencies/vcpkg/qt5-install-5.12.6-ubuntu-19.10.tar.xz'
                 elif u_major > 18 and ( u_major != 19 and u_minor != 4):
                     print("We don't support " + distro.name(pretty=True) + " yet. Perhaps consider helping us out?")
-                    raise Exception('UNSUPPORTED LINUX VERSION!!!')
+                    raise Exception('LINUX DISTRO IS NOT SUPPORTED YET!!!')
                 else:
                     print("Sorry, " + distro.name(pretty=True) + " is old and won't be officially supported. Please consider upgrading.");
-                    raise Exception('UNSUPPORTED LINUX VERSION!!!')
+                    raise Exception('UNKNOWN LINUX DISTRO VERSION!!!')
             else:
                 print("Sorry, " + distro.name(pretty=True) + " is not supported. Please consider helping us out.")
                 print("It's also possible to build Qt for your distribution, please see the documentation at:")
@@ -89,10 +89,6 @@ endif()
             print("Machine     : " + platform.machine())
             raise Exception('UNKNOWN OPERATING SYSTEM!!!')
 
-    def readVar(self, var):
-        with open(os.path.join(self.args.build_root, '_env', var + ".txt")) as fp:
-            return fp.read()
-
     def writeConfig(self):
         print("Writing cmake config to {}".format(self.configFilePath))
         # Write out the configuration for use by CMake
diff --git a/hifi_utils.py b/hifi_utils.py
index 3a49f6d52b..157e5858a8 100644
--- a/hifi_utils.py
+++ b/hifi_utils.py
@@ -121,3 +121,7 @@ def downloadAndExtract(url, destPath, hash=None, hasher=hashlib.sha512(), isZip=
         with tarfile.open(tempFileName, 'r:*') as tgz:
             tgz.extractall(destPath)
     os.remove(tempFileName)
+
+def readEnviromentVariableFromFile(buildRootDir, var):
+    with open(os.path.join(buildRootDir, '_env', var + ".txt")) as fp:
+        return fp.read()
diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py
index 9846d386c4..1b9976da6e 100644
--- a/hifi_vcpkg.py
+++ b/hifi_vcpkg.py
@@ -21,6 +21,7 @@ get_filename_component(CMAKE_TOOLCHAIN_FILE "{}" ABSOLUTE CACHE)
 get_filename_component(CMAKE_TOOLCHAIN_FILE_UNCACHED "{}" ABSOLUTE)
 set(VCPKG_INSTALL_ROOT "{}")
 set(VCPKG_TOOLS_DIR "{}")
+set(VCPKG_TARGET_TRIPLET "{}")
 """
 
     CMAKE_TEMPLATE_NON_ANDROID = """
@@ -34,7 +35,11 @@ endif()
         self.args = args
         # our custom ports, relative to the script location
         self.sourcePortsPath = args.ports_path
-        self.id = hifi_utils.hashFolder(self.sourcePortsPath)[:8]
+        self.vcpkgBuildType = args.vcpkg_build_type
+        if (self.vcpkgBuildType):
+            self.id = hifi_utils.hashFolder(self.sourcePortsPath)[:8] + "-" + self.vcpkgBuildType
+        else:
+            self.id = hifi_utils.hashFolder(self.sourcePortsPath)[:8]
         self.configFilePath = os.path.join(args.build_root, 'vcpkg.cmake')
         self.assets_url = self.readVar('EXTERNAL_BUILD_ASSETS')
 
@@ -78,27 +83,31 @@ endif()
         self.bootstrapEnv = os.environ.copy()
         self.buildEnv = os.environ.copy()
         self.prebuiltArchive = None
-        usePrebuilt = ('CI_BUILD' in os.environ) and os.environ["CI_BUILD"] == "Github" and (not self.noClean)
+        usePrebuilt = False
+        # usePrebuild Disabled, to re-enabled using the prebuilt archives for GitHub action builds uncomment the following line:
+        # usePrebuilt = ('CI_BUILD' in os.environ) and os.environ["CI_BUILD"] == "Github" and (not self.noClean)
 
         if 'Windows' == system:
             self.exe = os.path.join(self.path, 'vcpkg.exe')
-            self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat') ]
+            self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.bat'), '-disableMetrics' ]
             self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-win32-client.zip%3FversionId=tSFzbw01VkkVFeRQ6YuAY4dro2HxJR9U'
             self.vcpkgHash = 'a650db47a63ccdc9904b68ddd16af74772e7e78170b513ea8de5a3b47d032751a3b73dcc7526d88bcb500753ea3dd9880639ca842bb176e2bddb1710f9a58cd3'
             self.hostTriplet = 'x64-windows'
             if usePrebuilt:
-               self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx"
+                self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-win32.zip%3FversionId=3SF3mDC8dkQH1JP041m88xnYmWNzZflx"
         elif 'Darwin' == system:
             self.exe = os.path.join(self.path, 'vcpkg')
-            self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.sh'), '--allowAppleClang' ]
+            self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.sh'), '--allowAppleClang', '-disableMetrics' ]
             self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-osx-client.tgz%3FversionId=j0b4azo_zTlH_Q9DElEWOz1UMYZ2nqQw'
             self.vcpkgHash = '519d666d02ef22b87c793f016ca412e70f92e1d55953c8f9bd4ee40f6d9f78c1df01a6ee293907718f3bbf24075cc35492fb216326dfc50712a95858e9cbcb4d'
             self.hostTriplet = 'x64-osx'
+            # Potential fix for a vcpkg build issue on OSX (see https://github.com/microsoft/vcpkg/issues/9029)
+            self.bootstrapEnv['CXXFLAGS'] = '-D_CTERMID_H_'
             if usePrebuilt:
                 self.prebuiltArchive = self.assets_url + "/dependencies/vcpkg/builds/vcpkg-osx.tgz%3FversionId=6JrIMTdvpBF3MAsjA92BMkO79Psjzs6Z"
         else:
             self.exe = os.path.join(self.path, 'vcpkg')
-            self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.sh') ]
+            self.bootstrapCmds = [ os.path.join(self.path, 'bootstrap-vcpkg.sh'), '-disableMetrics' ]
             self.vcpkgUrl = self.assets_url + '/dependencies/vcpkg/builds/vcpkg-linux-client.tgz%3FversionId=y7mct0gFicEXz5hJy3KROBugcLR56YWf'
             self.vcpkgHash = '6a1ce47ef6621e699a4627e8821ad32528c82fce62a6939d35b205da2d299aaa405b5f392df4a9e5343dd6a296516e341105fbb2dd8b48864781d129d7fba10d'
             self.hostTriplet = 'x64-linux'
@@ -206,6 +215,19 @@ endif()
         print(actualCommands)
         hifi_utils.executeSubprocess(actualCommands, folder=self.path, env=self.buildEnv)
 
+    def copyTripletForBuildType(self, triplet):
+        print('Copying triplet ' + triplet + ' to have build type ' + self.vcpkgBuildType)
+        tripletPath = os.path.join(self.path, 'triplets', triplet + '.cmake')
+        tripletForBuildTypePath = os.path.join(self.path, 'triplets', self.getTripletWithBuildType(triplet) + '.cmake')
+        shutil.copy(tripletPath, tripletForBuildTypePath)
+        with open(tripletForBuildTypePath, "a") as tripletForBuildTypeFile:
+            tripletForBuildTypeFile.write("set(VCPKG_BUILD_TYPE " + self.vcpkgBuildType + ")\n")
+
+    def getTripletWithBuildType(self, triplet):
+        if (not self.vcpkgBuildType):
+            return triplet
+        return triplet + '-' + self.vcpkgBuildType
+
     def setupDependencies(self, qt=None):
         if self.prebuiltArchive:
             if not os.path.isfile(self.prebuildTagFile):
@@ -224,12 +246,16 @@ endif()
             self.setupAndroidDependencies()
 
         print("Installing host tools")
-        self.run(['install', '--triplet', self.hostTriplet, 'hifi-host-tools'])
+        if (self.vcpkgBuildType):
+            self.copyTripletForBuildType(self.hostTriplet)
+        self.run(['install', '--triplet', self.getTripletWithBuildType(self.hostTriplet), 'hifi-host-tools'])
 
         # If not android, install the hifi-client-deps libraries
         if not self.args.android:
             print("Installing build dependencies")
-            self.run(['install', '--triplet', self.triplet, 'hifi-client-deps'])
+            if (self.vcpkgBuildType):
+                self.copyTripletForBuildType(self.triplet)
+            self.run(['install', '--triplet', self.getTripletWithBuildType(self.triplet), 'hifi-client-deps'])
 
     def cleanBuilds(self):
         if self.noClean:
@@ -240,6 +266,12 @@ endif()
             print("Wiping build trees")
             shutil.rmtree(builddir, ignore_errors=True)
 
+    # Removes large files used to build the vcpkg, for CI purposes.
+    def cleanupDevelopmentFiles(self):
+        shutil.rmtree(os.path.join(self.path, "downloads"), ignore_errors=True)
+        shutil.rmtree(os.path.join(self.path, "packages"), ignore_errors=True)
+
+
     def setupAndroidDependencies(self):
         # vcpkg prebuilt
         if not os.path.isdir(os.path.join(self.path, 'installed', 'arm64-android')):
@@ -276,12 +308,32 @@ endif()
         with open(self.prebuildTagFile, 'w') as f:
             f.write(self.tagContents)
 
+    def fixupCmakeScript(self):
+        cmakeScript = os.path.join(self.path, 'scripts/buildsystems/vcpkg.cmake')
+        newCmakeScript = cmakeScript + '.new'
+        isFileChanged = False
+        removalPrefix = "set(VCPKG_TARGET_TRIPLET "
+        # Open original file in read only mode and dummy file in write mode
+        with open(cmakeScript, 'r') as read_obj, open(newCmakeScript, 'w') as write_obj:
+            # Line by line copy data from original file to dummy file
+            for line in read_obj:
+                if not line.startswith(removalPrefix):
+                    write_obj.write(line)
+                else:
+                    isFileChanged = True
+     
+        if isFileChanged:
+            shutil.move(newCmakeScript, cmakeScript)
+        else:
+            os.remove(newCmakeScript)
+ 
+
     def writeConfig(self):
         print("Writing cmake config to {}".format(self.configFilePath))
         # Write out the configuration for use by CMake
         cmakeScript = os.path.join(self.path, 'scripts/buildsystems/vcpkg.cmake')
-        installPath = os.path.join(self.path, 'installed', self.triplet)
-        toolsPath = os.path.join(self.path, 'installed', self.hostTriplet, 'tools')
+        installPath = os.path.join(self.path, 'installed', self.getTripletWithBuildType(self.triplet))
+        toolsPath = os.path.join(self.path, 'installed', self.getTripletWithBuildType(self.hostTriplet), 'tools')
 
         cmakeTemplate = VcpkgRepo.CMAKE_TEMPLATE
         if self.args.android:
@@ -289,7 +341,7 @@ endif()
             cmakeTemplate += 'set(HIFI_ANDROID_PRECOMPILED "{}")\n'.format(precompiled)
         else:
             cmakeTemplate += VcpkgRepo.CMAKE_TEMPLATE_NON_ANDROID
-        cmakeConfig = cmakeTemplate.format(cmakeScript, cmakeScript, installPath, toolsPath).replace('\\', '/')
+        cmakeConfig = cmakeTemplate.format(cmakeScript, cmakeScript, installPath, toolsPath, self.getTripletWithBuildType(self.hostTriplet)).replace('\\', '/')
         with open(self.configFilePath, 'w') as f:
             f.write(cmakeConfig)
 
diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
index 74d1b53cd9..ac09f14d9e 100644
--- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
+++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
@@ -7,7 +7,7 @@ MessageBox {
         popup.onButton2Clicked = callback;
         popup.titleText = 'Specify Avatar URL'
         popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.<br>' +
-                '<a href="https://docs.projectathena.dev/create/avatars/create-avatars.html">' +
+                '<a href="https://docs.vircadia.dev/create/avatars.html">' +
                 'Learn to make a custom avatar by opening this link on your desktop.' +
                 '</a>'
         popup.inputText.visible = true;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 341a403f40..2a83ab69c1 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -154,12 +154,12 @@ class MyAvatar : public Avatar {
      *     <p><strong>Note:</strong> This property will automatically be set to <code>true</code> if the controller system has
      *     valid facial blend shape actions.</p>
      * @property {boolean} hasProceduralBlinkFaceMovement=true - <code>true</code> if avatars blink automatically by animating
-     *     facial blend shapes, <code>false</code> if automatic blinking is disabled. Set this property to <code>false</code>
-     *     to fully control the blink facial blend shapes via the {@link MyAvatar.setBlendshape} method.
+     *     facial blend shapes, <code>false</code> if automatic blinking is disabled. Set to <code>false</code> to fully control 
+     *     the blink facial blend shapes via the {@link MyAvatar.setBlendshape} method.
      * @property {boolean} hasProceduralEyeFaceMovement=true - <code>true</code> if the facial blend shapes for an avatar's eyes
      *     adjust automatically as the eyes move, <code>false</code> if this automatic movement is disabled. Set this property
-     *     to <code>true</code> to prevent the iris from being obscured by the upper or lower lids. Set this property to
-     *     <code>false</code> to fully control the eye blend shapes via the {@link MyAvatar.setBlendshape} method.
+     *     to <code>true</code> to prevent the iris from being obscured by the upper or lower lids. Set to <code>false</code> to 
+     *     fully control the eye blend shapes via the {@link MyAvatar.setBlendshape} method.
      * @property {boolean} hasAudioEnabledFaceMovement=true - <code>true</code> if the avatar's mouth blend shapes animate
      *     automatically based on detected microphone input, <code>false</code> if this automatic movement is disabled. Set
      *     this property to <code>false</code> to fully control the mouth facial blend shapes via the
@@ -654,7 +654,7 @@ public:
      * <p>Note: When using pre-built animation data, it's critical that the joint orientation of the source animation and target 
      * rig are equivalent, since the animation data applies absolute values onto the joints. If the orientations are different, 
      * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see 
-     * <a href="https://docs.projectathena.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
      * @function MyAvatar.overrideAnimation
      * @param {string} url - The URL to the animation file. Animation files may be in glTF or FBX format, but only need to 
      *     contain the avatar skeleton and animation data. glTF models may be in JSON or binary format (".gltf" or ".glb" URLs 
@@ -665,7 +665,7 @@ public:
      * @param {number} firstFrame - The frame to start the animation at.
      * @param {number} lastFrame - The frame to end the animation at.
      * @example <caption> Play a clapping animation on your avatar for three seconds. </caption>
-     * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
+     * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx";
      * MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53);
      * Script.setTimeout(function () {
      *     MyAvatar.restoreAnimation();
@@ -688,7 +688,7 @@ public:
      * @param {number} firstFrame - The frame to start the animation at.
      * @param {number} lastFrame - The frame to end the animation at.
      * @example <caption> Override left hand animation for three seconds.</caption>
-     * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
+     * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx";
      * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53);
      * Script.setTimeout(function () {
      *     MyAvatar.restoreHandAnimation();
@@ -705,7 +705,7 @@ public:
      * animation, this function has no effect.</p>
      * @function MyAvatar.restoreAnimation
      * @example <caption> Play a clapping animation on your avatar for three seconds. </caption>
-     * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
+     * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx";
      * MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53);
      * Script.setTimeout(function () {
      *     MyAvatar.restoreAnimation();
@@ -722,7 +722,7 @@ public:
      * @function MyAvatar.restoreHandAnimation
      * @param isLeft {boolean} Set to true if using the left hand
      * @example <caption> Override left hand animation for three seconds. </caption>
-     * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
+     * var ANIM_URL = "https://apidocs.projectathena.dev/models/ClapHands_Standing.fbx";
      * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53);
      * Script.setTimeout(function () {
      *     MyAvatar.restoreHandAnimation();
@@ -780,7 +780,7 @@ public:
      * hanging at its sides when it is not moving, the avatar will stand and clap its hands. Note that just as it did before, as soon as the avatar
      * starts to move, the animation will smoothly blend into the walk animation used by the "walkFwd" animation role.</caption>
      * // An animation of the avatar clapping its hands while standing. Restore default after 30s.
-     * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
+     * var ANIM_URL = "https://apidocs.projectathena.dev/models/ClapHands_Standing.fbx";
      * MyAvatar.overrideRoleAnimation("idleStand", ANIM_URL, 30, true, 0, 53);
      * Script.setTimeout(function () {
      *     MyAvatar.restoreRoleAnimation();
@@ -1793,15 +1793,15 @@ public:
 
     /**jsdoc
      * Turns the avatar's head until it faces the target point within a +90/-90 degree range.
-     * Once this method is called, API calls will have full control of the head for a limited time.
-     * If this method is not called for 2 seconds, the engine will regain control of the head.
+     * Once this method is called, API calls have full control of the head for a limited time.
+     * If this method is not called for 2 seconds, the engine regains control of the head.
      * @function MyAvatar.setHeadLookAt
      * @param {Vec3} lookAtTarget - The target point in world coordinates.
      */
     Q_INVOKABLE void setHeadLookAt(const glm::vec3& lookAtTarget);
 
     /**jsdoc
-     * Returns the current target point of the head's look direction in world coordinates.
+     * Gets the current target point of the head's look direction in world coordinates.
      * @function MyAvatar.getHeadLookAt
      * @returns {Vec3} The head's look-at target in world coordinates.
      */
@@ -1815,7 +1815,7 @@ public:
 
     /**jsdoc
      * Forces the avatar's eyes to look at a specified location. Once this method is called, API calls
-     * have full control of the eyes for a limited time. If this method is not called for two seconds, 
+     * full control of the eyes for a limited time. If this method is not called for 2 seconds, 
      * the engine regains control of the eyes.
      * @function MyAvatar.setEyesLookAt
      * @param {Vec3} lookAtTarget - The target point in world coordinates.
@@ -1823,7 +1823,7 @@ public:
     Q_INVOKABLE void setEyesLookAt(const glm::vec3& lookAtTarget);
 
     /**jsdoc
-     * Returns the current target point of the eyes look direction in world coordinates.
+     * Gets the current target point of the eyes look direction in world coordinates.
      * @function MyAvatar.getEyesLookAt
      * @returns {Vec3} The eyes' look-at target in world coordinates.
      */
@@ -2051,7 +2051,8 @@ public slots:
     float getGravity();
 
     /**jsdoc
-     * Moves the avatar to a new position and/or orientation in the domain, while taking into account Avatar leg-length.
+     * Moves the avatar to a new position and/or orientation in the domain, with safe landing, while taking into account avatar 
+     * leg length.
      * @function MyAvatar.goToFeetLocation
      * @param {Vec3} position - The new position for the avatar, in world coordinates.
      * @param {boolean} [hasOrientation=false] - Set to <code>true</code> to set the orientation of the avatar.
@@ -2076,14 +2077,14 @@ public slots:
                       bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
                       bool shouldFaceLocation = false, bool withSafeLanding = true);
     /**jsdoc
-     * Moves the avatar to a new position and (optional) orientation in the domain.
+     * Moves the avatar to a new position and (optional) orientation in the domain, with safe landing.
      * @function MyAvatar.goToLocation
      * @param {MyAvatar.GoToProperties} target - The goto target.
      */
     void goToLocation(const QVariant& properties);
 
     /**jsdoc
-     * Moves the avatar to a new position and then enables collisions.
+     * Moves the avatar to a new position, with safe landing, and enables collisions.
      * @function MyAvatar.goToLocationAndEnableCollisions
      * @param {Vec3} position - The new position for the avatar, in world coordinates.
      */
diff --git a/interface/src/graphics/WorldBox.h b/interface/src/graphics/WorldBox.h
index 114777ba0f..4d53652c0e 100644
--- a/interface/src/graphics/WorldBox.h
+++ b/interface/src/graphics/WorldBox.h
@@ -19,8 +19,6 @@
 #include <GeometryCache.h>
 #include "Menu.h"
 
-
-
 class WorldBoxRenderData {
 public:
     typedef render::Payload<WorldBoxRenderData> Payload;
@@ -29,8 +27,6 @@ public:
     int _val = 0;
     static render::ItemID _item; // unique WorldBoxRenderData
 
-
-
     static void renderWorldBox(RenderArgs* args, gpu::Batch& batch);
 };
 
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index bf39073db1..0c2fdd1614 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -325,7 +325,7 @@ public slots:
      * @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is <code>0</code>, the
      *     full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the
      *     dimensions is adjusted in order to match the aspect ratio.
-     * @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-&lt;user 
+     * @param {string} [filename=""] - If a filename is not provided, the image is saved as "vircadia-snap-by-&lt;user 
      *     name&gt;-on-YYYY-MM-DD_HH-MM-SS".
      *     <p>Still images are saved in JPEG or PNG format according to the extension provided &mdash; <code>".jpg"</code>, 
      *     <code>".jpeg"</code>, or <code>".png"</code> &mdash; or if not provided then in JPEG format with an extension of 
@@ -364,7 +364,7 @@ public slots:
      * @function Window.takeSecondaryCameraSnapshot
      * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
      *     signal.
-     * @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-&lt;user
+     * @param {string} [filename=""] - If a filename is not provided, the image is saved as "vircadia-snap-by-&lt;user
      *     name&gt;-on-YYYY-MM-DD_HH-MM-SS".
      *     <p>Images are saved in JPEG or PNG format according to the extension provided &mdash; <code>".jpg"</code>,
      *     <code>".jpeg"</code>, or <code>".png"</code> &mdash; or if not provided then in JPEG format with an extension of
@@ -383,7 +383,7 @@ public slots:
      *     otherwise it is saved as an equirectangular image.
      * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
      *     signal.
-     * @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-&lt;user
+     * @param {string} [filename=""] - If a filename is not provided, the image is saved as "vircadia-snap-by-&lt;user
      *     name&gt;-on-YYYY-MM-DD_HH-MM-SS".
      *     <p>Images are saved in JPEG or PNG format according to the extension provided &mdash; <code>".jpg"</code>,
      *     <code>".jpeg"</code>, or <code>".png"</code> &mdash; or if not provided then in JPEG format with an extension of
diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp
index 2acac2c529..0ac1f05737 100644
--- a/interface/src/ui/InteractiveWindow.cpp
+++ b/interface/src/ui/InteractiveWindow.cpp
@@ -145,7 +145,7 @@ void InteractiveWindow::emitMainWindowResizeEvent() {
  * @property {InteractiveWindow.Flags} [additionalFlags=0] - Customizes window behavior.
  * @property {InteractiveWindow.OverrideFlags} [overrideFlags=0] - Customizes window controls.
 
-  * @property {InteractiveWindow.RelativePositionAnchor} [relativePositionAnchor] - he anchor for the 
+  * @property {InteractiveWindow.RelativePositionAnchor} [relativePositionAnchor] - The anchor for the 
   *     <code>relativePosition</code>, if used.
   * @property {Vec2} [relativePosition] - The position of the window, relative to the <code>relativePositionAnchor</code>, in 
   *     pixels. Excludes the window frame.
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index bb9971e582..4882d6e5da 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -42,9 +42,9 @@
 #include "Snapshot.h"
 #include "SnapshotUploader.h"
 
-// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
+// filename format: vircadia-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
 // %1 <= username, %2 <= date and time, %3 <= current location
-const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2.jpg";
+const QString FILENAME_PATH_FORMAT = "vircadia-snap-by-%1-on-%2.jpg";
 const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss";
 const QString SNAPSHOTS_DIRECTORY = "Snapshots";
 const QString URL = "highfidelity_url";
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index a9e16e9ff1..3792057052 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -1925,7 +1925,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) {
  * @typedef {object} SkeletonJoint
  * @property {string} name - Joint name.
  * @property {number} index - Joint index.
- * @property {number} parentIndex - Index of this joint's parent (-1 if no parent).
+ * @property {number} parentIndex - Index of this joint's parent (<code>-1</code> if no parent).
  */
 QList<QVariant> Avatar::getSkeleton() {
     SkeletonModelPointer skeletonModel = _skeletonModel;
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
index 9730ddf96c..cd1fc7c7fc 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
@@ -442,7 +442,7 @@ public:
     void setOrientationViaScript(const glm::quat& orientation) override;
 
     /**jsdoc
-     * Gets the ID of the entity of avatar that the avatar is parented to.
+     * Gets the ID of the entity or avatar that the avatar is parented to.
      * @function MyAvatar.getParentID
      * @returns {Uuid} The ID of the entity or avatar that the avatar is parented to. {@link Uuid(0)|Uuid.NULL} if not parented.
      */
@@ -450,7 +450,7 @@ public:
     Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); }
 
     /**jsdoc
-     * Sets the ID of the entity of avatar that the avatar is parented to.
+     * Sets the ID of the entity or avatar that the avatar is parented to.
      * @function MyAvatar.setParentID
      * @param {Uuid} parentID - The ID of the entity or avatar that the avatar should be parented to. Set to 
      *    {@link Uuid(0)|Uuid.NULL} to unparent.
diff --git a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h
index 3f8fc4f88b..0b89e9d59e 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h
@@ -19,7 +19,7 @@
 /**jsdoc
  * Information about an avatar.
  *
- * <p>Created using {@link MyAvatar.getTargetAvatar} or {@link AvatarList.getAvatar}.</p>
+ * <p>Create using {@link MyAvatar.getTargetAvatar} or {@link AvatarList.getAvatar}.</p>
  *
  * @class ScriptAvatar
  * @hideconstructor
@@ -87,7 +87,7 @@ public slots:
     /**jsdoc
      * Gets the default rotation of a joint in the avatar relative to its parent.
      * <p>For information on the joint hierarchy used, see
-     * <a href="https://docs.highfidelity.com/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
      * @function ScriptAvatar.getDefaultJointRotation
      * @param {number} index - The joint index.
      * @returns {Quat} The default rotation of the joint if avatar data are available and the joint index is valid, otherwise 
@@ -99,7 +99,7 @@ public slots:
      * Gets the default translation of a joint in the avatar relative to its parent, in model coordinates.
      * <p><strong>Warning:</strong> These coordinates are not necessarily in meters.</p>
      * <p>For information on the joint hierarchy used, see
-     * <a href="https://docs.highfidelity.com/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
      * @function ScriptAvatar.getDefaultJointTranslation
      * @param {number} index - The joint index.
      * @returns {Vec3} The default translation of the joint (in model coordinates) if avatar data are available and the joint 
@@ -152,7 +152,7 @@ public slots:
 
 
     /**jsdoc
-     * Gets the ID of the entity of avatar that the avatar is parented to.
+     * Gets the ID of the entity or avatar that the avatar is parented to.
      * @function ScriptAvatar.getParentID
      * @returns {Uuid} The ID of the entity or avatar that the avatar is parented to. {@link Uuid(0)|Uuid.NULL} if not parented 
      *     or avatar data aren't available.
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index f5e72b2dfe..d6a4497ee7 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -538,12 +538,12 @@ class AvatarData : public QObject, public SpatiallyNestable {
      *     <p><strong>Note:</strong> This property will automatically be set to <code>true</code> if the controller system has 
      *     valid facial blend shape actions.</p>
      * @property {boolean} hasProceduralBlinkFaceMovement=true - <code>true</code> if avatars blink automatically by animating 
-     *     facial blend shapes, <code>false</code> if automatic blinking is disabled. Set this property to <code>false</code> 
-     *     to fully control the blink facial blend shapes via the {@link Avatar.setBlendshape} method.
+     *     facial blend shapes, <code>false</code> if automatic blinking is disabled. Set to <code>false</code> to fully control 
+     *     the blink facial blend shapes via the {@link Avatar.setBlendshape} method.
      * @property {boolean} hasProceduralEyeFaceMovement=true - <code>true</code> if the facial blend shapes for an avatar's eyes 
      *     adjust automatically as the eyes move, <code>false</code> if this automatic movement is disabled. Set this property 
-     *     to <code>true</code> to prevent the iris from being obscured by the upper or lower lids. Set this property to  
-     *     <code>false</code> to fully control the eye blend shapes via the {@link Avatar.setBlendshape} method.
+     *     to <code>true</code> to prevent the iris from being obscured by the upper or lower lids. Set to <code>false</code> to
+     *     fully control the eye blend shapes via the {@link Avatar.setBlendshape} method.
      * @property {boolean} hasAudioEnabledFaceMovement=true - <code>true</code> if the avatar's mouth blend shapes animate 
      *     automatically based on detected microphone input, <code>false</code> if this automatic movement is disabled. Set 
      *     this property to <code>false</code> to fully control the mouth facial blend shapes via the 
@@ -1274,7 +1274,7 @@ public:
      *     <code>null</code> to remove all attachments.
      * @deprecated This function is deprecated and will be removed. Use avatar entities instead.
      * @example <caption>Remove a hat attachment if your avatar is wearing it.</caption>
-     * var hatURL = "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx";
+     * var hatURL = "https://apidocs.vircadia.dev/models/cowboy-hat.fbx";
      * var attachments = MyAvatar.getAttachmentData();
      *
      * for (var i = 0; i < attachments.length; i++) {
@@ -1311,7 +1311,7 @@ public:
      * @deprecated This function is deprecated and will be removed. Use avatar entities instead.
      * @example <caption>Attach a cowboy hat to your avatar's head.</caption>
      * var attachment = {
-     *     modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx",
+     *     modelURL: "https://apidocs.vircadia.dev/models/cowboy-hat.fbx",
      *     jointName: "Head",
      *     translation: {"x": 0, "y": 0.25, "z": 0},
      *     rotation: {"x": 0, "y": 0, "z": 0, "w": 1},
@@ -1542,7 +1542,7 @@ signals:
     void sessionDisplayNameChanged();
 
     /**jsdoc
-     * Triggered when the avatar's model (i.e., <code>skeletonModelURL</code> property value) is changed.
+     * Triggered when the avatar's model (i.e., <code>skeletonModelURL</code> property value) changes.
      * @function Avatar.skeletonModelURLChanged
      * @returns {Signal}
      * @example <caption>Report when your avatar's skeleton model changes.</caption>
diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h
index 7dcd7cc7e1..290e8c178a 100644
--- a/libraries/avatars/src/ScriptAvatarData.h
+++ b/libraries/avatars/src/ScriptAvatarData.h
@@ -117,7 +117,7 @@ public:
 
     /**jsdoc
      * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see
-     * <a href="https://docs.highfidelity.com/create/avatars/avatar-standards.html">Avatar Standards</a>.
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.
      * @function ScriptAvatar.getJointRotation
      * @param {number} index - The index of the joint.
      * @returns {Quat} The rotation of the joint relative to its parent, or {@link Quat(0)|Quat.IDENTITY} if the avatar data 
@@ -129,7 +129,7 @@ public:
      * Gets the translation of a joint relative to its parent, in model coordinates.
      * <p><strong>Warning:</strong> These coordinates are not necessarily in meters.</p>
      * <p>For information on the joint hierarchy used, see
-     * <a href="https://docs.highfidelity.com/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
      * @function ScriptAvatar.getJointTranslation
      * @param {number} index - The index of the joint.
      * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates, or {@link Vec3(0)|Vec3.ZERO} 
@@ -139,7 +139,7 @@ public:
 
     /**jsdoc
      * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see
-     * <a href="https://docs.highfidelity.com/create/avatars/avatar-standards.html">Avatar Standards</a>.
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.
      * @function ScriptAvatar.getJointRotation
      * @param {string} name - The name of the joint.
      * @returns {Quat} The rotation of the joint relative to its parent, or {@link Quat(0)|Quat.IDENTITY} if the avatar data 
@@ -151,7 +151,7 @@ public:
      * Gets the translation of a joint relative to its parent, in model coordinates.
      * <p><strong>Warning:</strong> These coordinates are not necessarily in meters.</p>
      * <p>For information on the joint hierarchy used, see
-     * <a href="https://docs.highfidelity.com/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
+     * <a href="https://docs.vircadia.dev/create/avatars/avatar-standards.html">Avatar Standards</a>.</p>
      * @function ScriptAvatar.getJointTranslation
      * @param {number} name - The name of the joint.
      * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates, or {@link Vec3(0)|Vec3.ZERO} 
@@ -248,7 +248,7 @@ signals:
     void sessionDisplayNameChanged();
 
     /**jsdoc
-     * Triggered when the avatar's model (i.e., <code>skeletonModelURL</code> property value) is changed.
+     * Triggered when the avatar's model (i.e., <code>skeletonModelURL</code> property value) changes.
      * @function ScriptAvatar.skeletonModelURLChanged
      * @returns {Signal}
      */
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp
index f38a6c23bc..9ca958d128 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp
@@ -129,7 +129,7 @@ std::shared_ptr<T> make_renderer(const EntityItemPointer& entity) {
     return std::shared_ptr<T>(new T(entity), [](T* ptr) { ptr->deleteLater(); });
 }
 
-EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity) {}
+EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity), _entityID(entity->getID()) {}
 
 EntityRenderer::~EntityRenderer() {}
 
@@ -197,6 +197,23 @@ uint32_t EntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) const {
     return 0;
 }
 
+bool EntityRenderer::passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const {
+    auto renderWithZones = resultWithReadLock<QVector<QUuid>>([&] {
+        return _renderWithZones;
+    });
+    if (!renderWithZones.isEmpty()) {
+        if (!containingZones.empty()) {
+            for (auto renderWithZone : renderWithZones) {
+                if (containingZones.find(renderWithZone) != containingZones.end()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    return true;
+}
+
 void EntityRenderer::render(RenderArgs* args) {
     if (!isValidRenderItem()) {
         return;
@@ -386,6 +403,10 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity
         return true;
     }
 
+    if (entity->needsZoneOcclusionUpdate()) {
+        return true;
+    }
+
     return false;
 }
 
@@ -427,6 +448,10 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa
         _canCastShadow = entity->getCanCastShadow();
         setCullWithParent(entity->getCullWithParent());
         _cauterized = entity->getCauterized();
+        if (entity->needsZoneOcclusionUpdate()) {
+            entity->resetNeedsZoneOcclusionUpdate();
+            setRenderWithZones(entity->getRenderWithZones());
+        }
         entity->setNeedsRenderUpdate(false);
     });
 }
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h
index 227eb6a018..9c787d82c2 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableEntityItem.h
@@ -65,6 +65,7 @@ public:
 
     virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override;
     virtual Item::Bound getBound() override;
+    bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const override;
 
 protected:
     virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
@@ -109,6 +110,7 @@ protected:
     virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; }
     virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; }
     virtual void setCullWithParent(bool value) { _cullWithParent = value; }
+    virtual void setRenderWithZones(const QVector<QUuid>& renderWithZones) { _renderWithZones = renderWithZones; }
 
     template <typename F, typename T>
     T withReadLockResult(const std::function<T()>& f) {
@@ -143,6 +145,7 @@ protected:
     bool _cullWithParent { false };
     RenderLayer _renderLayer { RenderLayer::WORLD };
     PrimitiveMode _primitiveMode { PrimitiveMode::SOLID };
+    QVector<QUuid> _renderWithZones;
     bool _cauterized { false };
     bool _moving { false };
     // Only touched on the rendering thread
@@ -153,6 +156,7 @@ protected:
     std::mutex _materialsLock;
 
     quint64 _created;
+    QUuid _entityID;
 
     // The base class relies on comparing the model transform to the entity transform in order 
     // to trigger an update, so the member must not be visible to derived classes as a modifiable
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index ad2737143c..90b5546786 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -1264,6 +1264,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
             setKey(didVisualGeometryRequestSucceed);
             _model->setTagMask(getTagMask());
             _model->setHifiRenderLayer(getHifiRenderLayer());
+            _model->setPrimitiveMode(_primitiveMode);
+            _model->setCullWithParent(_cullWithParent);
+            _model->setRenderWithZones(_renderWithZones);
             emit requestRenderUpdate();
             if(didVisualGeometryRequestSucceed) {
                 emit DependencyManager::get<scriptable::ModelProviderFactory>()->
@@ -1447,6 +1450,13 @@ void ModelEntityRenderer::setCullWithParent(bool value) {
     }
 }
 
+void ModelEntityRenderer::setRenderWithZones(const QVector<QUuid>& renderWithZones) {
+    Parent::setRenderWithZones(renderWithZones);
+    if (_model) {
+        _model->setRenderWithZones(renderWithZones);
+    }
+}
+
 // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items
 void ModelEntityRenderer::doRender(RenderArgs* args) {
     DETAILED_PROFILE_RANGE(render_detail, "MetaModelRender");
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h
index eb8aeefba9..104301a440 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h
@@ -165,6 +165,7 @@ protected:
     void setRenderLayer(RenderLayer value) override;
     void setPrimitiveMode(PrimitiveMode value) override;
     void setCullWithParent(bool value) override;
+    void setRenderWithZones(const QVector<QUuid>& renderWithZones) override;
 
 private:
     void animate(const TypedEntityPointer& entity);
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
index fbd10e2f5b..2ba1acea11 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
@@ -289,6 +289,17 @@ ShapeKey entities::TextPayload::getShapeKey() const {
     return ShapeKey::Builder::invalid();
 }
 
+bool entities::TextPayload::passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const {
+    auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
+    if (entityTreeRenderer) {
+        auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
+        if (renderable) {
+            return std::static_pointer_cast<TextEntityRenderer>(renderable)->passesZoneOcclusionTest(containingZones);
+        }
+    }
+    return false;
+}
+
 void entities::TextPayload::render(RenderArgs* args) {
     PerformanceTimer perfTimer("TextPayload::render");
     Q_ASSERT(args->_batch);
@@ -388,4 +399,12 @@ template <> const ShapeKey shapeGetShapeKey(const TextPayload::Pointer& payload)
 template <> void payloadRender(const TextPayload::Pointer& payload, RenderArgs* args) {
     return payload->render(args);
 }
+
+template <> bool payloadPassesZoneOcclusionTest(const entities::TextPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones) {
+    if (payload) {
+        return payload->passesZoneOcclusionTest(containingZones);
+    }
+    return false;
+}
+
 }
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h
index 63cf3e6e9e..87102daa32 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h
@@ -94,6 +94,7 @@ public:
     Item::Bound getBound() const;
     ShapeKey getShapeKey() const;
     void render(RenderArgs* args);
+    bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const;
 
 protected:
     QUuid _entityID;
@@ -109,6 +110,7 @@ namespace render {
     template <> const Item::Bound payloadGetBound(const entities::TextPayload::Pointer& payload);
     template <> const ShapeKey shapeGetShapeKey(const entities::TextPayload::Pointer& payload);
     template <> void payloadRender(const entities::TextPayload::Pointer& payload, RenderArgs* args);
+    template <> bool payloadPassesZoneOcclusionTest(const entities::TextPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones);
 }
 
 #endif // hifi_RenderableTextEntityItem_h
diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
index bbb8c67ad1..948c6ca762 100644
--- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
@@ -71,6 +71,11 @@ void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity
 }
 
 void ZoneEntityRenderer::doRender(RenderArgs* args) {
+    // This is necessary so that zones can themselves be zone culled
+    if (!passesZoneOcclusionTest(CullTest::_prevContainingZones)) {
+        return;
+    }
+
     if (!_stage) {
         _stage = args->_scene->getStage<LightStage>();
         assert(_stage);
@@ -180,6 +185,8 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
             _bloomStage->_currentFrame.pushBloom(_bloomIndex);
         }
     }
+
+    CullTest::_containingZones.insert(_entityID);
 }
 
 void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf
index 3d5cc190d0..eb46be1e20 100644
--- a/libraries/entities-renderer/src/paintStroke.slf
+++ b/libraries/entities-renderer/src/paintStroke.slf
@@ -19,6 +19,8 @@
 <@include paintStroke.slh@>
 <$declarePolyLineBuffers()$>
 
+<@include CullFace.slh@>
+
 LAYOUT(binding=0) uniform sampler2D _texture;
 
 <@if not HIFI_USE_FORWARD@>
@@ -35,9 +37,9 @@ void main(void) {
 
 <@if not HIFI_USE_FORWARD@>
     <@if HIFI_USE_TRANSLUCENT@>
-        packDeferredFragmentTranslucent((2.0 * float(gl_FrontFacing) - 1.0) * _normalWS, texel.a, texel.rgb, DEFAULT_ROUGHNESS);
+        packDeferredFragmentTranslucent(evalFrontOrBackFaceNormal(_normalWS), texel.a, texel.rgb, DEFAULT_ROUGHNESS);
     <@else@>
-        packDeferredFragmentUnlit((2.0 * float(gl_FrontFacing) - 1.0) * _normalWS, texel.a, texel.rgb);
+        packDeferredFragmentUnlit(evalFrontOrBackFaceNormal(_normalWS), texel.a, texel.rgb);
     <@endif@>
 <@else@>
     _fragColor0 = texel;
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 5fc99882c0..3623e5f96a 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -106,6 +106,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
     requestedProperties += PROP_RENDER_LAYER;
     requestedProperties += PROP_PRIMITIVE_MODE;
     requestedProperties += PROP_IGNORE_PICK_INTERSECTION;
+    requestedProperties += PROP_RENDER_WITH_ZONES;
     requestedProperties += _grabProperties.getEntityProperties(params);
 
     // Physics
@@ -301,6 +302,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
         APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)getRenderLayer());
         APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)getPrimitiveMode());
         APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, getIgnorePickIntersection());
+        APPEND_ENTITY_PROPERTY(PROP_RENDER_WITH_ZONES, getRenderWithZones());
         withReadLock([&] {
             _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
                 propertyFlags, propertiesDidntFit, propertyCount, appendState);
@@ -876,6 +878,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
     READ_ENTITY_PROPERTY(PROP_RENDER_LAYER, RenderLayer, setRenderLayer);
     READ_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode);
     READ_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection);
+    READ_ENTITY_PROPERTY(PROP_RENDER_WITH_ZONES, QVector<QUuid>, setRenderWithZones);
     withWriteLock([&] {
         int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
             propertyFlags, overwriteLocalData,
@@ -1355,6 +1358,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderLayer, getRenderLayer);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(primitiveMode, getPrimitiveMode);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignorePickIntersection, getIgnorePickIntersection);
+    COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderWithZones, getRenderWithZones);
     withReadLock([&] {
         _grabProperties.getProperties(properties);
     });
@@ -1504,6 +1508,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderLayer, setRenderLayer);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(primitiveMode, setPrimitiveMode);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignorePickIntersection, setIgnorePickIntersection);
+    SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderWithZones, setRenderWithZones);
     withWriteLock([&] {
         bool grabPropertiesChanged = _grabProperties.setProperties(properties);
         somethingChanged |= grabPropertiesChanged;
@@ -3564,3 +3569,18 @@ void EntityItem::disableGrab(GrabPointer grab) {
         }
     }
 }
+
+void EntityItem::setRenderWithZones(const QVector<QUuid>& renderWithZones) {
+    withWriteLock([&] {
+        if (_renderWithZones != renderWithZones) {
+            _needsZoneOcclusionUpdate = true;
+            _renderWithZones = renderWithZones;
+        }
+    });
+}
+
+QVector<QUuid> EntityItem::getRenderWithZones() const {
+    return resultWithReadLock<QVector<QUuid>>([&] {
+        return _renderWithZones;
+    });
+}
\ No newline at end of file
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index 5f0f90ad68..2bb5dab16b 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -582,6 +582,11 @@ public:
     bool needsRenderUpdate() const { return resultWithReadLock<bool>([&] { return _needsRenderUpdate; }); }
     void setNeedsRenderUpdate(bool needsRenderUpdate) { withWriteLock([&] { _needsRenderUpdate = needsRenderUpdate; }); }
 
+    void setRenderWithZones(const QVector<QUuid>& renderWithZones);
+    QVector<QUuid> getRenderWithZones() const;
+    bool needsZoneOcclusionUpdate() const { return _needsZoneOcclusionUpdate; }
+    void resetNeedsZoneOcclusionUpdate() { withWriteLock([&] { _needsZoneOcclusionUpdate = false; }); }
+
 signals:
     void spaceUpdate(std::pair<int32_t, glm::vec4> data);
 
@@ -770,6 +775,9 @@ protected:
 
     QHash<QUuid, EntityDynamicPointer> _grabActions;
 
+    QVector<QUuid> _renderWithZones;
+    mutable bool _needsZoneOcclusionUpdate { false };
+
     bool _cullWithParent { false };
 
     mutable bool _needsRenderUpdate { false };
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 09a8021a43..ebc8ae1c59 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -431,6 +431,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
     CHECK_PROPERTY_CHANGE(PROP_RENDER_LAYER, renderLayer);
     CHECK_PROPERTY_CHANGE(PROP_PRIMITIVE_MODE, primitiveMode);
     CHECK_PROPERTY_CHANGE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection);
+    CHECK_PROPERTY_CHANGE(PROP_RENDER_WITH_ZONES, renderWithZones);
     changedProperties += _grab.getChangedProperties();
 
     // Physics
@@ -804,6 +805,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
  *     avatar entities, <code>false</code> if they won't be.
  * @property {Uuid} cloneOriginID - The ID of the entity that this entity was cloned from.
  *
+ * @property {Uuid[]} renderWithZones=[]] - A list of entity IDs representing with which zones this entity should render.
+ *     If it is empty, this entity will render normally.  Otherwise, this entity will only render if your avatar is within
+ *     one of the zones in this list.
+ *
  * @property {Entities.Grab} grab - The entity's grab-related properties.
  *
  * @property {string} itemName="" - Certifiable name of the Marketplace item.
@@ -1036,8 +1041,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
  *     type: "Model",
  *     position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -2 })),
  *     rotation: MyAvatar.orientation,
- *     modelURL: "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj",
- *     dimensions: { x: 0.0945, y: 0.0921, z: 0.0423 },
+ *     modelURL: "https://apidocs.vircadia.dev/models/cowboy-hat.fbx",
+ *     dimensions: { x: 0.8569, y: 0.3960, z: 1.0744 },
  *     lifetime: 300  // Delete after 5 minutes.
  * });
  */
@@ -1596,6 +1601,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
     COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_RENDER_LAYER, renderLayer, getRenderLayerAsString());
     COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PRIMITIVE_MODE, primitiveMode, getPrimitiveModeAsString());
     COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection);
+    COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RENDER_WITH_ZONES, renderWithZones);
     _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
 
     // Physics
@@ -2014,6 +2020,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
     COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(renderLayer, RenderLayer);
     COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(primitiveMode, PrimitiveMode);
     COPY_PROPERTY_FROM_QSCRIPTVALUE(ignorePickIntersection, bool, setIgnorePickIntersection);
+    COPY_PROPERTY_FROM_QSCRIPTVALUE(renderWithZones, qVectorQUuid, setRenderWithZones);
     _grab.copyFromScriptValue(object, _defaultSettings);
 
     // Physics
@@ -2308,6 +2315,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
     COPY_PROPERTY_IF_CHANGED(renderLayer);
     COPY_PROPERTY_IF_CHANGED(primitiveMode);
     COPY_PROPERTY_IF_CHANGED(ignorePickIntersection);
+    COPY_PROPERTY_IF_CHANGED(renderWithZones);
     _grab.merge(other._grab);
 
     // Physics
@@ -2601,6 +2609,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
         ADD_PROPERTY_TO_MAP(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer);
         ADD_PROPERTY_TO_MAP(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode);
         ADD_PROPERTY_TO_MAP(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool);
+        ADD_PROPERTY_TO_MAP(PROP_RENDER_WITH_ZONES, RenderWithZones, renderWithZones, QVector<QUuid>);
         { // Grab
             ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable);
             ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic);
@@ -3090,6 +3099,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
             APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)properties.getRenderLayer());
             APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)properties.getPrimitiveMode());
             APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, properties.getIgnorePickIntersection());
+            APPEND_ENTITY_PROPERTY(PROP_RENDER_WITH_ZONES, properties.getRenderWithZones());
             _staticGrab.setProperties(properties);
             _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags,
                                            propertiesDidntFit, propertyCount, appendState);
@@ -3580,6 +3590,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_LAYER, RenderLayer, setRenderLayer);
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode);
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection);
+    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_WITH_ZONES, QVector<QUuid>, setRenderWithZones);
     properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
 
     // Physics
@@ -3998,6 +4009,7 @@ void EntityItemProperties::markAllChanged() {
     _renderLayerChanged = true;
     _primitiveModeChanged = true;
     _ignorePickIntersectionChanged = true;
+    _renderWithZonesChanged = true;
     _grab.markAllChanged();
 
     // Physics
@@ -4401,6 +4413,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
     if (ignorePickIntersectionChanged()) {
         out += "ignorePickIntersection";
     }
+    if (renderWithZonesChanged()) {
+        out += "renderWithZones";
+    }
     getGrab().listChangedProperties(out);
 
     // Physics
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index 264fc3881b..baad0bcd6f 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -195,6 +195,7 @@ public:
     DEFINE_PROPERTY_REF_ENUM(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer, RenderLayer::WORLD);
     DEFINE_PROPERTY_REF_ENUM(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode, PrimitiveMode::SOLID);
     DEFINE_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool, false);
+    DEFINE_PROPERTY_REF(PROP_RENDER_WITH_ZONES, RenderWithZones, renderWithZones, QVector<QUuid>, QVector<QUuid>());
     DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup);
 
     // Physics
diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h
index 4c989ef74e..c25eb21e6c 100644
--- a/libraries/entities/src/EntityItemPropertiesMacros.h
+++ b/libraries/entities/src/EntityItemPropertiesMacros.h
@@ -119,6 +119,7 @@ inline QScriptValue qVectorVec3Color_convertScriptValue(QScriptEngine* e, const
 inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<glm::quat>& v) {return qVectorQuatToScriptValue(e, v); }
 inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<bool>& v) {return qVectorBoolToScriptValue(e, v); }
 inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<float>& v) { return qVectorFloatToScriptValue(e, v); }
+inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<QUuid>& v) { return qVectorQUuidToScriptValue(e, v); }
 
 inline QScriptValue convertScriptValue(QScriptEngine* e, const QRect& v) { return qRectToScriptValue(e, v); }
 
@@ -216,6 +217,7 @@ typedef QVector<glm::vec3> qVectorVec3;
 typedef QVector<glm::quat> qVectorQuat;
 typedef QVector<bool> qVectorBool;
 typedef QVector<float> qVectorFloat;
+typedef QVector<QUuid> qVectorQUuid;
 inline float float_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); }
 inline quint64 quint64_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); }
 inline quint32 quint32_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
@@ -293,6 +295,11 @@ inline qVectorBool qVectorBool_convertFromScriptValue(const QScriptValue& v, boo
     return qVectorBoolFromScriptValue(v);
 }
 
+inline qVectorQUuid qVectorQUuid_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
+    isValid = true;
+    return qVectorQUuidFromScriptValue(v);
+}
+
 inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
     isValid = false; /// assume it can't be converted
     QScriptValue x = v.property("x");
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index 1ffd8fdc3c..37ea303166 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -43,6 +43,7 @@ enum EntityPropertyList {
     PROP_RENDER_LAYER,
     PROP_PRIMITIVE_MODE,
     PROP_IGNORE_PICK_INTERSECTION,
+    PROP_RENDER_WITH_ZONES,
     // Grab
     PROP_GRAB_GRABBABLE,
     PROP_GRAB_KINEMATIC,
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index 11cd6309da..1c1b3a41f5 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -784,6 +784,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
             }
         }
         if (theEntity->isSimulated()) {
+            theEntity->die();
             _simulation->prepareEntityForDelete(theEntity);
         }
     }
diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
index 9d36cfd91a..a27eff9cbe 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
+++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp
@@ -434,7 +434,7 @@ namespace scriptable {
      * @property {string} [glossMap] - The URL of the gloss texture image.
      *     Only use one of <code>roughnessMap</code> and <code>glossMap</code>.
      *     <code>"hifi_pbr"</code> model only.
-     * @property {string} [normalMa]p - The URL of the normal texture image.
+     * @property {string} [normalMap] - The URL of the normal texture image.
      *     If <code>"fallthrough"</code> then it and <code>bumpMap</code> fall through to the material below.
      *     Only use one of <code>normalMap</code> and <code>bumpMap</code>.
      *     <code>"hifi_pbr"</code> model only.
diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h
index 9b56433bf6..0f24ae8461 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h
@@ -87,8 +87,8 @@ public slots:
      *     type: "Model",
      *     position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(Camera.orientation, { x: -0.5, y: 0, z: -3 })),
      *     rotation: MyAvatar.orientation,
-     *     modelURL: "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj",
-     *     dimensions: { x: 0.945, y: 0.921, z: 0.423 },
+     *     modelURL: "https://apidocs.vircadia.dev/models/cowboy-hat.fbx",
+     *     dimensions: { x: 0.8569, y: 0.3960, z: 1.0744 },
      *     lifetime: 300  // Delete after 5 minutes.
      * });
      * var shapeEntityID = Entities.addEntity({
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h
index 8d70eda9e4..0e7eecc03b 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h
@@ -31,7 +31,7 @@ namespace scriptable {
     /**jsdoc
      * A handle to in-memory mesh data in a {@link GraphicsModel}.
      * 
-     * <p>Created using the {@link Graphics} API, {@link GraphicsModel.cloneModel}, or {@link GraphicsMesh.cloneMesh}.</p>
+     * <p>Create using the {@link Graphics} API, {@link GraphicsModel.cloneModel}, or {@link GraphicsMesh.cloneMesh}.</p>
      *
      * @class GraphicsMesh
      * @hideconstructor
@@ -133,7 +133,7 @@ namespace scriptable {
         QVector<glm::uint32> findNearbyVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const;
 
         /**jsdoc
-         * Adds an attribute to the (whole) mesh's vertices.
+         * Adds an attribute for all vertices.
          * @function GraphicsMesh.addAttribute
          * @param {Graphics.BufferTypeName} name - The name of the attribute.
          * @param {Graphics.BufferType} [defaultValue] - The value to give the attributes added to the vertices.
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h
index 78ddb4f2b0..878b239f3d 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h
@@ -13,7 +13,7 @@ namespace scriptable {
     /**jsdoc
      * A handle to in-memory mesh part data in a {@link GraphicsModel}.
      *
-     * <p>Created using the {@link Graphics} API, {@link GraphicsModel.cloneModel}, {@link GraphicsMesh.cloneMesh}, or 
+     * <p>Create using the {@link Graphics} API, {@link GraphicsModel.cloneModel}, {@link GraphicsMesh.cloneMesh}, or 
      * {@link GraphicsMeshPart.cloneMeshPart}.</p>
      *
      * @class GraphicsMeshPart
diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h
index 6dc2a06747..a6f135c321 100644
--- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h
+++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.h
@@ -22,7 +22,7 @@ namespace scriptable {
      * <p>Note: The model may be used for more than one instance of an item displayed in the scene. Modifying the model updates 
      * all instances displayed.</p>
      *
-     * <p>Created using the {@link Graphics} API or {@link GraphicsModel.cloneModel}.</p>
+     * <p>Create using the {@link Graphics} API or {@link GraphicsModel.cloneModel}.</p>
      *
      * @class GraphicsModel
      * @hideconstructor
@@ -34,7 +34,7 @@ namespace scriptable {
      * @property {Uuid} objectID - The ID of the entity or avatar that the model is associated with, if any; <code>null</code> 
      *     if the model is not associated with an entity or avatar.
      *     <em>Read-only.</em>
-     * @property {number} numMeshes - The number of meshes contained in the model.
+     * @property {number} numMeshes - The number of meshes in the model.
      *     <em>Read-only.</em>
      * @property {GraphicsMesh[]} meshes - The meshes in the model. Each mesh may have more than one mesh part.
      *     <em>Read-only.</em>
diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.cpp b/libraries/graphics/src/graphics/BufferViewHelpers.cpp
index 076cb92dcf..88caa3cea4 100644
--- a/libraries/graphics/src/graphics/BufferViewHelpers.cpp
+++ b/libraries/graphics/src/graphics/BufferViewHelpers.cpp
@@ -76,7 +76,7 @@ const std::array<const char*, 4> ZERO123 = { { "0", "1", "2", "3" } };
  *     <tr><td>{@link Vec2}</td><td><code>"texcoord4"</code></td><td>Fifth UV coordinates buffer.</td></tr>
  *   </tbody>
  * </table>
- * @typedef {Vec3|vec2} Graphics.BufferType
+ * @typedef {Vec4|Vec3|Vec2} Graphics.BufferType
  */
 QMap<QString,int> ATTRIBUTES{
     {"position", gpu::Stream::POSITION },
@@ -120,50 +120,6 @@ template<> glm::uint32 forEach<glm::vec3>(const gpu::BufferView& view, std::func
     return forEachGlmVec<glm::vec3>(view, func);
 }
 
-template <typename T>
-QVariant glmVecToVariant(const T& v, bool asArray /*= false*/) {
-    static const auto len = T().length();
-    if (asArray) {
-        QVariantList list;
-        for (int i = 0; i < len ; i++) {
-            list << v[i];
-        }
-        return list;
-    } else {
-        QVariantMap obj;
-        for (int i = 0; i < len ; i++) {
-            obj[XYZW[i]] = v[i];
-        }
-        return obj;
-    }
-}
-
-template <typename T>
-const T glmVecFromVariant(const QVariant& v) {
-    auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap;
-    static const auto len = T().length();
-    const auto& components = isMap ? XYZW : ZERO123;
-    T result;
-    QVariantMap map;
-    QVariantList list;
-    if (isMap) map = v.toMap(); else list = v.toList();
-    for (int i = 0; i < len ; i++) {
-        float value;
-        if (isMap) {
-            value = map.value(components[i]).toFloat();
-        } else {
-            value = list.value(i).toFloat();
-        }
-#ifdef DEBUG_BUFFERVIEW_HELPERS
-        if (value != value) { // NAN
-            qWarning().nospace()<< "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString();
-        }
-#endif
-        result[i] = value;
-    }
-    return result;
-}
-
 // QVector<T> => BufferView
 template <typename T>
 gpu::BufferView newFromVector(const QVector<T>& elements, const gpu::Element& elementType) {
diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h
index 3635ef64e5..c42bb0426d 100644
--- a/libraries/graphics/src/graphics/BufferViewHelpers.h
+++ b/libraries/graphics/src/graphics/BufferViewHelpers.h
@@ -27,8 +27,54 @@ namespace buffer_helpers {
     extern const std::array<const char*, 4> XYZW;
     extern const std::array<const char*, 4> ZERO123;
 
-    template <typename T> QVariant glmVecToVariant(const T& v, bool asArray = false);
-    template <typename T> const T glmVecFromVariant(const QVariant& v);
+    template <typename T>
+    QVariant glmVecToVariant(const T& v, bool asArray = false) {
+        static const auto len = T().length();
+        if (asArray) {
+            QVariantList list;
+            for (int i = 0; i < len; i++) {
+                list << v[i];
+            }
+            return list;
+        } else {
+            QVariantMap obj;
+            for (int i = 0; i < len; i++) {
+                obj[XYZW[i]] = v[i];
+            }
+            return obj;
+        }
+    }
+
+    template <typename T>
+    const T glmVecFromVariant(const QVariant& v) {
+        auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap;
+        static const auto len = T().length();
+        const auto& components = isMap ? XYZW : ZERO123;
+        T result;
+        QVariantMap map;
+        QVariantList list;
+        if (isMap) {
+            map = v.toMap();
+        } else {
+            list = v.toList();
+        }
+        for (int i = 0; i < len; i++) {
+            float value;
+            if (isMap) {
+                value = map.value(components[i]).toFloat();
+            } else {
+                value = list.value(i).toFloat();
+            }
+#ifdef DEBUG_BUFFERVIEW_HELPERS
+            if (value != value) { // NAN
+                qWarning().nospace() << "vec" << len << "." << components[i] << " NAN received from script.... " << v.toString();
+            }
+#endif
+            result[i] = value;
+        }
+        return result;
+    }
+
 
     glm::uint32 forEachVariant(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const QVariant& value)> func, const char* hint = "");
     template <typename T> glm::uint32 forEach(const gpu::BufferView& view, std::function<bool(glm::uint32 index, const T& value)> func);
@@ -36,7 +82,7 @@ namespace buffer_helpers {
     template <typename T> gpu::BufferView newFromVector(const QVector<T>& elements, const gpu::Element& elementType);
     template <typename T> gpu::BufferView newFromVariantList(const QVariantList& list, const gpu::Element& elementType);
 
-    template <typename T> QVector<T> variantToVector(const QVariant& list);
+    template <typename T> QVector<T> variantToVector(const QVariant& value);
     template <typename T> QVector<T> bufferToVector(const gpu::BufferView& view, const char *hint = "");
 
     // note: these do value conversions from the underlying buffer type into the template type
diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh
index 328ff4a3af..274dbc1cdd 100644
--- a/libraries/graphics/src/graphics/Material.slh
+++ b/libraries/graphics/src/graphics/Material.slh
@@ -13,7 +13,6 @@
 
 <@include graphics/ShaderConstants.h@>
 
-
 const int MAX_TEXCOORDS = 2;
 
 struct TexMapArray { 
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 805e5d3a09..bfcf806843 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -212,10 +212,11 @@ using PacketType = PacketTypeEnum::Value;
 
 const int NUM_BYTES_MD5_HASH = 16;
 
-typedef char PacketVersion;
+// NOTE: There is a max limit of 255, hopefully we have a better way to manage this by then.
+typedef uint8_t PacketVersion;
 
 PacketVersion versionForPacketType(PacketType packetType);
-QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols
+QByteArray protocolVersionsSignature(); /// returns a unique signature for all the current protocols
 QString protocolVersionsSignatureBase64();
 
 #if (PR_BUILD || DEV_BUILD)
@@ -226,7 +227,7 @@ uint qHash(const PacketType& key, uint seed);
 QDebug operator<<(QDebug debug, const PacketType& type);
 
 // Due to the different legacy behaviour, we need special processing for domains that were created before
-// the zone inheritance modes were added.  These have version numbers up to 80
+// the zone inheritance modes were added. These have version numbers up to 80.
 enum class EntityVersion : PacketVersion {
     StrokeColorProperty = 0,
     HasDynamicOwnershipTests,
@@ -278,6 +279,7 @@ enum class EntityVersion : PacketVersion {
     TextEntityFonts,
     ScriptServerKinematicMotion,
     ScreenshareZone,
+    ZoneOcclusion,
 
     // Add new versions above here
     NUM_PACKET_TYPE,
diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp
index 8ab502e951..5e6825c886 100755
--- a/libraries/octree/src/OctreePacketData.cpp
+++ b/libraries/octree/src/OctreePacketData.cpp
@@ -482,6 +482,19 @@ bool OctreePacketData::appendValue(const QVector<bool>& value) {
     return success;
 }
 
+bool OctreePacketData::appendValue(const QVector<QUuid>& value) {
+    uint16_t qVecSize = value.size();
+    bool success = appendValue(qVecSize);
+    if (success) {
+        success = append((const unsigned char*)value.constData(), qVecSize * sizeof(QUuid));
+        if (success) {
+            _bytesOfValues += qVecSize * sizeof(QUuid);
+            _totalBytesOfValues += qVecSize * sizeof(QUuid);
+        }
+    }
+    return success;
+}
+
 bool OctreePacketData::appendValue(const glm::quat& value) {
     const size_t VALUES_PER_QUAT = 4;
     const size_t PACKED_QUAT_SIZE = sizeof(uint16_t) * VALUES_PER_QUAT;
@@ -774,6 +787,15 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
     return (dataBytes - start) + (int)sizeof(uint16_t);
 }
 
+int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVector<QUuid>& result) {
+    uint16_t length;
+    memcpy(&length, dataBytes, sizeof(uint16_t));
+    dataBytes += sizeof(length);
+    result.resize(length);
+    memcpy(result.data(), dataBytes, length * sizeof(QUuid));
+    return sizeof(uint16_t) + length * sizeof(QUuid);
+}
+
 int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result) {
     uint16_t length;
     memcpy(&length, dataBytes, sizeof(length));
diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h
index 46e5de9bda..2050dd1487 100644
--- a/libraries/octree/src/OctreePacketData.h
+++ b/libraries/octree/src/OctreePacketData.h
@@ -186,6 +186,9 @@ public:
     /// appends a QVector of bools to the end of the stream, may fail if new data stream is too long to fit in packet
     bool appendValue(const QVector<bool>& value);
 
+    /// appends a QVector of QUuids to the end of the stream, may fail if new data stream is too long to fit in packet
+    bool appendValue(const QVector<QUuid>& value);
+
     /// appends a packed quat to the end of the stream, may fail if new data stream is too long to fit in packet
     bool appendValue(const glm::quat& value);
 
@@ -284,6 +287,7 @@ public:
     static int unpackDataFromBytes(const unsigned char* dataBytes, QVector<glm::quat>& result);
     static int unpackDataFromBytes(const unsigned char* dataBytes, QVector<float>& result);
     static int unpackDataFromBytes(const unsigned char* dataBytes, QVector<bool>& result);
+    static int unpackDataFromBytes(const unsigned char* dataBytes, QVector<QUuid>& result);
     static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result);
     static int unpackDataFromBytes(const unsigned char* dataBytes, AACube& result);
     static int unpackDataFromBytes(const unsigned char* dataBytes, QRect& result);
diff --git a/libraries/render-utils/src/CullFace.slh b/libraries/render-utils/src/CullFace.slh
new file mode 100644
index 0000000000..61b76bb22f
--- /dev/null
+++ b/libraries/render-utils/src/CullFace.slh
@@ -0,0 +1,20 @@
+<!
+//  CullFace.slh
+//  libraries/render-utils/src
+//
+//  Created by HifiExperiments on 4/16/2020.
+//  Copyright 2020 Vircadia
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+!>
+<@if not CULL_FACE_SLH@>
+<@def CULL_FACE_SLH@>
+
+// NOTE: this calculation happens once per fragment.  this could be optimized by creating different shaders (via defines)
+// for front, back, and double-sided.  for front/back-only triangles, this will simplify to always 1 or always -1
+vec3 evalFrontOrBackFaceNormal(vec3 normal) {
+    return (2.0 * float(gl_FrontFacing) - 1.0) * normal;
+}
+
+<@endif@>
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 0786ef62ca..3b51d195f7 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -55,6 +55,13 @@ template <> const ShapeKey shapeGetShapeKey(const MeshPartPayload::Pointer& payl
 template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderArgs* args) {
     return payload->render(args);
 }
+
+template <> bool payloadPassesZoneOcclusionTest(const MeshPartPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones) {
+    if (payload) {
+        return payload->passesZoneOcclusionTest(containingZones);
+    }
+    return false;
+}
 }
 
 MeshPartPayload::MeshPartPayload(const std::shared_ptr<const graphics::Mesh>& mesh, int partIndex, graphics::MaterialPointer material, const uint64_t& created) :
@@ -171,10 +178,23 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) {
     batch.setInputStream(0, _drawMesh->getVertexStream());
 }
 
- void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
+void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
     batch.setModelTransform(_worldFromLocalTransform);
 }
 
+bool MeshPartPayload::passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const {
+    if (!_renderWithZones.isEmpty()) {
+        if (!containingZones.empty()) {
+            for (auto renderWithZone : _renderWithZones) {
+                if (containingZones.find(renderWithZone) != containingZones.end()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    return true;
+}
 
 void MeshPartPayload::render(RenderArgs* args) {
     PerformanceTimer perfTimer("MeshPartPayload::render");
@@ -246,6 +266,12 @@ template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, Ren
     return payload->render(args);
 }
 
+template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones) {
+    if (payload) {
+        return payload->passesZoneOcclusionTest(containingZones);
+    }
+    return false;
+}
 }
 
 ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex,
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index 92dbae2e01..0a1562ff44 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -74,11 +74,15 @@ public:
 
     void setCullWithParent(bool value) { _cullWithParent = value; }
 
+    void setRenderWithZones(const QVector<QUuid>& renderWithZones) { _renderWithZones = renderWithZones; }
+    bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const;
+
     static bool enableMaterialProceduralShaders;
 
 protected:
     render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() };
     bool _cullWithParent { false };
+    QVector<QUuid> _renderWithZones;
     uint64_t _created;
 };
 
@@ -87,6 +91,7 @@ namespace render {
     template <> const Item::Bound payloadGetBound(const MeshPartPayload::Pointer& payload);
     template <> const ShapeKey shapeGetShapeKey(const MeshPartPayload::Pointer& payload);
     template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderArgs* args);
+    template <> bool payloadPassesZoneOcclusionTest(const MeshPartPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones);
 }
 
 class ModelMeshPartPayload : public MeshPartPayload {
@@ -147,6 +152,7 @@ namespace render {
     template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload);
     template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload);
     template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args);
+    template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones);
 }
 
 #endif // hifi_MeshPartPayload_h
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 0320d8ee01..1b7e1ebfb6 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -893,7 +893,7 @@ void Model::updateRenderItemsKey(const render::ScenePointer& scene) {
     }
     auto renderItemsKey = _renderItemKeyGlobalFlags;
     render::Transaction transaction;
-    for(auto itemID: _modelMeshRenderItemIDs) {
+    for (auto itemID: _modelMeshRenderItemIDs) {
         transaction.updateItem<ModelMeshPartPayload>(itemID, [renderItemsKey](ModelMeshPartPayload& data) {
             data.updateKey(renderItemsKey);
         });
@@ -979,7 +979,7 @@ void Model::setCullWithParent(bool cullWithParent) {
 
         render::Transaction transaction;
         auto renderItemsKey = _renderItemKeyGlobalFlags;
-        for(auto item : _modelMeshRenderItemIDs) {
+        for (auto item : _modelMeshRenderItemIDs) {
             transaction.updateItem<ModelMeshPartPayload>(item, [cullWithParent, renderItemsKey](ModelMeshPartPayload& data) {
                 data.setCullWithParent(cullWithParent);
                 data.updateKey(renderItemsKey);
@@ -989,6 +989,16 @@ void Model::setCullWithParent(bool cullWithParent) {
     }
 }
 
+void Model::setRenderWithZones(const QVector<QUuid>& renderWithZones) {
+    render::Transaction transaction;
+    for (auto item : _modelMeshRenderItemIDs) {
+        transaction.updateItem<ModelMeshPartPayload>(item, [renderWithZones](ModelMeshPartPayload& data) {
+            data.setRenderWithZones(renderWithZones);
+        });
+    }
+    AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
+}
+
 const render::ItemKey Model::getRenderItemKeyGlobalFlags() const {
     return _renderItemKeyGlobalFlags;
 }
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 2def9f3287..ab7a748a32 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -131,6 +131,8 @@ public:
 
     void setCullWithParent(bool value);
 
+    void setRenderWithZones(const QVector<QUuid>& renderWithZones);
+
     // Access the current RenderItemKey Global Flags used by the model and applied to the render items  representing the parts of the model.
     const render::ItemKey getRenderItemKeyGlobalFlags() const;
 
diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf
index a6cc82e335..98abc29d8c 100644
--- a/libraries/render-utils/src/model.slf
+++ b/libraries/render-utils/src/model.slf
@@ -13,6 +13,7 @@
 <@include graphics/Material.slh@>
 <@include graphics/MaterialTextures.slh@>
 <@include render-utils/ShaderConstants.h@>
+<@include CullFace.slh@>
 
 <@if not HIFI_USE_SHADOW@>
     <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@>
@@ -129,7 +130,7 @@ void main(void) {
             _fragColor0 = vec4(albedo * isUnlitEnabled(), opacity);
     <@else@>
             packDeferredFragmentUnlit(
-                normalize(_normalWS),
+                evalFrontOrBackFaceNormal(normalize(_normalWS)),
                 opacity,
                 albedo * isUnlitEnabled());
     <@endif@>
@@ -195,7 +196,7 @@ void main(void) {
     <@else@>
             vec3 fragNormalWS = _normalWS;
     <@endif@>
-        fragNormalWS = normalize(fragNormalWS);
+        fragNormalWS = evalFrontOrBackFaceNormal(normalize(fragNormalWS));
 
     <@if HIFI_USE_FORWARD@>
         TransformCamera cam = getTransformCamera();
diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf
index 9760216682..fabe85cb4f 100644
--- a/libraries/render-utils/src/simple.slf
+++ b/libraries/render-utils/src/simple.slf
@@ -13,6 +13,7 @@
 <@include gpu/Color.slh@>
 <@include DefaultMaterials.slh@>
 <@include render-utils/ShaderConstants.h@>
+<@include CullFace.slh@>
 
 <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@>
     <@if not HIFI_USE_UNLIT@>
@@ -94,7 +95,7 @@ void main(void) {
             1.0,
             DEFAULT_OCCLUSION,
             fragPosition,
-            normalize(_normalWS) * (2.0 * float(gl_FrontFacing) - 1.0),
+            evalFrontOrBackFaceNormal(normalize(_normalWS)),
             texel.rgb,
             fresnel,
             metallic,
@@ -111,7 +112,7 @@ void main(void) {
             1.0,
             DEFAULT_OCCLUSION,
             fragPosition,
-            normalize(_normalWS) * (2.0 * float(gl_FrontFacing) - 1.0),
+            evalFrontOrBackFaceNormal(normalize(_normalWS)),
             texel.rgb,
             fresnel,
             metallic,
@@ -119,7 +120,7 @@ void main(void) {
             texel.a);
     <@else@>
         packDeferredFragment(
-            normalize(_normalWS) * (2.0 * float(gl_FrontFacing) - 1.0),
+            evalFrontOrBackFaceNormal(normalize(_normalWS)),
             1.0,
             texel.rgb,
             DEFAULT_ROUGHNESS,
@@ -141,7 +142,7 @@ void main(void) {
             , texel.a);
     <@else@>
         packDeferredFragmentUnlit(
-            normalize(_normalWS) * (2.0 * float(gl_FrontFacing) - 1.0),
+            evalFrontOrBackFaceNormal(normalize(_normalWS)),
             1.0,
             texel.rgb
         <@if HIFI_USE_FADE@>
diff --git a/libraries/render-utils/src/simple_procedural.slf b/libraries/render-utils/src/simple_procedural.slf
index 5b0eb62cca..cc8edbb415 100644
--- a/libraries/render-utils/src/simple_procedural.slf
+++ b/libraries/render-utils/src/simple_procedural.slf
@@ -24,6 +24,7 @@
 <@include gpu/Transform.slh@>
 <$declareStandardCameraTransform()$>
 
+<@include CullFace.slh@>
 <@include render-utils/ShaderConstants.h@>
 
 layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS;
@@ -66,7 +67,7 @@ float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition pro
 
 #line 2030
 void main(void) {
-    vec3 normal = normalize(_normalWS.xyz) * (2.0 * float(gl_FrontFacing) - 1.0);
+    vec3 normal = evalFrontOrBackFaceNormal(normalize(_normalWS.xyz));
     vec3 diffuse = _color.rgb;
     vec3 fresnel = DEFAULT_FRESNEL;
     float roughness = DEFAULT_ROUGHNESS;
diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h
index a75192bad7..5fee00b370 100644
--- a/libraries/render/src/render/Args.h
+++ b/libraries/render/src/render/Args.h
@@ -13,15 +13,16 @@
 #include <functional>
 #include <memory>
 #include <stack>
+#include <unordered_set>
 
 #include <GLMHelpers.h>
 #include <ViewFrustum.h>
 #include <StencilMaskMode.h>
+#include <UUIDHasher.h>
 
 #include <gpu/Forward.h>
 #include "Forward.h"
 
-
 class AABox;
 
 namespace render {
@@ -142,13 +143,6 @@ namespace render {
         bool _takingSnapshot { false };
         StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE };
         std::function<void(gpu::Batch&)> _stencilMaskOperator;
-
-        float _visionSqueezeX { 0.0f };
-        float _visionSqueezeY { 0.0f };
-        float _visionSqueezeTransition { 0.15f };
-        int _visionSqueezePerEye { 0 };
-        float _visionSqueezeGroundPlaneY { 0.0f };
-        float _visionSqueezeSpotlightSize { 0.02f };
     };
 
 }
diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp
index 8cedbc7f3e..164d8e9f21 100644
--- a/libraries/render/src/render/CullTask.cpp
+++ b/libraries/render/src/render/CullTask.cpp
@@ -19,6 +19,9 @@
 
 using namespace render;
 
+std::unordered_set<QUuid> CullTest::_containingZones = std::unordered_set<QUuid>();
+std::unordered_set<QUuid> CullTest::_prevContainingZones = std::unordered_set<QUuid>();
+
 CullTest::CullTest(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum) :
     _functor(functor),
     _args(pargs),
@@ -64,46 +67,8 @@ bool CullTest::solidAngleTest(const AABox& bound) {
     return true;
 }
 
-void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
-                       const ItemBounds& inItems, ItemBounds& outItems) {
-    assert(renderContext->args);
-    assert(renderContext->args->hasViewFrustum());
-
-    RenderArgs* args = renderContext->args;
-    const ViewFrustum& frustum = args->getViewFrustum();
-
-    details._considered += (int)inItems.size();
-
-    // Culling / LOD
-    for (auto item : inItems) {
-        if (item.bound.isNull()) {
-            outItems.emplace_back(item); // One more Item to render
-            continue;
-        }
-
-        // TODO: some entity types (like lights) might want to be rendered even
-        // when they are outside of the view frustum...
-        bool inView;
-        {
-            PerformanceTimer perfTimer("boxIntersectsFrustum");
-            inView = frustum.boxIntersectsFrustum(item.bound);
-        }
-        if (inView) {
-            bool bigEnoughToRender;
-            {
-                PerformanceTimer perfTimer("shouldRender");
-                bigEnoughToRender = cullFunctor(args, item.bound);
-            }
-            if (bigEnoughToRender) {
-                outItems.emplace_back(item); // One more Item to render
-            } else {
-                details._tooSmall++;
-            }
-        } else {
-            details._outOfView++;
-        }
-    }
-    details._rendered += (int)outItems.size();
+bool CullTest::zoneOcclusionTest(const render::Item& item) {
+    return item.passesZoneOcclusionTest(_containingZones);
 }
 
 void FetchNonspatialItems::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemBounds& outItems) {
@@ -117,7 +82,7 @@ void FetchNonspatialItems::run(const RenderContextPointer& renderContext, const
     outItems.reserve(items.size());
     for (auto& id : items) {
         auto& item = scene->getItem(id);
-        if (filter.test(item.getKey())) {
+        if (filter.test(item.getKey()) && item.passesZoneOcclusionTest(CullTest::_containingZones)) {
             outItems.emplace_back(ItemBound(id, item.getBound()));
         }
     }
@@ -126,7 +91,6 @@ void FetchNonspatialItems::run(const RenderContextPointer& renderContext, const
 void FetchSpatialTree::configure(const Config& config) {
     _justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
     _freezeFrustum = config.freezeFrustum;
-    _lodAngle = config.lodAngle;
 }
 
 void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection) {
@@ -223,7 +187,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideFitItems");
                 for (auto id : inSelection.insideItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -238,7 +202,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideSmallItems");
                 for (auto id : inSelection.insideSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -253,7 +217,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialFitItems");
                 for (auto id : inSelection.partialItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -268,7 +232,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialSmallItems");
                 for (auto id : inSelection.partialSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -285,7 +249,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideFitItems");
                 for (auto id : inSelection.insideItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         outItems.emplace_back(itemBound);
                         if (item.getKey().isMetaCullGroup()) {
@@ -300,7 +264,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("insideSmallItems");
                 for (auto id : inSelection.insideSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         if (test.solidAngleTest(itemBound.bound)) {
                             outItems.emplace_back(itemBound);
@@ -317,7 +281,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialFitItems");
                 for (auto id : inSelection.partialItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
                         if (test.frustumTest(itemBound.bound)) {
                             outItems.emplace_back(itemBound);
@@ -334,14 +298,12 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext,
                 PerformanceTimer perfTimer("partialSmallItems");
                 for (auto id : inSelection.partialSubcellItems) {
                     auto& item = scene->getItem(id);
-                    if (filter.test(item.getKey())) {
+                    if (filter.test(item.getKey()) && test.zoneOcclusionTest(item)) {
                         ItemBound itemBound(id, item.getBound());
-                        if (test.frustumTest(itemBound.bound)) {
-                            if (test.solidAngleTest(itemBound.bound)) {
-                                outItems.emplace_back(itemBound);
-                                if (item.getKey().isMetaCullGroup()) {
-                                    item.fetchMetaSubItemBounds(outItems, (*scene));
-                                }
+                        if (test.frustumTest(itemBound.bound) && test.solidAngleTest(itemBound.bound)) {
+                            outItems.emplace_back(itemBound);
+                            if (item.getKey().isMetaCullGroup()) {
+                                item.fetchMetaSubItemBounds(outItems, (*scene));
                             }
                         }
                     }
@@ -452,3 +414,13 @@ void ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext
         args->popViewFrustum();
     }
 }
+
+void ClearContainingZones::run(const RenderContextPointer& renderContext) {
+    // This is a bit of a hack.  We want to do zone culling as early as possible, so we do it
+    // during the RenderFetchCullSortTask (in CullSpatialSelection and FetchNonspatialItems),
+    // but the zones aren't collected until after (in SetupZones).  To get around this,
+    // we actually use the zones from the previous frame to render, and then clear at the beginning
+    // of the next frame
+    CullTest::_prevContainingZones = CullTest::_containingZones;
+    CullTest::_containingZones.clear();
+}
\ No newline at end of file
diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h
index e67edd6666..9a7466223d 100644
--- a/libraries/render/src/render/CullTask.h
+++ b/libraries/render/src/render/CullTask.h
@@ -19,9 +19,6 @@ namespace render {
 
     using CullFunctor = std::function<bool(const RenderArgs*, const AABox&)>;
 
-    void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
-        const ItemBounds& inItems, ItemBounds& outItems);
-
     // Culling Frustum / solidAngle test helper class
     struct CullTest {
         CullFunctor _functor;
@@ -36,6 +33,10 @@ namespace render {
         bool frustumTest(const AABox& bound);
         bool antiFrustumTest(const AABox& bound);
         bool solidAngleTest(const AABox& bound);
+        bool zoneOcclusionTest(const render::Item& item);
+
+        static std::unordered_set<QUuid> _containingZones;
+        static std::unordered_set<QUuid> _prevContainingZones;
     };
 
     class FetchNonspatialItems {
@@ -48,7 +49,6 @@ namespace render {
         Q_OBJECT
         Q_PROPERTY(int numItems READ getNumItems)
         Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
-        Q_PROPERTY(float LODAngle MEMBER lodAngle NOTIFY dirty)
     
     public:
         int numItems{ 0 };
@@ -56,7 +56,6 @@ namespace render {
 
         bool freezeFrustum{ false };
     
-        float lodAngle{ 2.0 };
     public slots:
         void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
 
@@ -68,7 +67,6 @@ namespace render {
         bool _freezeFrustum{ false }; // initialized by Config
         bool _justFrozeFrustum{ false };
         ViewFrustum _frozenFrustum;
-        float _lodAngle;
 
     public:
         using Config = FetchSpatialTreeConfig;
@@ -159,6 +157,12 @@ namespace render {
 
         render::CullFunctor _cullFunctor;
     };
+
+    class ClearContainingZones {
+    public:
+        using JobModel = Job::Model<ClearContainingZones>;
+        void run(const RenderContextPointer& renderContext);
+    };
 }
 
 #endif // hifi_render_CullTask_h;
\ No newline at end of file
diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp
index 532964777f..f21df58368 100644
--- a/libraries/render/src/render/Item.cpp
+++ b/libraries/render/src/render/Item.cpp
@@ -154,4 +154,10 @@ namespace render {
         return payload->metaFetchMetaSubItems(subItems);
     }
 
+    template <> bool payloadPassesZoneOcclusionTest(const PayloadProxyInterface::Pointer& payload, const std::unordered_set<QUuid>& containingZones) {
+        if (!payload) {
+            return false;
+        }
+        return payload->passesZoneOcclusionTest(containingZones);
+    }
 }
diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h
index 3383101b5b..cb9fb32c7f 100644
--- a/libraries/render/src/render/Item.h
+++ b/libraries/render/src/render/Item.h
@@ -436,6 +436,8 @@ public:
 
         virtual uint32_t fetchMetaSubItems(ItemIDs& subItems) const = 0;
 
+        virtual bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const = 0;
+
         ~PayloadInterface() {}
 
         // Status interface is local to the base class
@@ -487,6 +489,8 @@ public:
     uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); }
     uint32_t fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const;
 
+    bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const { return _payload->passesZoneOcclusionTest(containingZones); }
+
     // Access the status
     const StatusPointer& getStatus() const { return _payload->getStatus(); }
 
@@ -537,6 +541,10 @@ template <class T> const ShapeKey shapeGetShapeKey(const std::shared_ptr<T>& pay
 // Meta items act as the grouping object for several sub items (typically shapes).
 template <class T> uint32_t metaFetchMetaSubItems(const std::shared_ptr<T>& payloadData, ItemIDs& subItems) { return 0; }
 
+// Zone Occlusion Interface
+// Allows payloads to determine if they should render or not, based on the zones that contain the current camera
+template <class T> bool payloadPassesZoneOcclusionTest(const std::shared_ptr<T>& payloadData, const std::unordered_set<QUuid>& containingZones) { return true; }
+
 // THe Payload class is the real Payload to be used
 // THis allow anything to be turned into a Payload as long as the required interface functions are available
 // When creating a new kind of payload from a new "stuff" class then you need to create specialized version for "stuff"
@@ -561,6 +569,8 @@ public:
     // Meta Type Interface
     virtual uint32_t fetchMetaSubItems(ItemIDs& subItems) const override { return metaFetchMetaSubItems<T>(_data, subItems); }
 
+    virtual bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const override { return payloadPassesZoneOcclusionTest<T>(_data, containingZones); }
+
 protected:
     DataPointer _data;
 
@@ -615,6 +625,7 @@ public:
     virtual Item::Bound getBound() = 0;
     virtual void render(RenderArgs* args) = 0;
     virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const = 0;
+    virtual bool passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const = 0;
 };
 
 template <> const ItemKey payloadGetKey(const PayloadProxyInterface::Pointer& payload);
@@ -622,7 +633,7 @@ template <> const Item::Bound payloadGetBound(const PayloadProxyInterface::Point
 template <> void payloadRender(const PayloadProxyInterface::Pointer& payload, RenderArgs* args);
 template <> uint32_t metaFetchMetaSubItems(const PayloadProxyInterface::Pointer& payload, ItemIDs& subItems);
 template <> const ShapeKey shapeGetShapeKey(const PayloadProxyInterface::Pointer& payload);
-
+template <> bool payloadPassesZoneOcclusionTest(const PayloadProxyInterface::Pointer& payload, const std::unordered_set<QUuid>& containingZones);
 
 typedef Item::PayloadPointer PayloadPointer;
 typedef std::vector<PayloadPointer> Payloads;
diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp
index ebcf5a432b..b2656a597f 100644
--- a/libraries/render/src/render/RenderFetchCullSortTask.cpp
+++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp
@@ -74,6 +74,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin
     const auto filteredLayeredOpaque = task.addJob<FilterLayeredItems>("FilterLayeredOpaque", layeredOpaques, ItemKey::Layer::LAYER_1);
     const auto filteredLayeredTransparent = task.addJob<FilterLayeredItems>("FilterLayeredTransparent", layeredTransparents, ItemKey::Layer::LAYER_1);
 
+    task.addJob<ClearContainingZones>("ClearContainingZones");
 
     output = Output(BucketList{ opaques, transparents, lights, metas,
                     filteredLayeredOpaque.getN<FilterLayeredItems::Outputs>(0), filteredLayeredTransparent.getN<FilterLayeredItems::Outputs>(0),
diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt
index eb3e286843..57904be586 100644
--- a/libraries/shared/CMakeLists.txt
+++ b/libraries/shared/CMakeLists.txt
@@ -13,6 +13,13 @@ if (ANDROID)
     target_link_libraries(${TARGET_NAME} android)
 endif()
 
+if (APPLE)
+    find_library(FRAMEWORK_IOKIT IOKit)
+    find_library(CORE_FOUNDATION CoreFoundation)
+    find_library(OpenGL OpenGL)
+    target_link_libraries(${TARGET_NAME} ${FRAMEWORK_IOKIT} ${CORE_FOUNDATION} ${OpenGL})
+endif()
+
 target_zlib()
 target_nsight()
 target_json()
diff --git a/libraries/shared/src/JointData.h b/libraries/shared/src/JointData.h
index 3f957fa046..329c2dd151 100644
--- a/libraries/shared/src/JointData.h
+++ b/libraries/shared/src/JointData.h
@@ -16,7 +16,7 @@ public:
 // Used by the avatar mixer to describe a single joint
 // Translations relative to their parent joint
 // Rotations are absolute (i.e. not relative to parent) and are in rig space.
-// No JSDoc because its not provided as a type to the script engine.
+// No JSDoc because it's not provided as a type to the script engine.
 class JointData {
 public:
     glm::quat rotation;
diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp
index 7c30d4f205..d47cc0769a 100644
--- a/libraries/shared/src/RegisteredMetaTypes.cpp
+++ b/libraries/shared/src/RegisteredMetaTypes.cpp
@@ -36,6 +36,7 @@ int qVectorVec3MetaTypeId = qRegisterMetaType<QVector<glm::vec3>>();
 int qVectorQuatMetaTypeId = qRegisterMetaType<QVector<glm::quat>>();
 int qVectorBoolMetaTypeId = qRegisterMetaType<QVector<bool>>();
 int qVectorGLMUint32MetaTypeId = qRegisterMetaType<QVector<unsigned int>>("QVector<glm::uint32>");
+int qVectorQUuidMetaTypeId = qRegisterMetaType<QVector<QUuid>>();
 int quatMetaTypeId = qRegisterMetaType<glm::quat>();
 int pickRayMetaTypeId = qRegisterMetaType<PickRay>();
 int collisionMetaTypeId = qRegisterMetaType<Collision>();
@@ -58,6 +59,7 @@ void registerMetaTypes(QScriptEngine* engine) {
     qScriptRegisterMetaType(engine, qVectorBoolToScriptValue, qVectorBoolFromScriptValue);
     qScriptRegisterMetaType(engine, qVectorFloatToScriptValue, qVectorFloatFromScriptValue);
     qScriptRegisterMetaType(engine, qVectorIntToScriptValue, qVectorIntFromScriptValue);
+    qScriptRegisterMetaType(engine, qVectorQUuidToScriptValue, qVectorQUuidFromScriptValue);
 
     qScriptRegisterMetaType(engine, qSizeFToScriptValue, qSizeFFromScriptValue);
     qScriptRegisterMetaType(engine, qRectToScriptValue, qRectFromScriptValue);
@@ -874,6 +876,22 @@ QVector<float> qVectorFloatFromScriptValue(const QScriptValue& array) {
     return newVector;
 }
 
+QScriptValue qVectorQUuidToScriptValue(QScriptEngine* engine, const QVector<QUuid>& vector) {
+    QScriptValue array = engine->newArray();
+    for (int i = 0; i < vector.size(); i++) {
+        array.setProperty(i, quuidToScriptValue(engine, vector.at(i)));
+    }
+    return array;
+}
+
+void qVectorQUuidFromScriptValue(const QScriptValue& array, QVector<QUuid>& vector) {
+    int length = array.property("length").toInteger();
+
+    for (int i = 0; i < length; i++) {
+        vector << array.property(i).toVariant().toUuid();
+    }
+}
+
 QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array) {
     if (!array.isArray()) {
         return QVector<QUuid>(); 
@@ -1309,7 +1327,7 @@ void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) {
 /**jsdoc
  * A triangle in a mesh.
  * @typedef {object} MeshFace
- * @property {number[]} vertices - The indexes of the three vertices that make up the fase.
+ * @property {number[]} vertices - The indexes of the three vertices that make up the face.
  */
 QScriptValue meshFaceToScriptValue(QScriptEngine* engine, const MeshFace &meshFace) {
     QScriptValue obj = engine->newObject();
diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h
index a7b7c4edf4..86e06d7f93 100644
--- a/libraries/shared/src/RegisteredMetaTypes.h
+++ b/libraries/shared/src/RegisteredMetaTypes.h
@@ -247,6 +247,8 @@ QVector<float> qVectorFloatFromScriptValue(const QScriptValue& array);
 QScriptValue qVectorIntToScriptValue(QScriptEngine* engine, const QVector<uint32_t>& vector);
 void qVectorIntFromScriptValue(const QScriptValue& array, QVector<uint32_t>& vector);
 
+QScriptValue qVectorQUuidToScriptValue(QScriptEngine* engine, const QVector<QUuid>& vector);
+void qVectorQUuidFromScriptValue(const QScriptValue& array, QVector<QUuid>& vector);
 QVector<QUuid> qVectorQUuidFromScriptValue(const QScriptValue& array);
 
 QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube);
diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp
index 2ccc247af3..9fc72bd000 100644
--- a/libraries/shared/src/shared/FileLogger.cpp
+++ b/libraries/shared/src/shared/FileLogger.cpp
@@ -38,12 +38,12 @@ private:
     QMutex _fileMutex;
 };
 
-static const QString FILENAME_FORMAT = "hifi-log_%1%2.txt";
+static const QString FILENAME_FORMAT = "vircadia-log_%1%2.txt";
 static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
 static const QString LOGS_DIRECTORY = "Logs";
 static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[01][0-9]-[0-3][0-9]_[0-2][0-9]\\.[0-6][0-9]\\.[0-6][0-9]";
 static const QString SESSION_WILDCARD = "[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}";
-static QRegExp LOG_FILENAME_REGEX { "hifi-log_" + DATETIME_WILDCARD + "(_" + SESSION_WILDCARD + ")?\\.txt" };
+static QRegExp LOG_FILENAME_REGEX { "vircadia-log_" + DATETIME_WILDCARD + "(_" + SESSION_WILDCARD + ")?\\.txt" };
 static QUuid SESSION_ID;
 
 // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively
@@ -68,7 +68,7 @@ QString getLogRollerFilename() {
 }
 
 const QString& getLogFilename() {
-    static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "hifi-log.txt";
+    static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "vircadia-log.txt";
     return fileName;
 }
 
diff --git a/prebuild.py b/prebuild.py
index 03677f21d7..cc315a49a4 100644
--- a/prebuild.py
+++ b/prebuild.py
@@ -94,6 +94,8 @@ def parse_args():
     parser.add_argument('--force-build', action='store_true')
     parser.add_argument('--release-type', type=str, default="DEV", help="DEV, PR, or PRODUCTION")
     parser.add_argument('--vcpkg-root', type=str, help='The location of the vcpkg distribution')
+    parser.add_argument('--vcpkg-build-type', type=str, help='Could be `release` or `debug`. By default it doesn`t set the build-type')
+    parser.add_argument('--vcpkg-skip-clean', action='store_true', help='Skip the cleanup of vcpkg downloads and packages folders after vcpkg build completition.')
     parser.add_argument('--build-root', required=True, type=str, help='The location of the cmake build')
     parser.add_argument('--ports-path', type=str, default=defaultPortsPath)
     parser.add_argument('--ci-build', action='store_true', default=os.getenv('CI_BUILD') is not None)
@@ -113,6 +115,7 @@ def main():
             del os.environ[var]
 
     args = parse_args()
+    assets_url = hifi_utils.readEnviromentVariableFromFile(args.build_root, 'EXTERNAL_BUILD_ASSETS')
 
     if args.ci_build:
         logging.basicConfig(datefmt='%H:%M:%S', format='%(asctime)s %(guid)s %(message)s', level=logging.INFO)
@@ -125,7 +128,7 @@ def main():
     if 'Windows' == system and 'CI_BUILD' in os.environ and os.environ["CI_BUILD"] == "Github":
         logger.info("Downloading NSIS")
         with timer('NSIS'):
-            hifi_utils.downloadAndExtract('https://athena-public.s3.amazonaws.com/dependencies/NSIS-hifi-plugins-1.0.tgz', "C:/Program Files (x86)")
+            hifi_utils.downloadAndExtract(assets_url + '/dependencies/NSIS-hifi-plugins-1.0.tgz', "C:/Program Files (x86)")
 
     qtInstallPath = ''
     # If not android, install our Qt build
@@ -138,6 +141,11 @@ def main():
                 qt.writeConfig()
 
     pm = hifi_vcpkg.VcpkgRepo(args)
+    if qtInstallPath != '':
+        pm.writeVar('QT_CMAKE_PREFIX_PATH', qtInstallPath)
+
+    # Only allow one instance of the program to run at a time
+
     if qtInstallPath != '':
         pm.writeVar('QT_CMAKE_PREFIX_PATH', qtInstallPath)
 
@@ -160,7 +168,7 @@ def main():
             pm.setupDependencies(qt=qtInstallPath)
 
         # wipe out the build directories (after writing the tag, since failure 
-        # here shouldn't invalidte the vcpkg install)
+        # here shouldn't invalidate the vcpkg install)
         with timer('Cleaning builds'):
             pm.cleanBuilds()
 
@@ -175,6 +183,13 @@ def main():
             qtPath = os.path.join(pm.androidPackagePath, 'qt')
             hifi_android.QtPackager(appPath, qtPath).bundle()
 
+        # Fixup the vcpkg cmake to not reset VCPKG_TARGET_TRIPLET
+        pm.fixupCmakeScript()
+
+        if not args.vcpkg_skip_clean:
+            # Cleanup downloads and packages folders in vcpkg to make it smaller for CI
+            pm.cleanupDevelopmentFiles()
+
         # Write the vcpkg config to the build directory last
         with timer('Writing configuration'):
             pm.writeConfig()
diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json
index da12d2d503..4efd0593fb 100644
--- a/scripts/system/create/assets/data/createAppTooltips.json
+++ b/scripts/system/create/assets/data/createAppTooltips.json
@@ -3,7 +3,10 @@
         "tooltip": "The shape of this entity's geometry."
     },
     "color": {
-        "tooltip": "The RGB value of this entity."
+        "tooltip": "The color of this entity."
+    },
+    "shapeAlpha": {
+        "tooltip": "The opacity of the entity between 0.0 fully transparent and 1.0 completely opaque."
     },
     "text": {
         "tooltip": "The text to display on the entity."
@@ -12,17 +15,29 @@
         "tooltip": "The color of the text."
     },
     "textAlpha": {
-        "tooltip": "The alpha of the text."
+        "tooltip": "The opacity of the text between 0.0 fully transparent and 1.0 completely opaque."
     },
     "backgroundColor": {
         "tooltip": "The color of the background."
     },
     "backgroundAlpha": {
-        "tooltip": "The alpha of the background."
+        "tooltip": "The opacity of the background between 0.0 fully transparent and 1.0 completely opaque."
     },
     "lineHeight": {
         "tooltip": "The height of each line of text. This determines the size of the text."
     },
+    "font": {
+        "tooltip": "The font to render the text. Supported values: \"Courier\", \"Inconsolata\", \"Roboto\", \"Timeless\", or a URL to a .sdff file."
+    },
+    "textEffect": {
+        "tooltip": "The effect that is applied to the text."
+    },
+    "textEffectColor": {
+        "tooltip": "The color of the text effect."
+    },
+    "textEffectThickness": {
+        "tooltip": "The magnitude of the text effect."
+    },
     "textBillboardMode": {
         "tooltip": "If enabled, determines how the entity will face the camera.",
         "jsPropertyName": "billboardMode"
@@ -149,7 +164,7 @@
     },
     "screenshare": {
         "tooltip": "Enable screen-sharing within this zone"
-    }, 
+    },
     "modelURL": {
         "tooltip": "A mesh model from an FBX or OBJ file."
     },
@@ -199,6 +214,9 @@
         "tooltip": "The tint to be applied to the image.",
         "jsPropertyName": "color"
     },
+    "imageAlpha": {
+        "tooltip": "The opacity of the image between 0.0 fully transparent and 1.0 completely opaque."
+    },    
     "emissive": {
         "tooltip": "If enabled, the image will display at full brightness."
     },
@@ -218,6 +236,16 @@
     "dpi": {
         "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame."
     },
+    "webBillboardMode": {
+        "tooltip": "If enabled, determines how the entity will face the camera.",
+        "jsPropertyName": "billboardMode"
+    },
+    "inputMode": {
+        "tooltip": "The user input mode to use."
+    },
+    "showKeyboardFocusHighlight": {
+        "tooltip": "If enabled, highlights when it has keyboard focus."
+    },    
     "isEmitting": {
         "tooltip": "If enabled, then particles are emitted."
     },
@@ -294,20 +322,20 @@
         "tooltip": "The spread in color that each particle is given, resulting in a variety of colors."
     },
     "particleAlphaTriple": {
-        "tooltip": "The alpha of each particle.",
+        "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque.",
         "jsPropertyName": "alpha"
     },
     "alpha": {
-        "tooltip": "The alpha of each particle."
+        "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque."
     },
     "alphaStart": {
-        "tooltip": "The start alpha of each particle."
+        "tooltip": "The initial opacity level of each particle between 0.0 fully transparent and 1.0 completely opaque."
     },
     "alphaFinish": {
-        "tooltip": "The finish alpha of each particle."
+        "tooltip": "The final opacity level of each particle between 0.0 fully transparent and 1.0 completely opaque."
     },
     "alphaSpread": {
-        "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas."
+        "tooltip": "The spread in opacity that each particle is given, resulting in a variety of opacity levels."
     },
     "emitAcceleration": {
         "tooltip": "The acceleration that is applied to each particle during its lifetime."
@@ -567,6 +595,9 @@
     "primitiveMode": {
         "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"."
     },
+    "renderWithZones": {
+        "tooltip": "If set, this entity will only render when your avatar is inside of a zone in this list."
+    },
     "groupCulled": {
         "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all."
     },
@@ -574,7 +605,7 @@
         "tooltip": "The tint of the web entity."
     },
     "webAlpha": {
-        "tooltip": "The alpha of the web entity."
+        "tooltip": "The opacity of the web entity between 0.0 fully transparent and 1.0 completely opaque."
     },
     "maxFPS": {
         "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact."
diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js
index 16b11698ac..564ce512aa 100644
--- a/scripts/system/create/edit.js
+++ b/scripts/system/create/edit.js
@@ -3,6 +3,7 @@
 //  Created by Brad Hefta-Gaub on 10/2/14.
 //  Persist toolbar by HRS 6/11/15.
 //  Copyright 2014 High Fidelity, Inc.
+//  Copyright 2020 Vircadia contributors.
 //
 //  This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing
 //
@@ -41,6 +42,7 @@ var CreateWindow = Script.require('./modules/createWindow.js');
 var TITLE_OFFSET = 60;
 var CREATE_TOOLS_WIDTH = 490;
 var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942;
+var ENTIRE_DOMAIN_SCAN_RADIUS = 27713;
 
 var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg";
 
@@ -562,7 +564,7 @@ var toolBar = (function () {
             if (!properties.grab) {
                 properties.grab = {};
                 if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) &&
-                    !(properties.type === "Zone" || properties.type === "Light"
+                    !(properties.type === "Zone" || properties.type === "Light" 
                     || properties.type === "ParticleEffect" || properties.type === "Web")) {
                     properties.grab.grabbable = true;
                 } else {
@@ -2587,6 +2589,11 @@ var PropertiesTool = function (opts) {
                 entityID: data.entityID,
                 materialTargetData: parentModelData,
             });
+        } else if (data.type === "zoneListRequest") {
+            emitScriptEvent({
+                type: 'zoneListRequest',
+                zones: getExistingZoneList()
+            });
         }
     };
 
@@ -2884,4 +2891,21 @@ selectionDisplay.onSpaceModeChange = function(spaceMode) {
     propertiesTool.setSpaceMode(spaceMode);
 };
 
+function getExistingZoneList() {
+    var center = { "x": 0, "y": 0, "z": 0 };
+    var existingZoneIDs = Entities.findEntitiesByType("Zone", center, ENTIRE_DOMAIN_SCAN_RADIUS);
+    var listExistingZones = [];
+    var thisZone = {};
+    var properties;
+    for (var k = 0; k < existingZoneIDs.length; k++) {
+        properties = Entities.getEntityProperties(existingZoneIDs[k], ["name"]);
+        thisZone = {
+            "id": existingZoneIDs[k],
+            "name": properties.name
+        };
+        listExistingZones.push(thisZone);
+    }
+    return listExistingZones;
+}
+
 }()); // END LOCAL_SCOPE
diff --git a/scripts/system/create/entityProperties/html/entityProperties.html b/scripts/system/create/entityProperties/html/entityProperties.html
index 876e75ec35..6eadf4d3c0 100644
--- a/scripts/system/create/entityProperties/html/entityProperties.html
+++ b/scripts/system/create/entityProperties/html/entityProperties.html
@@ -1,52 +1,63 @@
 <!--
+//
 //  entityProperties.html
 //
 //  Created by Ryan Huffman on 13 Nov 2014
 //  Copyright 2014 High Fidelity, Inc.
+//  Copyright 2020 Project Athena contributors.
 //
 //  Distributed under the Apache License, Version 2.0.
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
 -->
 <html>
-
-<head>
-    <title>Properties</title>
-    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
-    <link rel="stylesheet" type="text/css" href="../../../html/css/edit-style.css">
-    <link rel="stylesheet" type="text/css" href="../../../html/css/colpick.css">
-    <link rel="stylesheet" type="text/css" href="../../../html/css/jsoneditor.css">
-    <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
-    <script src="../../../html/js/jquery-2.1.4.min.js"></script>
-    <script type="text/javascript" src="../../../html/js/colpick.js"></script>
-    <script type="text/javascript" src="../../../html/js/jsoneditor.min.js"></script>
-    <script type="text/javascript" src="../../../html/js/eventBridgeLoader.js"></script>
-    <script type="text/javascript" src="../../../html/js/spinButtons.js"></script>
-    <script type="text/javascript" src="../../../html/js/utils.js"></script>
-    <script type="text/javascript" src="../../../html/js/includes.js"></script>
-    <script type="text/javascript" src="js/underscore-min.js"></script>
-    <script type="text/javascript" src="js/createAppTooltip.js"></script>
-    <script type="text/javascript" src="js/draggableNumber.js"></script>
-    <script type="text/javascript" src="js/entityProperties.js"></script>
-    
-</head>
-
-<body onload='loaded();'>
-    <div id="properties-list">
-        <div class='property container'>
-            <label id='placeholder-property-type'></label>
-            <div class='value'>
-                <div class='row flex-center' style='padding-bottom: 8px;'>
-                    <div id="placeholder-property-name" class="stretch"></div>
-                    <div id="placeholder-property-locked" class="shrink"></div>
-                    <div id="placeholder-property-visible" class="shrink"></div>
-                </div>
-                <div class='row'>
-                    <div id="placeholder-property-id" class="stretch"></div>
+    <head>
+        <title>Properties</title>
+        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+        <link rel="stylesheet" type="text/css" href="../../../html/css/edit-style.css">
+        <link rel="stylesheet" type="text/css" href="../../../html/css/colpick.css">
+        <link rel="stylesheet" type="text/css" href="../../../html/css/jsoneditor.css">
+        <link rel="stylesheet" type="text/css" href="../../../html/css/tabs.css">    
+        <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
+        <script src="../../../html/js/jquery-2.1.4.min.js"></script>
+        <script type="text/javascript" src="../../../html/js/colpick.js"></script>
+        <script type="text/javascript" src="../../../html/js/jsoneditor.min.js"></script>
+        <script type="text/javascript" src="../../../html/js/eventBridgeLoader.js"></script>
+        <script type="text/javascript" src="../../../html/js/spinButtons.js"></script>
+        <script type="text/javascript" src="../../../html/js/utils.js"></script>
+        <script type="text/javascript" src="../../../html/js/includes.js"></script>
+        <script type="text/javascript" src="js/underscore-min.js"></script>
+        <script type="text/javascript" src="js/createAppTooltip.js"></script>
+        <script type="text/javascript" src="js/draggableNumber.js"></script>
+        <script type="text/javascript" src="js/entityProperties.js"></script>    
+    </head>
+    <body onload='loaded();'>
+        <div id="properties-list">
+            <div class='property container'>
+                <label id='placeholder-property-type'></label>
+                <div class='value'>
+                    <div class='row flex-center' style='padding-bottom: 8px;'>
+                        <div id="placeholder-property-name" class="stretch"></div>
+                        <div id="placeholder-property-locked" class="shrink"></div>
+                        <div id="placeholder-property-visible" class="shrink"></div>
+                    </div>
+                    <div class='row'>
+                        <div id="placeholder-property-id" class="stretch"></div>
+                    </div>
                 </div>
             </div>
+            <table class="tabsTableFrame">
+                <tr class="tabsTrFrame">
+                    <td class = "tabsFrame">
+                        <div id="tabs" class="tabsContainer"></div>
+                    </td>
+                    <td class="tabsPropertiesFrame">
+                        <div class="tabsPropertiesPage" id="properties-pages">
+                            <!-- each property is added at runtime in entityProperties -->
+                        </div>
+                    </td>
+                </tr>
+            </table>
         </div>
-        <!-- each property is added at runtime in entityProperties -->
-    </div>
-</body>
-
+    </body>
 </html>
diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js
index 64d44cc270..182dddf817 100644
--- a/scripts/system/create/entityProperties/html/js/entityProperties.js
+++ b/scripts/system/create/entityProperties/html/js/entityProperties.js
@@ -1,8 +1,8 @@
 //  entityProperties.js
 //
 //  Created by Ryan Huffman on 13 Nov 2014
-//  Modified by David Back on 19 Oct 2018
 //  Copyright 2014 High Fidelity, Inc.
+//  Copyright 2020 Vircadia contributors.
 //
 //  Distributed under the Apache License, Version 2.0.
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@@ -10,6 +10,8 @@
 /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, 
    EventBridge, JSONEditor, openEventBridge, setTimeout, window, _, $ */
 
+var currentTab = "base";
+
 const DEGREES_TO_RADIANS = Math.PI / 180.0;
 
 const NO_SELECTION = ",";
@@ -24,7 +26,7 @@ const PROPERTY_SELECTION_VISIBILITY = Object.freeze({
     SINGLE_SELECTION: 1,
     MULTIPLE_SELECTIONS: 2,
     MULTI_DIFF_SELECTIONS: 4,
-    ANY_SELECTIONS: 7, /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */
+    ANY_SELECTIONS: 7 /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */
 });
 
 // Multiple-selection behavior
@@ -34,12 +36,13 @@ const PROPERTY_MULTI_DISPLAY_MODE = Object.freeze({
      * Comma separated values
      * Limited for properties with type "string" or "textarea" and readOnly enabled
      */
-    COMMA_SEPARATED_VALUES: 1,
+    COMMA_SEPARATED_VALUES: 1
 });
 
 const GROUPS = [
     {
         id: "base",
+        label: "ENTITY",
         properties: [
             {
                 label: NO_SELECTION,
@@ -113,11 +116,16 @@ const GROUPS = [
                 },
                 propertyID: "primitiveMode",
             },
+            {
+                label: "Render With Zones",
+                type: "multipleZonesSelection",
+                propertyID: "renderWithZones",
+            }
         ]
     },
     {
         id: "shape",
-        addToGroup: "base",
+        label: "SHAPE",        
         properties: [
             {
                 label: "Shape",
@@ -133,11 +141,21 @@ const GROUPS = [
                 type: "color",
                 propertyID: "color",
             },
+            {
+                label: "Alpha",
+                type: "number-draggable",
+                min: 0,
+                max: 1,
+                step: 0.01,
+                decimals: 2,
+                propertyID: "shapeAlpha",
+                propertyName: "alpha",
+            },            
         ]
     },
     {
         id: "text",
-        addToGroup: "base",
+        label: "TEXT",
         properties: [
             {
                 label: "Text",
@@ -181,6 +199,36 @@ const GROUPS = [
                 unit: "m",
                 propertyID: "lineHeight",
             },
+            {
+                label: "Font",
+                type: "string",
+                propertyID: "font",
+            },
+            {
+                label: "Effect",
+                type: "dropdown",
+                options: {
+                    none: "None",
+                    outline: "Outline",
+                    "outline fill": "Outline with fill",
+                    shadow: "Shadow"
+                },
+                propertyID: "textEffect",
+            },
+            {
+                label: "Effect Color",
+                type: "color",
+                propertyID: "textEffectColor",
+            },
+            {
+                label: "Effect Thickness",
+                type: "number-draggable",
+                min: 0.0,
+                max: 0.5,
+                step: 0.01,
+                decimals: 2,
+                propertyID: "textEffectThickness",
+            },
             {
                 label: "Billboard Mode",
                 type: "dropdown",
@@ -220,12 +268,12 @@ const GROUPS = [
                 label: "Unlit",
                 type: "bool",
                 propertyID: "unlit",
-            },
+            }
         ]
     },
     {
         id: "zone",
-        addToGroup: "base",
+        label: "ZONE",
         properties: [
             {
                 label: "Shape Type",
@@ -255,7 +303,13 @@ const GROUPS = [
                 label: "Filter",
                 type: "string",
                 propertyID: "filterURL",
-            },
+            }
+        ]
+    },
+    {
+        id: "zone_key_light",
+        label: "ZONE KEY LIGHT",
+        properties: [
             {
                 label: "Key Light",
                 type: "dropdown",
@@ -272,7 +326,7 @@ const GROUPS = [
             {
                 label: "Light Intensity",
                 type: "number-draggable",
-                min: 0,
+                min: -40,
                 max: 40,
                 step: 0.01,
                 decimals: 2,
@@ -324,7 +378,13 @@ const GROUPS = [
                 decimals: 2,
                 propertyID: "keyLight.shadowMaxDistance",
                 showPropertyRule: { "keyLightMode": "enabled" },
-            },
+            }    
+        ]
+    },    
+    {
+        id: "zone_skybox",
+        label: "ZONE SKYBOX",
+        properties: [
             {
                 label: "Skybox",
                 type: "dropdown",
@@ -342,7 +402,13 @@ const GROUPS = [
                 type: "string",
                 propertyID: "skybox.url",
                 showPropertyRule: { "skyboxMode": "enabled" },
-            },
+            }
+        ]
+    },
+    {
+        id: "zone_ambient_light",
+        label: "ZONE AMBIENT LIGHT",
+        properties: [
             {
                 label: "Ambient Light",
                 type: "dropdown",
@@ -352,7 +418,7 @@ const GROUPS = [
             {
                 label: "Ambient Intensity",
                 type: "number-draggable",
-                min: 0,
+                min: -200,
                 max: 200,
                 step: 0.1,
                 decimals: 2,
@@ -371,7 +437,13 @@ const GROUPS = [
                              className: "black", onClick: copySkyboxURLToAmbientURL } ],
                 propertyID: "copyURLToAmbient",
                 showPropertyRule: { "ambientLightMode": "enabled" },
-            },
+            }
+        ]
+    },
+    {
+        id: "zone_haze",
+        label: "ZONE HAZE",
+        properties: [
             {
                 label: "Haze",
                 type: "dropdown",
@@ -454,7 +526,13 @@ const GROUPS = [
                 decimals: 0,
                 propertyID: "haze.hazeGlareAngle",
                 showPropertyRule: { "hazeMode": "enabled" },
-            },
+            }
+        ]
+    },
+    {
+        id: "zone_bloom",
+        label: "ZONE BLOOM",
+        properties: [
             {
                 label: "Bloom",
                 type: "dropdown",
@@ -490,7 +568,13 @@ const GROUPS = [
                 decimals: 3,
                 propertyID: "bloom.bloomSize",
                 showPropertyRule: { "bloomMode": "enabled" },
-            },
+            }
+        ]
+    },
+    {
+        id: "zone_avatar_priority",
+        label: "ZONE AVATAR PRIORITY",
+        properties: [
             {
                 label: "Avatar Priority",
                 type: "dropdown",
@@ -503,12 +587,11 @@ const GROUPS = [
                 options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
                 propertyID: "screenshare",
             }
-
         ]
     },
     {
         id: "model",
-        addToGroup: "base",
+        label: "MODEL",
         properties: [
             {
                 label: "Model",
@@ -548,7 +631,7 @@ const GROUPS = [
                 propertyID: "animation.loop",
             },
             {
-                label: "Allow Transition",
+                label: "Allow Translation",
                 type: "bool",
                 propertyID: "animation.allowTranslation",
             },
@@ -593,12 +676,12 @@ const GROUPS = [
                 label: "Group Culled",
                 type: "bool",
                 propertyID: "groupCulled",
-            },
+            }
         ]
     },
     {
         id: "image",
-        addToGroup: "base",
+        label: "IMAGE",
         properties: [
             {
                 label: "Image",
@@ -612,6 +695,16 @@ const GROUPS = [
                 propertyID: "imageColor",
                 propertyName: "color", // actual entity property name
             },
+            {
+                label: "Alpha",
+                type: "number-draggable",
+                min: 0,
+                max: 1,
+                step: 0.01,
+                decimals: 2,
+                propertyID: "imageAlpha",
+                propertyName: "alpha",
+            },            
             {
                 label: "Emissive",
                 type: "bool",
@@ -636,12 +729,12 @@ const GROUPS = [
                 label: "Keep Aspect Ratio",
                 type: "bool",
                 propertyID: "keepAspectRatio",
-            },
+            }
         ]
     },
     {
         id: "web",
-        addToGroup: "base",
+        label: "WEB",
         properties: [
             {
                 label: "Source",
@@ -676,17 +769,38 @@ const GROUPS = [
                 decimals: 0,
                 propertyID: "maxFPS",
             },
+            {
+                label: "Billboard Mode",
+                type: "dropdown",
+                options: { none: "None", yaw: "Yaw", full: "Full"},
+                propertyID: "webBillboardMode",
+                propertyName: "billboardMode", // actual entity property name
+            },
+            {
+                label: "Input Mode",
+                type: "dropdown",
+                options: {
+                    touch: "Touch events",
+                    mouse: "Mouse events"
+                },
+                propertyID: "inputMode",
+            },
+            {
+                label: "Focus Highlight",
+                type: "bool",
+                propertyID: "showKeyboardFocusHighlight",
+            },            
             {
                 label: "Script URL",
                 type: "string",
                 propertyID: "scriptURL",
                 placeholder: "URL",
-            },
+            }
         ]
     },
     {
         id: "light",
-        addToGroup: "base",
+        label: "LIGHT",
         properties: [
             {
                 label: "Light Color",
@@ -697,7 +811,7 @@ const GROUPS = [
             {
                 label: "Intensity",
                 type: "number-draggable",
-                min: 0,
+                min: -1000,
                 max: 10000,
                 step: 0.1,
                 decimals: 2,
@@ -732,12 +846,12 @@ const GROUPS = [
                 step: 0.01,
                 decimals: 2,
                 propertyID: "cutoff",
-            },
+            }
         ]
     },
     {
         id: "material",
-        addToGroup: "base",
+        label: "MATERIAL",
         properties: [
             {
                 label: "Material URL",
@@ -806,12 +920,12 @@ const GROUPS = [
                 label: "Material Repeat",
                 type: "bool",
                 propertyID: "materialRepeat",
-            },
+            }
         ]
     },
     {
         id: "grid",
-        addToGroup: "base",
+        label: "GRID",
         properties: [
             {
                 label: "Color",
@@ -839,12 +953,12 @@ const GROUPS = [
                 step: 0.01,
                 decimals: 2,
                 propertyID: "minorGridEvery",
-            },
+            }
         ]
     },
     {
         id: "particles",
-        addToGroup: "base",
+        label: "PARTICLES",
         properties: [
             {
                 label: "Emit",
@@ -870,13 +984,12 @@ const GROUPS = [
                 type: "texture",
                 propertyID: "particleTextures",
                 propertyName: "textures", // actual entity property name
-            },
+            }
         ]
     },
     {
         id: "particles_emit",
-        label: "EMIT",
-        isMinor: true,
+        label: "PARTICLES EMIT",
         properties: [
             {
                 label: "Emit Rate",
@@ -943,13 +1056,12 @@ const GROUPS = [
                 label: "Trails",
                 type: "bool",
                 propertyID: "emitterShouldTrail",
-            },
+            }
         ]
     },
     {
         id: "particles_size",
-        label: "SIZE",
-        isMinor: true,
+        label: "PARTICLES SIZE",
         properties: [
             {
                 type: "triple",
@@ -978,7 +1090,7 @@ const GROUPS = [
                         decimals: 2,
                         propertyID: "radiusFinish",
                         fallbackProperty: "particleRadius",
-                    },
+                    }
                 ]
             },
             {
@@ -987,13 +1099,12 @@ const GROUPS = [
                 step: 0.01,
                 decimals: 2,
                 propertyID: "radiusSpread",
-            },
+            }
         ]
     },
     {
         id: "particles_color",
-        label: "COLOR",
-        isMinor: true,
+        label: "PARTICLES COLOR",
         properties: [
             {
                 type: "triple",
@@ -1017,7 +1128,7 @@ const GROUPS = [
                         type: "color",
                         propertyID: "colorFinish",
                         fallbackProperty: "color",
-                    },
+                    }
                 ]
             },
             {
@@ -1025,13 +1136,6 @@ const GROUPS = [
                 type: "color",
                 propertyID: "colorSpread",
             },
-        ]
-    },
-    {
-        id: "particles_alpha",
-        label: "ALPHA",
-        isMinor: true,
-        properties: [
             {
                 type: "triple",
                 label: "Alpha",
@@ -1059,7 +1163,7 @@ const GROUPS = [
                         decimals: 3,
                         propertyID: "alphaFinish",
                         fallbackProperty: "alpha",
-                    },
+                    }
                 ]
             },
             {
@@ -1068,13 +1172,12 @@ const GROUPS = [
                 step: 0.001,
                 decimals: 3,
                 propertyID: "alphaSpread",
-            },
+            }
         ]
     },
     {
-        id: "particles_acceleration",
-        label: "ACCELERATION",
-        isMinor: true,
+        id: "particles_behavior",
+        label: "PARTICLES BEHAVIOR",
         properties: [
             {
                 label: "Emit Acceleration",
@@ -1094,13 +1197,6 @@ const GROUPS = [
                 subLabels: [ "x", "y", "z" ],
                 propertyID: "accelerationSpread",
             },
-        ]
-    },
-    {
-        id: "particles_spin",
-        label: "SPIN",
-        isMinor: true,
-        properties: [
             {
                 type: "triple",
                 label: "Spin",
@@ -1134,7 +1230,7 @@ const GROUPS = [
                         unit: "deg",
                         propertyID: "spinFinish",
                         fallbackProperty: "particleSpin",
-                    },
+                    }
                 ]
             },
             {
@@ -1150,13 +1246,12 @@ const GROUPS = [
                 label: "Rotate with Entity",
                 type: "bool",
                 propertyID: "rotateWithEntity",
-            },
+            }
         ]
     },
     {
         id: "particles_constraints",
-        label: "CONSTRAINTS",
-        isMinor: true,
+        label: "PARTICLES CONSTRAINTS",
         properties: [
             {
                 type: "triple",
@@ -1180,7 +1275,7 @@ const GROUPS = [
                         multiplier: DEGREES_TO_RADIANS,
                         unit: "deg",
                         propertyID: "polarFinish",
-                    },
+                    }
                 ],
             },
             {
@@ -1205,7 +1300,7 @@ const GROUPS = [
                         multiplier: DEGREES_TO_RADIANS,
                         unit: "deg",
                         propertyID: "azimuthFinish",
-                    },
+                    }
                 ]
             }
         ]
@@ -1305,7 +1400,7 @@ const GROUPS = [
                 buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid },
                            { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ],
                 propertyID: "alignToGrid",
-            },
+            }
         ]
     },
     {
@@ -1375,6 +1470,18 @@ const GROUPS = [
                 type: "bool",
                 propertyID: "ignorePickIntersection",
             },
+            {
+                label: "Lifetime",
+                type: "number",
+                unit: "s",
+                propertyID: "lifetime",
+            }
+        ]
+    },
+    {
+        id: "scripts",
+        label: "SCRIPTS",
+        properties: [
             {
                 label: "Script",
                 type: "string",
@@ -1397,12 +1504,6 @@ const GROUPS = [
                 propertyID: "serverScriptStatus",
                 selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION,
             },
-            {
-                label: "Lifetime",
-                type: "number",
-                unit: "s",
-                propertyID: "lifetime",
-            },
             {
                 label: "User Data",
                 type: "textarea",
@@ -1410,7 +1511,7 @@ const GROUPS = [
                            { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor },
                            { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ],
                 propertyID: "userData",
-            },
+            }
         ]
     },
     {
@@ -1475,7 +1576,7 @@ const GROUPS = [
                 label: "Dynamic",
                 type: "bool",
                 propertyID: "dynamic",
-            },
+            }
         ]
     },
     {
@@ -1551,26 +1652,37 @@ const GROUPS = [
                 unit: "m/s<sup>2</sup>",
                 propertyID: "gravity",
             },
+            {
+                label: "Acceleration",
+                type: "vec3",
+                vec3Type: "xyz",
+                subLabels: [ "x", "y", "z" ],
+                step: 0.1,
+                decimals: 4,
+                unit: "m/s<sup>2</sup>",
+                propertyID: "acceleration",
+            }
         ]
     },
 ];
 
 const GROUPS_PER_TYPE = {
-  None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
-  Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ],
-  Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ],
-  Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ],
-  Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ],
-  Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ],
-  Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ],
-  Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ],
-  Material: [ 'base', 'material', 'spatial', 'behavior' ],
-  ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', 
-                    'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ],
-  PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
-  PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
-  Grid: [ 'base', 'grid', 'spatial', 'behavior', 'physics' ],
-  Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ],
+  None: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Shape: [ 'base', 'shape', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Text: [ 'base', 'text', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Zone: [ 'base', 'zone', 'zone_key_light', 'zone_skybox', 'zone_ambient_light', 'zone_haze', 
+            'zone_bloom', 'zone_avatar_priority', 'spatial', 'behavior', 'scripts', 'physics' ],
+  Model: [ 'base', 'model', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Image: [ 'base', 'image', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Web: [ 'base', 'web', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Light: [ 'base', 'light', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Material: [ 'base', 'material', 'spatial', 'behavior', 'scripts', 'physics' ],
+  ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 
+                    'particles_behavior', 'particles_constraints', 'spatial', 'behavior', 'scripts', 'physics' ],
+  PolyLine: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  PolyVox: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
+  Grid: [ 'base', 'grid', 'spatial', 'behavior', 'scripts', 'physics' ],
+  Multiple: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ],
 };
 
 const EDITOR_TIMEOUT_DURATION = 1500;
@@ -1637,7 +1749,7 @@ let selectedEntityIDs = new Set();
 let currentSelections = [];
 let createAppTooltip = new CreateAppTooltip();
 let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL;
-
+let zonesList = [];
 
 function createElementFromHTML(htmlString) {
     let elTemplate = document.createElement('template');
@@ -1654,7 +1766,7 @@ function isFlagSet(value, flag) {
  */
 
 function getPropertyInputElement(propertyID) {
-    let property = properties[propertyID];          
+    let property = properties[propertyID];
     switch (property.data.type) {
         case 'string':
         case 'number':
@@ -1663,6 +1775,8 @@ function getPropertyInputElement(propertyID) {
         case 'textarea':
         case 'texture':
             return property.elInput;
+        case 'multipleZonesSelection':
+            return property.elInput;
         case 'number-draggable':
             return property.elNumber.elInput;
         case 'rect':
@@ -1703,12 +1817,7 @@ function disableChildren(el, selector) {
 function enableProperties() {
     enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR);
     enableChildren(document, ".colpick");
-    
-    let elLocked = getPropertyInputElement("locked");
-    if (elLocked.checked === false) {
-        removeStaticUserData();
-        removeStaticMaterialData();
-    }
+    enableAllMultipleZoneSelector();
 }
 
 function disableProperties() {
@@ -1717,16 +1826,7 @@ function disableProperties() {
     for (let pickKey in colorPickers) {
         colorPickers[pickKey].colpickHide();
     }
-    
-    let elLocked = getPropertyInputElement("locked");
-    if (elLocked.checked === true) {
-        if ($('#property-userData-editor').css('display') === "block") {
-            showStaticUserData();
-        }
-        if ($('#property-materialData-editor').css('display') === "block") {
-            showStaticMaterialData();
-        }
-    }
+    disableAllMultipleZoneSelector();
 }
 
 function showPropertyElement(propertyID, show) {
@@ -1739,7 +1839,7 @@ function setPropertyVisibility(property, visible) {
 
 function resetProperties() {
     for (let propertyID in properties) { 
-        let property = properties[propertyID];      
+        let property = properties[propertyID];
         let propertyData = property.data;
         
         switch (propertyData.type) {
@@ -1801,6 +1901,12 @@ function resetProperties() {
                 setTextareaScrolling(property.elInput);
                 break;
             }
+            case 'multipleZonesSelection': {
+                property.elInput.classList.remove('multi-diff');
+                property.elInput.value = "[]";
+                setZonesSelectionData(property.elInput, false);
+                break;
+            }
             case 'icon': {
                 property.elSpan.style.display = "none";
                 break;
@@ -1816,7 +1922,7 @@ function resetProperties() {
                 break;
             }
         }
-        
+
         let showPropertyRules = properties[propertyID].showPropertyRules;
         if (showPropertyRules !== undefined) {
             for (let propertyToHide in showPropertyRules) {
@@ -1838,9 +1944,15 @@ function resetServerScriptStatus() {
 function showGroupsForType(type) {
     if (type === "Box" || type === "Sphere") {
         showGroupsForTypes(["Shape"]);
+        showOnTheSamePage(["Shape"]);
         return;
     }
+    if (type === "None") {
+        showGroupsForTypes(["None"]);
+        return;        
+    }
     showGroupsForTypes([type]);
+    showOnTheSamePage([type]);
 }
 
 function getGroupsForTypes(types) {
@@ -1854,9 +1966,15 @@ function getGroupsForTypes(types) {
 function showGroupsForTypes(types) {
     Object.entries(elGroups).forEach(([groupKey, elGroup]) => {
         if (types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { return hasGroup; })) {
-            elGroup.style.display = "block";
+            elGroup.style.display = "none";
+            if (types !== "None") {
+                document.getElementById("tab-" + groupKey).style.display = "block";
+            } else {
+                document.getElementById("tab-" + groupKey).style.display = "none";
+            }
         } else {
             elGroup.style.display = "none";
+            document.getElementById("tab-" + groupKey).style.display = "none";
         }
     });
 }
@@ -2253,7 +2371,7 @@ function updateCheckedSubProperty(propertyName, propertyMultiValue, subPropertyE
  * PROPERTY ELEMENT CREATION FUNCTIONS
  */
 
-function createStringProperty(property, elProperty) {    
+function createStringProperty(property, elProperty) {
     let elementID = property.elementID;
     let propertyData = property.data;
     
@@ -2266,12 +2384,12 @@ function createStringProperty(property, elProperty) {
                ${propertyData.readOnly ? 'readonly' : ''}/>
         `);
 
-    
+
     elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
     if (propertyData.onChange !== undefined) {
         elInput.addEventListener('change', propertyData.onChange);
     }
-    
+
 
     let elMultiDiff = document.createElement('span');
     elMultiDiff.className = "multi-diff";
@@ -2282,7 +2400,7 @@ function createStringProperty(property, elProperty) {
     if (propertyData.buttons !== undefined) {
         addButtons(elProperty, elementID, propertyData.buttons, false);
     }
-    
+
     return elInput;
 }
 
@@ -2290,9 +2408,9 @@ function createBoolProperty(property, elProperty) {
     let propertyName = property.name;
     let elementID = property.elementID;
     let propertyData = property.data;
-    
+
     elProperty.className = "checkbox";
-                        
+
     if (propertyData.glyph !== undefined) {
         let elSpan = document.createElement('span');
         elSpan.innerHTML = propertyData.glyph;
@@ -2521,7 +2639,7 @@ function createVec3Property(property, elProperty) {
     let propertyData = property.data;
 
     elProperty.className = propertyData.vec3Type + " fstuple";
-    
+
     let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]);
     let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]);
     let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]);
@@ -2536,7 +2654,7 @@ function createVec3Property(property, elProperty) {
     elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x'));
     elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y'));
     elNumberZ.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'z'));
-    
+
     let elResult = [];
     elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX;
     elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY;
@@ -2544,11 +2662,11 @@ function createVec3Property(property, elProperty) {
     return elResult;
 }
 
-function createVec2Property(property, elProperty) {  
+function createVec2Property(property, elProperty) {
     let propertyData = property.data;
     
     elProperty.className = propertyData.vec2Type + " fstuple";
-                        
+
     let elTuple = document.createElement('div');
     elTuple.className = "tuple";
     
@@ -2585,19 +2703,19 @@ function createColorProperty(property, elProperty) {
     let propertyName = property.name;
     let elementID = property.elementID;
     let propertyData = property.data;
-    
+
     elProperty.className += " rgb fstuple";
-    
+
     let elColorPicker = document.createElement('div');
     elColorPicker.className = "color-picker";
     elColorPicker.setAttribute("id", elementID);
-    
+
     let elTuple = document.createElement('div');
     elTuple.className = "tuple";
-    
+
     elProperty.appendChild(elColorPicker);
     elProperty.appendChild(elTuple);
-    
+
     if (propertyData.min === undefined) {
         propertyData.min = COLOR_MIN;
     }
@@ -2607,19 +2725,19 @@ function createColorProperty(property, elProperty) {
     if (propertyData.step === undefined) {
         propertyData.step = COLOR_STEP;
     }
-    
+
     let elNumberR = createTupleNumberInput(property, "red");
     let elNumberG = createTupleNumberInput(property, "green");
     let elNumberB = createTupleNumberInput(property, "blue");
     elTuple.appendChild(elNumberR.elDiv);
     elTuple.appendChild(elNumberG.elDiv);
     elTuple.appendChild(elNumberB.elDiv);
-    
+
     let valueChangeFunction = createEmitColorPropertyUpdateFunction(property);
     elNumberR.setValueChangeFunction(valueChangeFunction);
     elNumberG.setValueChangeFunction(valueChangeFunction);
     elNumberB.setValueChangeFunction(valueChangeFunction);
-    
+
     let colorPickerID = "#" + elementID;
     colorPickers[colorPickerID] = $(colorPickerID).colpick({
         colorScheme: 'dark',
@@ -2648,7 +2766,7 @@ function createColorProperty(property, elProperty) {
             }
         }
     });
-    
+
     let elResult = [];
     elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker;
     elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR;
@@ -2673,26 +2791,26 @@ function createDropdownProperty(property, propertyID, elProperty) {
         option.text = propertyData.options[optionKey];
         elInput.add(option);
     }
-    
+
     elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
 
     elProperty.appendChild(elInput);
-    
+
     return elInput;
 }
 
-function createTextareaProperty(property, elProperty) {   
+function createTextareaProperty(property, elProperty) {
     let elementID = property.elementID;
     let propertyData = property.data;
-    
+
     elProperty.className = "textarea";
-    
+
     let elInput = document.createElement('textarea');
     elInput.setAttribute("id", elementID);
     if (propertyData.readOnly) {
         elInput.readOnly = true;
-    }                   
-    
+    }
+
     elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
 
     let elMultiDiff = document.createElement('span');
@@ -2700,42 +2818,42 @@ function createTextareaProperty(property, elProperty) {
 
     elProperty.appendChild(elInput);
     elProperty.appendChild(elMultiDiff);
-                        
+
     if (propertyData.buttons !== undefined) {
         addButtons(elProperty, elementID, propertyData.buttons, true);
     }
-    
+
     return elInput;
 }
 
-function createIconProperty(property, elProperty) { 
+function createIconProperty(property, elProperty) {
     let elementID = property.elementID;
 
     elProperty.className = "value";
-    
+
     let elSpan = document.createElement('span');
     elSpan.setAttribute("id", elementID + "-icon");
     elSpan.className = 'icon';
 
     elProperty.appendChild(elSpan);
-    
+
     return elSpan;
 }
 
-function createTextureProperty(property, elProperty) { 
+function createTextureProperty(property, elProperty) {
     let elementID = property.elementID;
-    
+
     elProperty.className = "texture";
-    
+
     let elDiv = document.createElement("div");
     let elImage = document.createElement("img");
     elDiv.className = "texture-image no-texture";
     elDiv.appendChild(elImage);
-    
+
     let elInput = document.createElement('input');
     elInput.setAttribute("id", elementID);
     elInput.setAttribute("type", "text"); 
-    
+
     let imageLoad = function(url) {
         elDiv.style.display = null;
         if (url.slice(0, 5).toLowerCase() === "atp:/") {
@@ -2772,7 +2890,7 @@ function createTextureProperty(property, elProperty) {
     elMultiDiff.className = "multi-diff";
     elProperty.appendChild(elMultiDiff);
     elProperty.appendChild(elDiv);
-   
+
     let elResult = [];
     elResult[TEXTURE_ELEMENTS.IMAGE] = elImage;
     elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput;
@@ -2788,23 +2906,23 @@ function createButtonsProperty(property, elProperty) {
     if (propertyData.buttons !== undefined) {
         addButtons(elProperty, elementID, propertyData.buttons, false);
     }
-    
+
     return elProperty;
 }
 
 function createDynamicMultiselectProperty(property, elProperty) {
     let elementID = property.elementID;
     let propertyData = property.data;
-        
+
     elProperty.className = "dynamic-multiselect";
-    
+
     let elDivOptions = document.createElement('div');
     elDivOptions.setAttribute("id", elementID + "-options");
     elDivOptions.style = "overflow-y:scroll;max-height:160px;";
-    
+
     let elDivButtons = document.createElement('div');
     elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons");
-        
+
     let elLabel = document.createElement('label');
     elLabel.innerText = "No Options";
     elDivOptions.appendChild(elLabel);
@@ -2812,10 +2930,10 @@ function createDynamicMultiselectProperty(property, elProperty) {
     let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget }, 
                     { id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ];
     addButtons(elDivButtons, elementID, buttons, false);
-    
+
     elProperty.appendChild(elDivOptions);
     elProperty.appendChild(elDivButtons);
-    
+
     return elDivOptions;
 }
 
@@ -2833,13 +2951,13 @@ function createTupleNumberInput(property, subLabel) {
     let propertyElementID = property.elementID;
     let propertyData = property.data;
     let elementID = propertyElementID + "-" + subLabel.toLowerCase();
-    
+
     let elLabel = document.createElement('label');
     elLabel.className = "sublabel " + subLabel;
     elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1);
     elLabel.setAttribute("for", elementID);
     elLabel.style.visibility = "visible";
-    
+
     let dragStartFunction = createDragStartFunction(property);
     let dragEndFunction = createDragEndFunction(property);
     let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, 
@@ -2847,14 +2965,14 @@ function createTupleNumberInput(property, subLabel) {
     elDraggableNumber.elInput.setAttribute("id", elementID);
     elDraggableNumber.elDiv.className += " fstuple";
     elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
-    
+
     return elDraggableNumber;
 }
 
 function addButtons(elProperty, propertyID, buttons, newRow) {
     let elDiv = document.createElement('div');
     elDiv.className = "row";
-    
+
     buttons.forEach(function(button) {
         let elButton = document.createElement('input');
         elButton.className = button.className;
@@ -2876,7 +2994,7 @@ function addButtons(elProperty, propertyID, buttons, newRow) {
 }
 
 function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) {
-    let property = { 
+    let property = {
         data: propertyData, 
         elementID: propertyElementID, 
         name: propertyName,
@@ -2938,6 +3056,10 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI
             property.elInput = createTextareaProperty(property, elProperty);
             break;
         }
+        case 'multipleZonesSelection': {
+            property.elInput = createZonesSelection(property, elProperty);
+            break;            
+        }
         case 'icon': {
             property.elSpan = createIconProperty(property, elProperty);
             break;
@@ -3167,19 +3289,6 @@ function hideUserDataSaved() {
     $('#property-userData-saved').hide();
 }
 
-function showStaticUserData() {
-    if (editor !== null) {
-        let $propertyUserDataStatic = $('#property-userData-static');
-        $propertyUserDataStatic.show();
-        $propertyUserDataStatic.css('height', $('#property-userData-editor').height());
-        $propertyUserDataStatic.text(editor.getText());
-    }
-}
-
-function removeStaticUserData() {
-    $('#property-userData-static').hide();
-}
-
 function setEditorJSON(json) {
     editor.set(json);
     if (editor.hasOwnProperty('expandAll')) {
@@ -3332,19 +3441,6 @@ function hideMaterialDataSaved() {
     $('#property-materialData-saved').hide();
 }
 
-function showStaticMaterialData() {
-    if (materialEditor !== null) {
-        let $propertyMaterialDataStatic = $('#property-materialData-static');
-        $propertyMaterialDataStatic.show();
-        $propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height());
-        $propertyMaterialDataStatic.text(materialEditor.getText());
-    }
-}
-
-function removeStaticMaterialData() {
-    $('#property-materialData-static').hide();
-}
-
 function setMaterialEditorJSON(json) {
     materialEditor.set(json);
     if (materialEditor.hasOwnProperty('expandAll')) {
@@ -3456,6 +3552,175 @@ function setTextareaScrolling(element) {
     element.setAttribute("scrolling", isScrolling ? "true" : "false");
 }
 
+/**
+ * ZONE SELECTOR FUNCTIONS
+ */
+
+function enableAllMultipleZoneSelector() {
+    let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection");
+    let i, propId;
+    for (i = 0; i < allMultiZoneSelectors.length; i++) {
+        propId = allMultiZoneSelectors[i].id;
+        displaySelectedZones(propId, true);
+    }
+} 
+
+function disableAllMultipleZoneSelector() {
+    let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection");
+    let i, propId;
+    for (i = 0; i < allMultiZoneSelectors.length; i++) {
+        propId = allMultiZoneSelectors[i].id;
+        displaySelectedZones(propId, false);
+    }
+} 
+
+function requestZoneList() {
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "zoneListRequest"
+    }));
+}
+
+function addZoneToZonesSelection(propertyId) {
+    let hiddenField = document.getElementById(propertyId);
+    if (JSON.stringify(hiddenField.value) === '"undefined"') {
+        hiddenField.value = "[]";
+    }
+    let selectedZones = JSON.parse(hiddenField.value);
+    let zoneToAdd = document.getElementById("zones-select-" + propertyId).value;
+    if (!selectedZones.includes(zoneToAdd)) {
+        selectedZones.push(zoneToAdd);
+    }
+    hiddenField.value = JSON.stringify(selectedZones);
+    displaySelectedZones(propertyId, true);
+    let propertyName = propertyId.replace("property-", "");
+    updateProperty(propertyName, selectedZones, false);
+}
+
+function removeZoneFromZonesSelection(propertyId, zoneId) {
+    let hiddenField = document.getElementById(propertyId);
+    if (JSON.stringify(hiddenField.value) === '"undefined"') {
+        hiddenField.value = "[]";
+    }
+    let selectedZones = JSON.parse(hiddenField.value);
+    let index = selectedZones.indexOf(zoneId);
+    if (index > -1) {
+      selectedZones.splice(index, 1);
+    }
+    hiddenField.value = JSON.stringify(selectedZones);
+    displaySelectedZones(propertyId, true);
+    let propertyName = propertyId.replace("property-", "");
+    updateProperty(propertyName, selectedZones, false);
+}
+
+function displaySelectedZones(propertyId, isEditable) {
+    let i,j, name, listedZoneInner, hiddenData, isMultiple;
+    hiddenData = document.getElementById(propertyId).value;
+    if (JSON.stringify(hiddenData) === '"undefined"') {
+        isMultiple = true;
+        hiddenData = "[]";
+    } else {
+        isMultiple = false;  
+    }
+    let selectedZones = JSON.parse(hiddenData);
+    listedZoneInner = "<table>";
+    if (selectedZones.length === 0) {
+        if (!isMultiple) {
+            listedZoneInner += "<tr><td class='zoneItem'>&nbsp;</td><td>&nbsp;</td></tr>";
+        } else {
+            listedZoneInner += "<tr><td class='zoneItem'>[ WARNING: Any changes will apply to all. ]</td><td>&nbsp;</td></tr>";
+        }
+    } else {
+        for (i = 0; i < selectedZones.length; i++) {
+            name = "{ERROR: NOT FOUND}";
+            for (j = 0; j < zonesList.length; j++) {
+                if (selectedZones[i] === zonesList[j].id) {
+                    if (zonesList[j].name !== "") {
+                        name = zonesList[j].name;
+                    } else {
+                        name = zonesList[j].id;
+                    }
+                    break;
+                }
+            }
+            if (isEditable) {
+                listedZoneInner += "<tr><td class='zoneItem'>" + name + "</td><td><a href='#' onClick='removeZoneFromZonesSelection(" + '"' + propertyId + '"' + ", " + '"' + selectedZones[i] + '"' + ");' >";
+                listedZoneInner += "<img src='../../../html/css/img/remove_icon.png'></a></td></tr>";
+            } else {
+                listedZoneInner += "<tr><td class='zoneItem'>" + name + "</td><td>&nbsp;</td></tr>";
+            }
+        }
+    }
+    listedZoneInner += "</table>";
+    document.getElementById("selected-zones-" + propertyId).innerHTML = listedZoneInner; 
+    if (isEditable) {
+        document.getElementById("multiZoneSelTools-" + propertyId).style.display = "block";
+    } else {
+        document.getElementById("multiZoneSelTools-" + propertyId).style.display = "none";
+    }
+}
+
+function createZonesSelection(property, elProperty) {
+    let elementID = property.elementID;
+    requestZoneList();
+    elProperty.className = "multipleZonesSelection";
+    let elInput = document.createElement('input');
+    elInput.setAttribute("id", elementID);
+    elInput.setAttribute("type", "hidden");
+    elInput.className = "hiddenMultiZonesSelection";
+
+    let elZonesSelector = document.createElement('div');
+    elZonesSelector.setAttribute("id", "zones-selector-" + elementID);
+
+    let elMultiDiff = document.createElement('span');
+    elMultiDiff.className = "multi-diff";
+
+    elProperty.appendChild(elInput);
+    elProperty.appendChild(elZonesSelector);
+    elProperty.appendChild(elMultiDiff);
+
+    return elInput;
+}
+
+function setZonesSelectionData(element, isEditable) {
+    let zoneSelectorContainer = document.getElementById("zones-selector-" + element.id);
+    let zoneSelector = "<div class='multiZoneSelToolbar' id='multiZoneSelTools-" + element.id + "'><select class = 'zoneSelect' id='zones-select-" + element.id + "' >";
+    let i, name;
+    for (i = 0; i < zonesList.length; i++) {
+        if (zonesList[i].name === "") {
+            name = zonesList[i].id;
+        } else {
+            name = zonesList[i].name;
+        }
+        zoneSelector += "<option value='" + zonesList[i].id + "'>" + name + "</option>";
+    }   
+    zoneSelector += "</select>&nbsp;<a href='#' id='zones-select-add-" + element.id + "' onClick='addZoneToZonesSelection(" + '"' + element.id + '"' + ");' >";
+    zoneSelector += "<img style='vertical-align:top' src='../../../html/css/img/add_icon.png'></a></div>";
+    zoneSelector += "<div class='selected-zone-container' id='selected-zones-" + element.id + "'></div>";
+    zoneSelectorContainer.innerHTML = zoneSelector;
+    displaySelectedZones(element.id, isEditable);
+}
+
+function updateAllZoneSelect() {
+    let allZoneSelects = document.querySelectorAll(".zoneSelect");
+    let i, j, name, propId;
+    for (i = 0; i < allZoneSelects.length; i++) {
+        allZoneSelects[i].options.length = 0;
+        for (j = 0; j < zonesList.length; j++) {
+            if (zonesList[j].name === "") {
+                name = zonesList[j].id;
+            } else {
+                name = zonesList[j].name;
+            }
+            allZoneSelects[i].options[j] = new Option(name, zonesList[j].id, false , false);
+        }
+        propId = allZoneSelects[i].id.replace("zones-select-", "");
+        if (document.getElementById("multiZoneSelTools-" + propId).style.display === "block") {
+            displaySelectedZones(propId, true);
+        } else {
+            displaySelectedZones(propId, false);
+        }
+    }
+}
 
 /**
  * MATERIAL TARGET FUNCTIONS
@@ -3467,15 +3732,15 @@ function requestMaterialTarget() {
         entityID: getFirstSelectedID(),
     }));
 }
- 
+
 function setMaterialTargetData(materialTargetData) {
     let elDivOptions = getPropertyInputElement("parentMaterialName");
     resetDynamicMultiselectProperty(elDivOptions);
-    
+
     if (materialTargetData === undefined) {
         return;
     }
-    
+
     elDivOptions.firstChild.style.display = "none"; // hide "No Options" text
     elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons
 
@@ -3483,7 +3748,7 @@ function setMaterialTargetData(materialTargetData) {
     for (let i = 0; i < numMeshes; ++i) {
         addMaterialTarget(elDivOptions, i, false);
     }
-    
+
     let materialNames = materialTargetData.materialNames;
     let materialNamesAdded = [];
     for (let i = 0; i < materialNames.length; ++i) {
@@ -3493,7 +3758,7 @@ function setMaterialTargetData(materialTargetData) {
             materialNamesAdded.push(materialName);
         }
     }
-    
+
     materialTargetPropertyUpdate(elDivOptions.propertyValue);
 }
 
@@ -3501,12 +3766,12 @@ function addMaterialTarget(elDivOptions, targetID, isMaterialName) {
     let elementID = elDivOptions.getAttribute("id");
     elementID += isMaterialName ? "-material-" : "-mesh-";
     elementID += targetID;
-    
+
     let elDiv = document.createElement('div');
     elDiv.className = "materialTargetDiv";
     elDiv.onclick = onToggleMaterialTarget;
     elDivOptions.appendChild(elDiv);
-    
+
     let elInput = document.createElement('input');
     elInput.className = "materialTargetInput";
     elInput.setAttribute("type", "checkbox");
@@ -3514,12 +3779,12 @@ function addMaterialTarget(elDivOptions, targetID, isMaterialName) {
     elInput.setAttribute("targetID", targetID);
     elInput.setAttribute("isMaterialName", isMaterialName);
     elDiv.appendChild(elInput);
-    
+
     let elLabel = document.createElement('label');
     elLabel.setAttribute("for", elementID);
     elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID;
     elDiv.appendChild(elLabel);
-    
+
     return elDiv;
 }
 
@@ -3552,7 +3817,7 @@ function clearAllMaterialTarget() {
 function sendMaterialTargetProperty() {
     let elDivOptions = getPropertyInputElement("parentMaterialName");   
     let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
-    
+
     let materialTargetList = [];
     for (let i = 0; i < elInputs.length; ++i) {
         let elInput = elInputs[i];
@@ -3565,26 +3830,26 @@ function sendMaterialTargetProperty() {
             }
         }
     }
-    
+
     let propertyValue = materialTargetList.join(",");
     if (propertyValue.length > 1) {
         propertyValue = "[" + propertyValue + "]";
     }
-    
+
     updateProperty("parentMaterialName", propertyValue, false);
 }
 
 function materialTargetPropertyUpdate(propertyValue) {
     let elDivOptions = getPropertyInputElement("parentMaterialName");
     let elInputs = elDivOptions.getElementsByClassName("materialTargetInput");
-    
+
     if (propertyValue.startsWith('[')) {
         propertyValue = propertyValue.substring(1, propertyValue.length);
     }
     if (propertyValue.endsWith(']')) {
         propertyValue = propertyValue.substring(0, propertyValue.length - 1);
     }
-    
+
     let materialTargets = propertyValue.split(",");
     for (let i = 0; i < elInputs.length; ++i) {
         let elInput = elInputs[i];
@@ -3595,7 +3860,7 @@ function materialTargetPropertyUpdate(propertyValue) {
         }
         elInput.checked = materialTargets.indexOf(materialTargetName) >= 0;
     }
-    
+
     elDivOptions.propertyValue = propertyValue;
 }
 
@@ -3629,7 +3894,9 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
     selectedEntityIDs = new Set(selections.map(selection => selection.id));
     const multipleSelections = currentSelections.length > 1;
     const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs);
-
+    
+    requestZoneList();
+    
     if (selections.length === 0) {
         deleteJSONEditor();
         deleteJSONMaterialEditor();
@@ -3674,6 +3941,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
 
         const shownGroups = getGroupsForTypes(entityTypes);
         showGroupsForTypes(entityTypes);
+        showOnTheSamePage(entityTypes);
 
         const lockedMultiValue = getMultiplePropertyValue('locked');
 
@@ -3683,7 +3951,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
         } else {
             enableProperties();
             disableSaveUserDataButton();
-            disableSaveMaterialDataButton()
+            disableSaveMaterialDataButton();
         }
 
         const certificateIDMultiValue = getMultiplePropertyValue('certificateID');
@@ -3839,6 +4107,15 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
                     setTextareaScrolling(property.elInput);
                     break;
                 }
+                case 'multipleZonesSelection': {
+                    property.elInput.value =  JSON.stringify(propertyValue);
+                    if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) {
+                        setZonesSelectionData(property.elInput, false);
+                    } else {
+                        setZonesSelectionData(property.elInput, true);
+                    }
+                    break;
+                }
                 case 'icon': {
                     property.elSpan.innerHTML = propertyData.icons[propertyValue];
                     property.elSpan.style.display = "inline-block";
@@ -3884,7 +4161,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
 
             }
         }
-        if (json !== null) {
+        if (json !== null && !lockedMultiValue.isMultiDiffValue && !lockedMultiValue.value) {
             if (editor === null) {
                 createJSONEditor();
             }
@@ -3916,7 +4193,7 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
 
             }
         }
-        if (materialJson !== null) {
+        if (materialJson !== null && !lockedMultiValue.isMultiDiffValue && !lockedMultiValue.value) {
             if (materialEditor === null) {
                 createJSONMaterialEditor();
             }
@@ -3950,44 +4227,35 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) {
 
 function loaded() {
     openEventBridge(function() {
-        let elPropertiesList = document.getElementById("properties-list");
-        
+        let elPropertiesList = document.getElementById("properties-pages");
+        let elTabs = document.getElementById("tabs");
+
         GROUPS.forEach(function(group) {
             let elGroup;
-            if (group.addToGroup !== undefined) {
-                let fieldset = document.getElementById("properties-" + group.addToGroup);
-                elGroup = document.createElement('div');
-                fieldset.appendChild(elGroup);
-            } else {
-                elGroup = document.createElement('div');
-                elGroup.className = 'section ' + (group.isMinor ? "minor" : "major");
-                elGroup.setAttribute("id", "properties-" + group.id);
-                elPropertiesList.appendChild(elGroup);
-            }       
+
+            elGroup = document.createElement('div');
+            elGroup.className = 'section ' + "major";
+            elGroup.setAttribute("id", "properties-" + group.id);
+            elPropertiesList.appendChild(elGroup);
 
             if (group.label !== undefined) {
                 let elLegend = document.createElement('div');
-                elLegend.className = "section-header";
-
-                elLegend.appendChild(createElementFromHTML(`<div class="label">${group.label}</div>`));
-
-                let elSpan = document.createElement('span');
-                elSpan.className = "collapse-icon";
-                elSpan.innerText = "M";
-                elLegend.appendChild(elSpan);
+                elLegend.className = "tab-section-header";
+                elLegend.appendChild(createElementFromHTML(`<div class="labelTabHeader">${group.label}</div>`));
                 elGroup.appendChild(elLegend);
+                elTabs.appendChild(createElementFromHTML('<button id="tab-'+ group.id +'" onclick="showPage(' + "'" + group.id  + "'" + ');"><img src="tabs/'+ group.id +'.png"></button>'));
             }
-                
+
             group.properties.forEach(function(propertyData) {
                 let propertyType = propertyData.type;
-                let propertyID = propertyData.propertyID;               
+                let propertyID = propertyData.propertyID;
                 let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID;
                 let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL;
                 let propertyElementID = "property-" + propertyID;
                 propertyElementID = propertyElementID.replace('.', '-');
-                
+
                 let elContainer, elLabel;
-                
+
                 if (propertyData.replaceID === undefined) {
                     // Create subheader, or create new property and append it.
                     if (propertyType === "sub-header") {
@@ -4077,15 +4345,15 @@ function loaded() {
                     property.elContainer = elContainer;
                     property.spaceMode = propertySpaceMode;
                     property.group_id = group.id;
-                    
+
                     if (property.type !== 'placeholder') {
                         properties[propertyID] = property;
-                    }           
+                    }
                     if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || 
                         propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') {
                         propertyRangeRequests.push(propertyID);
                     }
-                    
+
                     let showPropertyRule = propertyData.showPropertyRule;
                     if (showPropertyRule !== undefined) {
                         let dependentProperty = Object.keys(showPropertyRule)[0];
@@ -4100,15 +4368,12 @@ function loaded() {
                     }
                 }
             });
-            
+
             elGroups[group.id] = elGroup;
         });
 
-        let minorSections = document.querySelectorAll(".section.minor");
-        minorSections[minorSections.length - 1].className += " last";
-
         updateVisibleSpaceModeProperties();
-        
+
         if (window.EventBridge !== undefined) {
             EventBridge.scriptEventReceived.connect(function(data) {
                 data = JSON.parse(data);
@@ -4180,6 +4445,9 @@ function loaded() {
                     if (data.entityID === getFirstSelectedID()) {
                         setMaterialTargetData(data.materialTargetData);
                     }
+                } else if (data.type === 'zoneListRequest') {
+                    zonesList = data.zones;
+                    updateAllZoneSelect();
                 }
             });
 
@@ -4187,7 +4455,7 @@ function loaded() {
             EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' }));
             EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests }));
         }
-        
+
         // Server Script Status
         let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus');
         let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1];
@@ -4196,7 +4464,7 @@ function loaded() {
         let elServerScriptStatus = document.createElement('div');
         elServerScriptStatus.setAttribute("id", serverScriptStatusElementID);
         elServerScriptStatusContainer.appendChild(elServerScriptStatus);
-        
+
         // Server Script Error
         let elServerScripts = getPropertyInputElement("serverScripts");
         let elDiv = document.createElement('div');
@@ -4206,18 +4474,16 @@ function loaded() {
         elServerScriptError.setAttribute("id", serverScriptErrorElementID);
         elDiv.appendChild(elServerScriptError);
         elServerScriptStatusContainer.appendChild(elDiv);
-        
+
         let elScript = getPropertyInputElement("script");
         elScript.parentNode.className = "url refresh";
         elServerScripts.parentNode.className = "url refresh";
-            
+
         // User Data
         let userDataProperty = properties["userData"];
         let elUserData = userDataProperty.elInput;
         let userDataElementID = userDataProperty.elementID;
         elDiv = elUserData.parentNode;
-        let elStaticUserData = document.createElement('div');
-        elStaticUserData.setAttribute("id", userDataElementID + "-static");
         let elUserDataEditor = document.createElement('div');
         elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
         let elUserDataEditorStatus = document.createElement('div');
@@ -4226,17 +4492,14 @@ function loaded() {
         elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
         elUserDataSaved.innerText = "Saved!";
         elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
-        elDiv.insertBefore(elStaticUserData, elUserData);
         elDiv.insertBefore(elUserDataEditor, elUserData);
         elDiv.insertBefore(elUserDataEditorStatus, elUserData);
-        
+
         // Material Data
         let materialDataProperty = properties["materialData"];
         let elMaterialData = materialDataProperty.elInput;
         let materialDataElementID = materialDataProperty.elementID;
         elDiv = elMaterialData.parentNode;
-        let elStaticMaterialData = document.createElement('div');
-        elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
         let elMaterialDataEditor = document.createElement('div');
         elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
         let elMaterialDataEditorStatus = document.createElement('div');
@@ -4245,26 +4508,9 @@ function loaded() {
         elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
         elMaterialDataSaved.innerText = "Saved!";
         elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
-        elDiv.insertBefore(elStaticMaterialData, elMaterialData);
         elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
         elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
-        
-        // Collapsible sections
-        let elCollapsible = document.getElementsByClassName("collapse-icon");
 
-        let toggleCollapsedEvent = function(event) {
-            let element = this.parentNode.parentNode;
-            let isCollapsed = element.dataset.collapsed !== "true";
-            element.dataset.collapsed = isCollapsed ? "true" : false;
-            element.setAttribute("collapsed", isCollapsed ? "true" : "false");
-            this.textContent = isCollapsed ? "L" : "M";
-        };
-
-        for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) {
-            let curCollapsibleElement = elCollapsible[collapseIndex];
-            curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true);
-        }
-        
         // Textarea scrollbars
         let elTextareas = document.getElementsByTagName("TEXTAREA");
 
@@ -4281,7 +4527,7 @@ function loaded() {
             event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */
             curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false);
         }
-        
+
         // Dropdowns
         // For each dropdown the following replacement is created in place of the original dropdown...
         // Structure created:
@@ -4293,7 +4539,7 @@ function loaded() {
         //              <li>...</li>
         //          </ul>
         //      </dd>
-        //  </dl>    
+        //  </dl>
         let elDropdowns = document.getElementsByTagName("select");
         for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) {
             let elDropdown = elDropdowns[dropDownIndex];
@@ -4342,7 +4588,7 @@ function loaded() {
                 li.addEventListener("click", setDropdownValue);
                 ul.appendChild(li);
             }
-            
+
             let propertyID = elDropdown.getAttribute("propertyID");
             let property = properties[propertyID];
             property.elInput = dt;
@@ -4403,14 +4649,14 @@ function loaded() {
                 }
             }));
         }, false);
-        
+
         window.onblur = function() {
             // Fake a change event
             let ev = document.createEvent("HTMLEvents");
             ev.initEvent("change", true, true);
             document.activeElement.dispatchEvent(ev);
         };
-        
+
         // For input and textarea elements, select all of the text on focus
         let els = document.querySelectorAll("input, textarea");
         for (let i = 0; i < els.length; ++i) {
@@ -4418,12 +4664,14 @@ function loaded() {
                 e.target.select();
             };
         }
-        
-        bindAllNonJSONEditorElements(); 
+
+        bindAllNonJSONEditorElements();
 
         showGroupsForType("None");
+        showPage("base");
         resetProperties();
-        disableProperties();        
+        disableProperties();
+
     });
 
     augmentSpinButtons();
@@ -4438,3 +4686,30 @@ function loaded() {
         EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' }));
     }, 1000);
 }
+
+function showOnTheSamePage(entityType) {
+    let numberOfTypes = entityType.length;
+    let matchingType = 0;
+    for (let i = 0; i < numberOfTypes; i++) {
+        if (GROUPS_PER_TYPE[entityType[i]].includes(currentTab)) {
+            matchingType = matchingType + 1;
+        }
+    }
+    if (matchingType !== numberOfTypes) {
+        currentTab = "base";
+    }
+    showPage(currentTab);
+}
+
+function showPage(id) {
+    currentTab = id;
+    Object.entries(elGroups).forEach(([groupKey, elGroup]) => {
+        if (groupKey === id) {
+            elGroup.style.display = "block";
+            document.getElementById("tab-" + groupKey).style.backgroundColor = "#2E2E2E";
+        } else {
+            elGroup.style.display = "none";
+            document.getElementById("tab-" + groupKey).style.backgroundColor = "#404040";
+        }
+    });
+}
diff --git a/scripts/system/create/entityProperties/html/tabs/base.png b/scripts/system/create/entityProperties/html/tabs/base.png
new file mode 100644
index 0000000000..9f656abe27
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/base.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/behavior.png b/scripts/system/create/entityProperties/html/tabs/behavior.png
new file mode 100644
index 0000000000..12662b8a1d
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/behavior.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/collision.png b/scripts/system/create/entityProperties/html/tabs/collision.png
new file mode 100644
index 0000000000..c9bed39385
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/collision.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/grid.png b/scripts/system/create/entityProperties/html/tabs/grid.png
new file mode 100644
index 0000000000..d9b8afb1ae
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/grid.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/image.png b/scripts/system/create/entityProperties/html/tabs/image.png
new file mode 100644
index 0000000000..ebd03648f6
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/image.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/light.png b/scripts/system/create/entityProperties/html/tabs/light.png
new file mode 100644
index 0000000000..bed097d54e
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/light.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/material.png b/scripts/system/create/entityProperties/html/tabs/material.png
new file mode 100644
index 0000000000..458c6bad48
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/material.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/model.png b/scripts/system/create/entityProperties/html/tabs/model.png
new file mode 100644
index 0000000000..79aa6b3830
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/model.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles.png b/scripts/system/create/entityProperties/html/tabs/particles.png
new file mode 100644
index 0000000000..6a0d47cacb
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles_alpha.png b/scripts/system/create/entityProperties/html/tabs/particles_alpha.png
new file mode 100644
index 0000000000..8df2981f14
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_alpha.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles_behavior.png b/scripts/system/create/entityProperties/html/tabs/particles_behavior.png
new file mode 100644
index 0000000000..6be9f90638
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_behavior.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles_color.png b/scripts/system/create/entityProperties/html/tabs/particles_color.png
new file mode 100644
index 0000000000..ac66a902cf
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_color.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles_constraints.png b/scripts/system/create/entityProperties/html/tabs/particles_constraints.png
new file mode 100644
index 0000000000..9c783acc9b
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_constraints.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles_emit.png b/scripts/system/create/entityProperties/html/tabs/particles_emit.png
new file mode 100644
index 0000000000..223baa5d56
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_emit.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/particles_size.png b/scripts/system/create/entityProperties/html/tabs/particles_size.png
new file mode 100644
index 0000000000..b51fe65cdf
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/particles_size.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/physics.png b/scripts/system/create/entityProperties/html/tabs/physics.png
new file mode 100644
index 0000000000..f0fc451d37
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/physics.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/scripts.png b/scripts/system/create/entityProperties/html/tabs/scripts.png
new file mode 100644
index 0000000000..2249af165b
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/scripts.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/shape.png b/scripts/system/create/entityProperties/html/tabs/shape.png
new file mode 100644
index 0000000000..5f3722caf7
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/shape.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/spatial.png b/scripts/system/create/entityProperties/html/tabs/spatial.png
new file mode 100644
index 0000000000..a280d0e822
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/spatial.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/text.png b/scripts/system/create/entityProperties/html/tabs/text.png
new file mode 100644
index 0000000000..405d8e4104
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/text.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/web.png b/scripts/system/create/entityProperties/html/tabs/web.png
new file mode 100644
index 0000000000..c1fc573619
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/web.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone.png b/scripts/system/create/entityProperties/html/tabs/zone.png
new file mode 100644
index 0000000000..276ba26799
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone_ambient_light.png b/scripts/system/create/entityProperties/html/tabs/zone_ambient_light.png
new file mode 100644
index 0000000000..ff01b16aaf
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_ambient_light.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone_avatar_priority.png b/scripts/system/create/entityProperties/html/tabs/zone_avatar_priority.png
new file mode 100644
index 0000000000..e91111fb9b
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_avatar_priority.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone_bloom.png b/scripts/system/create/entityProperties/html/tabs/zone_bloom.png
new file mode 100644
index 0000000000..925654df81
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_bloom.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone_haze.png b/scripts/system/create/entityProperties/html/tabs/zone_haze.png
new file mode 100644
index 0000000000..0cf96692f8
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_haze.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone_key_light.png b/scripts/system/create/entityProperties/html/tabs/zone_key_light.png
new file mode 100644
index 0000000000..6527c65320
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_key_light.png differ
diff --git a/scripts/system/create/entityProperties/html/tabs/zone_skybox.png b/scripts/system/create/entityProperties/html/tabs/zone_skybox.png
new file mode 100644
index 0000000000..17697a817b
Binary files /dev/null and b/scripts/system/create/entityProperties/html/tabs/zone_skybox.png differ
diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css
index 470e57ad6d..c72456d414 100644
--- a/scripts/system/html/css/edit-style.css
+++ b/scripts/system/html/css/edit-style.css
@@ -3,6 +3,7 @@
 //
 //  Created by Ryan Huffman on 13 Nov 2014
 //  Copyright 2014 High Fidelity, Inc.
+//  Copyright 2020 Vircadia contributors.
 //
 //  Distributed under the Apache License, Version 2.0.
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@@ -1794,3 +1795,28 @@ input[type=number].hide-spinner::-webkit-inner-spin-button {
 div.jsoneditor-menu a.jsoneditor-poweredBy {
     display: none;
 }
+td.zoneItem {
+    width: 100%;
+}
+
+select.zoneSelect {
+    clear: both;
+    cursor: pointer;
+    font-family: FiraSans-SemiBold;
+    font-size: 15px;
+    width: 90%;
+    height: 28px;
+    padding: 0 28px 0 12px;
+    color: #afafaf;
+    background: #575757;
+    position: relative;
+    display: inline;
+    border: 0px;
+    align-items: center;
+    outline: none;
+}
+
+div.multiZoneSelToolbar {
+    padding: 0px;
+}
+
diff --git a/scripts/system/html/css/img/add_icon.png b/scripts/system/html/css/img/add_icon.png
new file mode 100644
index 0000000000..f60e5561f2
Binary files /dev/null and b/scripts/system/html/css/img/add_icon.png differ
diff --git a/scripts/system/html/css/img/remove_icon.png b/scripts/system/html/css/img/remove_icon.png
new file mode 100644
index 0000000000..7a34bd0304
Binary files /dev/null and b/scripts/system/html/css/img/remove_icon.png differ
diff --git a/scripts/system/html/css/tabs.css b/scripts/system/html/css/tabs.css
new file mode 100644
index 0000000000..b2d63b5652
--- /dev/null
+++ b/scripts/system/html/css/tabs.css
@@ -0,0 +1,77 @@
+/*
+//  tabs.css
+//
+//  Created by Alezia Kurdis on 27 Feb 2020
+//  Copyright 2020 Project Athena contributors.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+*/
+
+div.tabsContainer {
+    float: left;
+    width: 32px;
+    padding: 0px;
+}
+
+.tabsContainer button {
+    padding: 4px;
+    text-align: center;
+    cursor: pointer;
+    transition: 0.4s;
+    font-size: 14px;
+    background-color: #404040;
+    border-color: #404040;
+    border-width: 1px 0px 1px 1px;
+    border-radius: 5px 0px 0px 5px;
+    outline: none;
+}
+
+.tabsContainer button:hover {
+    background-color: #575757;
+}
+
+.tabsContainer button.active {
+    background-color: #2E2E2E;
+}
+
+div.labelTabHeader {
+    font-size: 20px;
+    font-weight: 700;
+    height: 40px;
+    color: #ffffff;
+}
+
+div.tab-section-header {
+    width: 100%;
+    padding: 5px;
+}
+
+table.tabsTableFrame {
+    width: 100%;
+    min-height: 352px;
+    display: block;
+}
+
+tr.tabsTrFrame {
+    width: 100%;
+}
+
+td.tabsFrame {
+    width: 32px;
+    vertical-align: top;
+    background-color: #575757;
+    padding: 0px;
+    border: 0px;
+}
+
+td.tabsPropertiesFrame {
+    width: 100%;
+    vertical-align: top;
+    border: 0px;
+}
+
+div.tabsPropertiesPage {
+    min-height: 352px;
+    display: block;
+}
diff --git a/tools/ci-scripts/upload_to_publish_server.py b/tools/ci-scripts/upload_to_publish_server.py
new file mode 100644
index 0000000000..5e1a9b24f2
--- /dev/null
+++ b/tools/ci-scripts/upload_to_publish_server.py
@@ -0,0 +1,77 @@
+import os
+import json
+from hashlib import sha256
+import http.client
+from http import HTTPStatus
+import time
+import struct
+import random
+import glob
+
+FILE_READ_BUFFER = 4096
+
+path = os.path.join(os.getcwd(), os.environ['ARTIFACT_PATTERN'])
+files = glob.glob(path, recursive=False)
+uploading_files = []
+for archive_file in files:
+    file = open(archive_file, 'rb')
+    sha256_hash = sha256()
+    file.seek(0, 0)
+    for byte_block in iter(lambda: file.read(FILE_READ_BUFFER), b""):
+        sha256_hash.update(byte_block)
+
+    checksum = sha256_hash.hexdigest()
+
+    uploading_files.append({
+        "filename": os.path.basename(archive_file),
+        "sha256_checksum": checksum,
+        "file_length": file.tell()
+    })
+    file.close()
+
+print("BuildFileHashes: " + json.dumps(uploading_files))
+
+file_contents = []
+file_sizes = []
+
+for archiveFile in files:
+    file = open(archiveFile, 'rb')
+    file_data = file.read()
+    file_sizes.append(len(file_data))
+    file_contents.append(file_data)
+    file.close()
+
+conn = http.client.HTTPSConnection("build-uploader.vircadia.com")
+
+context = json.loads(os.environ['GITHUB_CONTEXT'])
+
+owner_and_repository = context["repository"].split("/")
+owner = owner_and_repository[0]
+repository = owner_and_repository[1]
+
+headers = {
+    "owner": owner,
+    "repo": repository,
+    "commit_hash": context["event"]["pull_request"]["head"]["sha"],
+    "pull_number": context["event"]["number"],
+    "job_name": os.environ["JOB_NAME"],
+    "run_id": context["run_id"],
+    "file_sizes": ','.join(str(e) for e in file_sizes)
+}
+
+concat_file_body = b''.join(file_contents)
+
+print("Total files size: " + str(len(concat_file_body)))
+
+conn.request("PUT", "/", body=concat_file_body, headers=headers)
+response = conn.getresponse()
+
+EXIT_CODE_OK = 0
+EXIT_CODE_ERROR = 1
+
+if (response.status == HTTPStatus.OK):
+    print("response: ",  json.loads(response.read()))
+    exit(EXIT_CODE_OK)
+else:
+    print(response.status, response.reason, response.read())
+    exit(EXIT_CODE_ERROR)
diff --git a/unpublishedScripts/marketplace/record/html/record.html b/unpublishedScripts/marketplace/record/html/record.html
index 89392e6951..810238a580 100644
--- a/unpublishedScripts/marketplace/record/html/record.html
+++ b/unpublishedScripts/marketplace/record/html/record.html
@@ -3,6 +3,7 @@
 //
 //  Created by David Rowe on 5 Apr 2017.
 //  Copyright 2017 High Fidelity, Inc.
+//  Copyright 2020 Vircadia contributors.
 //
 //  Distributed under the Apache License, Version 2.0.
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@@ -41,9 +42,9 @@
                                 <h1>Setup Instructions</h1>
                                 <p>In your sandbox domain:</p>
                                 <ul>
-                                    <li>Right-click the High Fidelity Sandbox icon in your system tray and click &ldquo;Settings&rdquo;.</li>
+                                    <li>Right-click the Vircadia Sandbox icon in your system tray and click &ldquo;Settings&rdquo;.</li>
                                     <li>In the &ldquo;Scripts&rdquo; section add a new row and paste in this script URL:<br />
-                                        <input type="text" value="https://content.highfidelity.com/Scripts/playRecordingAC.js" readonly />
+                                        <input type="text" value="https://cdn.vircadia.com/community-apps/applications/record/playRecordingAC.js" readonly />
                                     </li>
                                     <li>Set the number of recordings you&rsquo;d like to run at a given time in the &ldquo;Instances&rdquo; slot.</li>
                                     <li>Click &ldquo;Save and restart&rdquo;.</li>
diff --git a/unpublishedScripts/marketplace/record/icon.svg b/unpublishedScripts/marketplace/record/icon.svg
new file mode 100644
index 0000000000..06f8a3276f
--- /dev/null
+++ b/unpublishedScripts/marketplace/record/icon.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FFFFFF;}
+</style>
+<g id="Layer_2">
+</g>
+<g>
+	<path class="st0" d="M23.2,20.5c-1,0.8-1.8,1.4-2.7,2.1c-0.2,0.1-0.2,0.4-0.2,0.7c-0.3,1.7-0.6,3.4-0.9,5.1
+		c-0.1,0.8-0.6,1.2-1.3,1.1c-0.7-0.1-1.2-0.7-1.1-1.4c0.3-2.2,0.6-4.4,1-6.6c0.1-0.3,0.3-0.7,0.6-0.9c1.4-1.3,2.8-2.5,4.2-3.7
+		c0.7-0.6,1.5-1,2.4-0.9c0.3,0,0.7,0,1,0c1-0.1,1.7,0.4,2.1,1.3c0.7,1.4,1.4,2.8,1.9,4.3c0.5,1.3,1.2,2.1,2.4,2.6c1,0.4,2,1,3,1.5
+		c0.2,0.1,0.5,0.3,0.7,0.5c0.4,0.4,0.5,1,0.3,1.4C36.4,28,36,28.1,35.5,28c-0.4-0.1-0.8-0.2-1.2-0.4c-1.3-0.6-2.5-1.2-3.7-1.8
+		c-0.8-0.3-1.4-0.8-1.8-1.6c-0.2-0.3-0.4-0.6-0.7-1c-0.1,0.3-0.1,0.5-0.2,0.7c-0.3,1.2-0.5,2.4-0.8,3.6c-0.1,0.4,0,0.7,0.2,1
+		c2.2,3.7,4.4,7.4,6.6,11.1c0.3,0.4,0.4,1,0.5,1.5c0.1,0.7-0.1,1.3-0.7,1.6c-0.7,0.4-1.4,0.4-1.9-0.1c-0.4-0.4-0.8-0.8-1.1-1.3
+		c-2.5-3.9-5-7.9-7.5-11.8c-0.4-0.7-0.8-1.5-1.1-2.2c-0.1-0.4-0.1-0.9,0-1.3C22.5,24.2,22.8,22.4,23.2,20.5z"/>
+	<path class="st0" d="M23.2,33.9c-0.1-0.1-0.2-0.2-0.2-0.3c0,0,0,0,0,0c-0.2-0.2-0.3-0.5-0.5-0.7c-0.3-0.4-0.6-0.8-0.9-1.1
+		c-0.3,1-0.5,2-0.8,3c-0.1,0.3-0.3,0.7-0.4,1c-1,1.5-2,3.1-3,4.6c-0.2,0.4-0.4,0.8-0.6,1.3c-0.2,0.9,0.7,1.9,1.6,1.5
+		c0.5-0.2,1-0.7,1.3-1.1c0.9-1.1,1.6-2.3,2.5-3.3c0.8-1,1.4-2.2,1.8-3.4C23.8,34.7,23.5,34.3,23.2,33.9z"/>
+	<path class="st0" d="M29,11.6c0,1.3-1.1,2.4-2.4,2.4h-0.2c-1.3,0-2.4-1.1-2.4-2.4v-1.2C24,9.1,25.1,8,26.4,8h0.2
+		c1.3,0,2.4,1.1,2.4,2.4V11.6z"/>
+	<path class="st0" d="M43.4,24.1c-0.5,0.3-0.9,0.5-1.4,0.8v6.3h2.3v-7.6C44,23.8,43.7,23.9,43.4,24.1z"/>
+	<path class="st0" d="M42,38.6v0.4c0,1.2-1,2.1-2.1,2.1h-0.8v2.3h0.8c2.5,0,4.5-2,4.5-4.5v-0.4H42z"/>
+	<path class="st0" d="M9.7,12.2v-0.4c0-1.2,1-2.1,2.1-2.1h2V7.3h-2c-2.5,0-4.5,2-4.5,4.5v0.4H9.7z"/>
+	<rect x="7.4" y="18.3" class="st0" width="2.3" height="12.9"/>
+	<path class="st0" d="M9.7,38.9v-0.4H7.4v0.4c0,2.5,2,4.5,4.5,4.5h2v-2.3h-2C10.7,41.1,9.7,40.1,9.7,38.9z"/>
+	<g>
+		<circle class="st0" cx="38.6" cy="13.3" r="2.2"/>
+		<path class="st0" d="M38.6,15.5c-1.2,0-2.2-1-2.2-2.2s1-2.2,2.2-2.2c1.2,0,2.2,1,2.2,2.2S39.8,15.5,38.6,15.5z M38.6,11.2
+			c-1.1,0-2.1,0.9-2.1,2.1s0.9,2.1,2.1,2.1c1.1,0,2.1-0.9,2.1-2.1S39.7,11.2,38.6,11.2z"/>
+	</g>
+	<path class="st0" d="M38.6,19.7c-3.6,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4S42.1,19.7,38.6,19.7z M38.6,9.1
+		c-2.3,0-4.2,1.9-4.2,4.2s1.9,4.2,4.2,4.2c2.3,0,4.2-1.9,4.2-4.2S40.9,9.1,38.6,9.1z"/>
+</g>
+</svg>