diff --git a/Admin/rtbPublishReferenceData.m b/Admin/rtbPublishReferenceData.m
index 7b376d0eb731c219e5398a195769129225b6e50d..9a8e08a4bcf99477899ada757c65a2aeb00f0360 100644
--- a/Admin/rtbPublishReferenceData.m
+++ b/Admin/rtbPublishReferenceData.m
@@ -24,14 +24,18 @@ function artifacts = rtbPublishReferenceData(varargin)
 
 parser = inputParser();
 parser.addParameter('rdtConfig', 'render-toolbox');
+parser.addParameter('rdtUsername', '');
+parser.addParameter('rdtPassword', '');
 parser.addParameter('referenceRoot', pwd(), @ischar);
 parser.addParameter('tempRoot', fullfile(tempdir(), 'rtbPublishReferenceData'), @ischar);
 parser.addParameter('referenceVersion', 'test', @ischar);
 parser.addParameter('remotePath', 'reference-data', @ischar);
-parser.addParameter('deployToolboxes', true, @islogical);
+parser.addParameter('deployToolboxes', false, @islogical);
 parser.addParameter('dryRun', true, @islogical);
 parser.parse(varargin{:});
 rdtConfig = parser.Results.rdtConfig;
+rdtUsername = parser.Results.rdtUsername;
+rdtPassword = parser.Results.rdtPassword;
 referenceRoot = parser.Results.referenceRoot;
 tempRoot = parser.Results.tempRoot;
 referenceVersion = parser.Results.referenceVersion;
@@ -39,6 +43,12 @@ remotePath = parser.Results.remotePath;
 deployToolboxes = parser.Results.deployToolboxes;
 dryRun = parser.Results.dryRun;
 
+if ~isempty(rdtUsername) && ~isempty(rdtPassword)
+    rdtConfig = rdtConfiguration(rdtConfig);
+    rdtConfig.username = rdtUsername;
+    rdtConfig.password = rdtPassword;
+end
+
 if deployToolboxes
     tbUse({'RenderToolbox4', 'RemoteDataToolbox'});
 end
@@ -48,6 +58,7 @@ if 7 ~= exist(tempRoot, 'dir')
 end
 
 % iterate subfolders of referenceRoot for example names
+fprintf('Looking for examples in <%s>:', referenceRoot);
 [exampleNames, nExamples] = subfolderNames(referenceRoot);
 artifactCell = cell(1, nExamples);
 for ee = 1:nExamples
@@ -66,6 +77,7 @@ for ee = 1:nExamples
 end
 artifacts = [artifactCell{:}];
 
+
 function [names, nNames] = subfolderNames(parentPath)
 parentDir = dir(parentPath);
 parentDir = parentDir(3:end);
@@ -77,7 +89,7 @@ function artifact = publishFile(rdtConfig, fileName, remotePath, exampleName, ve
 
 % describe the example
 artifactPath = fullfile(remotePath, exampleName);
-description = sprintf('version <%s> example <%s>', versionName, exampleName);
+description = sprintf('  version <%s> example <%s>', versionName, exampleName);
 disp(description);
 
 if dryRun
diff --git a/Test/Automated/RtbExampleTests.m b/Test/Automated/RtbExampleTests.m
index bd07c83d137b88db3831335fef131c00c8e543ce..a551e9687a9c5b0421f45b21f29612db2817d550 100644
--- a/Test/Automated/RtbExampleTests.m
+++ b/Test/Automated/RtbExampleTests.m
@@ -21,7 +21,7 @@ classdef RtbExampleTests < matlab.unittest.TestCase
     methods (Test)
         
         function testNotAnExample(testCase)
-            results = rtbRunEpicTest( ...
+            results = rtbRunEpicExamples( ...
                 'outputRoot', testCase.outputRoot, ...
                 'makeFunctions', {'notAnExample.m'});
             testCase.assertFalse(results.isSuccess);
@@ -29,7 +29,7 @@ classdef RtbExampleTests < matlab.unittest.TestCase
         end
         
         function testCoordinatesTest(testCase)
-            results = rtbRunEpicTest( ...
+            results = rtbRunEpicExamples( ...
                 'outputRoot', testCase.outputRoot, ...
                 'makeFunctions', {'rtbMakeCoordinatesTest.m'});
             testCase.assertTrue(results.isSuccess);
@@ -51,7 +51,7 @@ classdef RtbExampleTests < matlab.unittest.TestCase
         end
         
         function testDragon(testCase)
-            results = rtbRunEpicTest( ...
+            results = rtbRunEpicExamples( ...
                 'outputRoot', testCase.outputRoot, ...
                 'makeFunctions', {'rtbMakeDragon.m'});
             testCase.assertTrue(results.isSuccess);
@@ -70,7 +70,7 @@ classdef RtbExampleTests < matlab.unittest.TestCase
         end
         
         function testMaterialSphereBumps(testCase)
-            results = rtbRunEpicTest( ...
+            results = rtbRunEpicExamples( ...
                 'outputRoot', testCase.outputRoot, ...
                 'makeFunctions', {'rtbMakeMaterialSphereBumps.m'});
             testCase.assertTrue(results.isSuccess);
@@ -89,7 +89,7 @@ classdef RtbExampleTests < matlab.unittest.TestCase
         end
         
         function testMaterialSphereRemodeled(testCase)
-            results = rtbRunEpicTest( ...
+            results = rtbRunEpicExamples( ...
                 'outputRoot', testCase.outputRoot, ...
                 'makeFunctions', {'rtbMakeMaterialSphereRemodeled.m'});
             testCase.assertTrue(results.isSuccess);
diff --git a/Test/Interactive/Comparison/rtbRunEpicComparison.m b/Test/Interactive/Comparison/rtbRunEpicComparison.m
index 5ea42c274ae7a849ec309c9de81cfe21a8fb674c..370e98a71e19cae6f084955ec19dcc514209d949 100644
--- a/Test/Interactive/Comparison/rtbRunEpicComparison.m
+++ b/Test/Interactive/Comparison/rtbRunEpicComparison.m
@@ -40,6 +40,7 @@ function [comparisons, matchInfo, figs] = rtbRunEpicComparison(folderA, folderB,
 %%% RenderToolbox4 is released under the MIT License.  See LICENSE file.
 
 parser = inputParser();
+parser.KeepUnmatched = true;
 parser.addRequired('folderA', @ischar);
 parser.addRequired('folderB', @ischar);
 parser.addParameter('plotSummary', true, @islogical);
diff --git a/Test/Interactive/rtbEpicValidationExample.m b/Test/Interactive/rtbEpicValidationExample.m
new file mode 100644
index 0000000000000000000000000000000000000000..ccc7643a1225dc1401714b7f7b3928e5bd98764f
--- /dev/null
+++ b/Test/Interactive/rtbEpicValidationExample.m
@@ -0,0 +1,60 @@
+%% Run an "epic" example tests, and compare to published reference data.
+%
+% This script is an example of how to run through an "epic scene test" and
+% comparison of results to reference data stored at Brainard Archiva.
+%   http://52.32.77.154/#browse~RenderToolbox/reference-data
+%
+% All the work is done by the function rtbRunEpicValidation().  But there
+% are several parameters that you might want to configure.  So here is a
+% script with comments.
+%
+%%% RenderToolbox4 Copyright (c) 2012-2017 The RenderToolbox Team.
+%%% About Us://github.com/RenderToolbox/RenderToolbox4/wiki/About-Us
+%%% RenderToolbox4 is released under the MIT License.  See LICENSE file.
+
+clear;
+clc;
+
+
+%% Parameters to pass to rtbRunEpicExamples().
+
+% where to store locally generated results
+localOutput = '/Users/ben/Deskop/rtb-epic';
+
+% choose a few recipes for testing this out
+makeFunctions = {'rtbMakeCoordinatesTest', 'rtbMakeDragon'};
+
+% leave empty to run them all
+% makeFunctions = {};
+
+
+%% Parameters to pass to rtbRunEpicComparison().
+
+% where to store fetched reference data
+rtbReference = '/Users/ben/Deskop/rtb-reference';
+
+% where to save generated comparison figures
+figureFolder = '/Users/ben/Deskop/rtb-figures';
+
+% a name to use for this comparison run
+summaryName = 'epic-validation-example';
+
+
+%% Run examples and do the comparison.
+% You could pass in additional parameters for rtbRunEpicExamples() or
+% rtbRunEpicComparison(), too.  They will be forwarded along.  The
+% parameters above seem like a good bunch to start with.
+
+% This may take a long time.
+% The outputs are:
+%  - exampleResults -- struct array with info for each example recipe run
+%  - comparisons -- struct array with info about comparisons to reference
+%  - matchInfo -- struct array about matching up local data vs reference
+%  - figures -- array of figure handles with comparison summary
+[exampleResults, comparisons, matchInfo, figs] = rtbRunEpicValidation( ...
+    localOutput, ...
+    rtbReference, ...
+    'makeFunctions', makeFunctions, ...
+    'figureFolder', figureFolder, ...
+    'summaryName', summaryName);
+
diff --git a/Test/Interactive/rtbRunEpicTest.m b/Test/Interactive/rtbRunEpicExamples.m
similarity index 93%
rename from Test/Interactive/rtbRunEpicTest.m
rename to Test/Interactive/rtbRunEpicExamples.m
index 68ba5ea8997032c0ed9af5247eb92f5e11dadcc3..b280a571e5d92c8bd2017dd976c3e8f72c8bafb5 100644
--- a/Test/Interactive/rtbRunEpicTest.m
+++ b/Test/Interactive/rtbRunEpicExamples.m
@@ -1,7 +1,7 @@
-function results = rtbRunEpicTest(varargin)
+function results = rtbRunEpicExamples(varargin)
 %% Run all "rtbMake..." scripts in the ExampleScenes/ folder.
 %
-% results = rtbRunEpicTest() renders example scenes by invoking
+% results = rtbRunEpicExamples() renders example scenes by invoking
 % all of the "rtbMake..." executive sripts found within the ExampleScenes/
 % folder
 %
@@ -19,11 +19,11 @@ function results = rtbRunEpicTest(varargin)
 % have a name that that includes the name of this m-file, plus the date and
 % time.
 %
-% rtbRunEpicTest( ... 'outputRoot', outputRoot) specifies the
+% rtbRunEpicExamples( ... 'outputRoot', outputRoot) specifies the
 % working folder where to put rendering outputs.  The default is from
 % rtbDefaultHints().
 %
-% rtbRunEpicTest( ... 'makeFunctions', makeFunctions) specifies a
+% rtbRunEpicExamples( ... 'makeFunctions', makeFunctions) specifies a
 % cell array of executive functions to run.  The default is to search the
 % ExampleScenes/ folder for m-files that begin with "rtbMake".
 %
@@ -32,6 +32,7 @@ function results = rtbRunEpicTest(varargin)
 %%% RenderToolbox4 is released under the MIT License.  See LICENSE file.
 
 parser = inputParser();
+parser.KeepUnmatched = true;
 parser.addParameter('outputRoot', rtbWorkingFolder(), @ischar);
 parser.addParameter('makeFunctions', {}, @iscellstr);
 parser.parse(varargin{:});
diff --git a/Test/Interactive/rtbRunEpicValidation.m b/Test/Interactive/rtbRunEpicValidation.m
new file mode 100644
index 0000000000000000000000000000000000000000..946b82b96103593d47552b78e76507a1ece1ccaf
--- /dev/null
+++ b/Test/Interactive/rtbRunEpicValidation.m
@@ -0,0 +1,40 @@
+function [results, comparisons, matchInfo, figs] = rtbRunEpicValidation(outputRoot, referenceRoot, varargin)
+%% Run an "epic" example tests, and compare to published reference data.
+%
+% results = rtbRunEpicValidation(outputRoot, referenceRoot) renders example
+% scenes by invoking all of the "rtbMake..." executive sripts found within
+% the ExampleScenes/ folder and putting results into the given outputRoot
+% folder.  Then compares all rendered recipes to published reference data,
+% using the given referenceRoot to store the reference data.
+%
+% Returns a struct with information about each recipe executed locally --
+% see rtbRunEpicExamples().  Also returns a struct with information about
+% comparisons to reference data, a struct with information about how local
+% and reference data files were matched up, and an array of figure handles
+% that illustrate the comparisons -- see rtbRunEpicComparison().
+%
+% rtbRunEpicValidation( ... name, value) passes additional name-value pairs
+% to the functions rtbRunEpicExamples() and rtbRunEpicComparison().
+%
+%%% RenderToolbox4 Copyright (c) 2012-2017 The RenderToolbox Team.
+%%% About Us://github.com/RenderToolbox/RenderToolbox4/wiki/About-Us
+%%% RenderToolbox4 is released under the MIT License.  See LICENSE file.
+
+parser = inputParser();
+parser.addRequired('outputRoot', @ischar);
+parser.addRequired('referenceRoot', @ischar);
+parser.parse(outputRoot, referenceRoot);
+outputRoot = parser.Results.outputRoot;
+referenceRoot = parser.Results.referenceRoot;
+
+
+%% Run the epic example set.
+results = rtbRunEpicExamples( ...
+    'outputRoot', outputRoot, ...
+    varargin{:});
+
+
+%% Run the epic comparison.
+[comparisons, matchInfo, figs] = rtbRunEpicComparison( ...
+    outputRoot, referenceRoot, ...
+    varargin{:});
diff --git a/Test/Interactive/rtbTestInstallation.m b/Test/Interactive/rtbTestInstallation.m
index ada27c2e7035d2605d8dd2a6ea5cb2ae05658f4a..259f1058515a7cb677d255e87813b5a843be6998 100644
--- a/Test/Interactive/rtbTestInstallation.m
+++ b/Test/Interactive/rtbTestInstallation.m
@@ -77,7 +77,7 @@ end
 if doAll
     fprintf('\nTesting rendering with all example scripts.\n');
     fprintf('This might take a while.\n');
-    renderResults = rtbRunEpicTest([], []);
+    renderResults = rtbRunEpicExamples([], []);
     
 else
     testScenes = { ...
@@ -88,7 +88,7 @@ else
     
     fprintf('\nTesting rendering with %d example scripts.\n', numel(testScenes));
     fprintf('You should see several figures with rendered images.\n\n');
-    renderResults = rtbRunEpicTest('makeFunctions', testScenes);
+    renderResults = rtbRunEpicExamples('makeFunctions', testScenes);
     
 end
 
diff --git a/Utilities/rtbBuildDesription.m b/Utilities/rtbBuildDesription.m
new file mode 100644
index 0000000000000000000000000000000000000000..fabaeda2bf01be47a843fd41c761ca8194d561a3
--- /dev/null
+++ b/Utilities/rtbBuildDesription.m
@@ -0,0 +1,45 @@
+%% Get a formatted struct describing a light, material, etc.
+%   @param category a scene element category like 'light', 'material', etc.
+%   @param type a scene element type like 'area', 'matte', etc.
+%   @param propertyNames cell aray of element property names like {'intensity', 'diffuseReflectance'}
+%   @param propertyValues cell aray of element property values like {'D65.spd, '255 0 0'}
+%   @param valueTypes cell aray of property values types like {'spectrum', 'rgb'}
+%
+% @details
+% Builds a struct with standard formatting that describes some scene
+% element.  This struct will be suitable for writing to a RenderToolbox3
+% mappings file using utilities like AppendMappings().
+%
+% @details
+% @a category and @a type indicate the kind of scene element to describe,
+% for example an area light or matte material.
+%
+% @details
+% @a propertyNames, @a propertyValues, and @a valueTypes all should have
+% n elements, and collectively specify n properties of the scene element.
+%
+% @details
+% See <a
+% href="https://github.com/DavidBrainard/RenderToolbox3/wiki/Generic-Scene-Elements">Generic-Scene-Elements</a>
+% for examples of valid elements and properties.
+%
+% @details
+% Returns a struct with standard formatting which describes a scene element
+% and its properties.
+%
+% @details
+% Usage:
+%   description = rtbBuildDesription(category, type, propertyNames, propertyValues, valueTypes)
+%
+% We should update the commenting style of this header someday.
+
+function description = rtbBuildDesription(category, type, propertyNames, propertyValues, valueTypes)
+properties = struct( ...
+    'propertyName', propertyNames, ...
+    'propertyValue', propertyValues, ...
+    'valueType', valueTypes);
+
+description = struct( ...
+    'category', category, ...
+    'type', type, ...
+    'properties', properties);
diff --git a/Utilities/rtbReadMetadata.m b/Utilities/rtbReadMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..e3b7ad8fa69210c53684f58e4a41bd7361cb44c1
--- /dev/null
+++ b/Utilities/rtbReadMetadata.m
@@ -0,0 +1,46 @@
+%% Read metadata about a VirtualScenes base scene or object.
+%   @param modelName the name of a VirtualScenes model like "RingToy"
+%
+% @details
+% Reads a mat-file of metadata for a 3D Collada model that we previouslt
+% registered in the VirtualScenes ModelRepository using WriteMetadata().
+% @a modelName must be the same name that was passed to WriteMetadata(),
+% for example "RingToy".  This must would correspond to the name of the
+% Collada file in the VirtualScenes Toolbox ModelRepository, such as
+% 'VirtualScenesToolbox/ModelRepository/Objects/Models/RingToy.dae'.
+%
+% @details
+% Returns the struct metadata that was previously written by
+% WriteMetadata().
+%
+% @details
+% Usage:
+%   metadata = ReadMetadata(modelName)
+%
+% @ingroup VirtualScenes
+function metadata = rtbReadMetadata(modelName)
+metadata = [];
+
+% locate the metadata file
+metadataFile = [modelName '.mat'];
+rootFolder = getpref('VirtualScenes', 'modelRepository');
+fileInfo = rtbResolveFilePath(metadataFile, rootFolder);
+
+if ~fileInfo.isRootFolderMatch
+    warning('VirtualScenes:NoSuchMetadata', ...
+        'Could not find metadata for model named "%s" in %s', modelName, rootFolder);
+    return;
+end
+
+metadataFullPath = fileInfo.absolutePath;
+fprintf('\nFound model metadata:\n  %s\n', metadataFullPath);
+
+fileData = load(fileInfo.absolutePath);
+
+if ~isfield(fileData, 'metadata')
+    warning('VirtualScenes:BadMetadata', ...
+        'Metadata is missing from data file %s', metadataFullPath);
+    return;
+end
+
+metadata = fileData.metadata;