#include "RenderStateComposer.h"
#include "RuntimeLiveState.h"

#include <chrono>
#include <cmath>
#include <iostream>

namespace
{
int gFailures = 0;

void Expect(bool condition, const char* message)
{
	if (condition)
		return;

	std::cerr << "FAIL: " << message << "\n";
	++gFailures;
}

ShaderParameterDefinition FloatDefinition(const std::string& id, const std::string& label)
{
	ShaderParameterDefinition definition;
	definition.id = id;
	definition.label = label;
	definition.type = ShaderParameterType::Float;
	definition.defaultNumbers = { 0.0 };
	definition.minNumbers = { 0.0 };
	definition.maxNumbers = { 1.0 };
	return definition;
}

ShaderParameterDefinition Vec2Definition(const std::string& id, const std::string& label)
{
	ShaderParameterDefinition definition;
	definition.id = id;
	definition.label = label;
	definition.type = ShaderParameterType::Vec2;
	definition.defaultNumbers = { 0.0, 0.0 };
	definition.minNumbers = { 0.0, 0.0 };
	definition.maxNumbers = { 1.0, 1.0 };
	return definition;
}

ShaderParameterDefinition TriggerDefinition(const std::string& id, const std::string& label)
{
	ShaderParameterDefinition definition;
	definition.id = id;
	definition.label = label;
	definition.type = ShaderParameterType::Trigger;
	return definition;
}

JsonValue NumberArray(std::initializer_list<double> numbers)
{
	JsonValue value = JsonValue::MakeArray();
	for (double number : numbers)
		value.pushBack(JsonValue(number));
	return value;
}

RuntimeRenderState MakeLayerState()
{
	RuntimeRenderState state;
	state.layerId = "layer-one";
	state.shaderId = "test-shader";
	state.shaderName = "Test Shader";
	state.parameterDefinitions.push_back(FloatDefinition("amount", "Amount"));
	ShaderParameterValue amount;
	amount.numberValues = { 0.25 };
	state.parameterValues["amount"] = amount;
	return state;
}

RuntimeRenderState MakeLayerStateWithDefinitions(const std::vector<ShaderParameterDefinition>& definitions)
{
	RuntimeRenderState state;
	state.layerId = "layer-one";
	state.shaderId = "test-shader";
	state.shaderName = "Test Shader";
	state.parameterDefinitions = definitions;
	return state;
}

void TestRuntimeLiveStateAppliesLatestOscOverlay()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate first;
	first.routeKey = "layer-one\namount";
	first.layerKey = "layer-one";
	first.parameterKey = "amount";
	first.targetValue = JsonValue(0.5);

	RuntimeLiveOscUpdate second = first;
	second.targetValue = JsonValue(0.75);

	liveState.ApplyOscUpdates({ first, second });
	Expect(liveState.OverlayCount() == 1, "live state keeps one overlay per route");

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	RuntimeLiveStateApplyOptions options;
	options.allowCommit = false;
	options.smoothing = 0.0;
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("amount");
	Expect(valueIt != states[0].parameterValues.end(), "overlay writes the target parameter");
	Expect(!valueIt->second.numberValues.empty() && std::fabs(valueIt->second.numberValues[0] - 0.75) < 0.0001,
		"overlay applies the latest target value");
}

void TestRuntimeLiveStateIgnoresStaleCommitCompletions()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.9);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	std::vector<RuntimeLiveOscCommitRequest> commitRequests;
	RuntimeLiveStateApplyOptions options;
	options.allowCommit = true;
	options.smoothing = 0.0;
	options.commitDelay = std::chrono::milliseconds(0);
	options.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1);
	liveState.ApplyToLayerStates(states, options, &commitRequests);
	Expect(commitRequests.size() == 1, "initial commit request is queued");

	liveState.ApplyOscCommitCompletions({ { "other-route", commitRequests[0].generation } });
	Expect(liveState.OverlayCount() == 1, "completion for another route does not remove overlay");

	liveState.ApplyOscCommitCompletions({ { commitRequests[0].routeKey, commitRequests[0].generation + 1 } });
	Expect(liveState.OverlayCount() == 1, "completion for another generation does not remove overlay");

	RuntimeLiveOscUpdate newerUpdate = update;
	newerUpdate.targetValue = JsonValue(0.2);
	liveState.ApplyOscUpdates({ newerUpdate });
	liveState.ApplyOscCommitCompletions({ { commitRequests[0].routeKey, commitRequests[0].generation } });
	Expect(liveState.OverlayCount() == 1, "stale completion for previous generation is ignored after newer update");
}

void TestRuntimeLiveStateQueuesAndCompletesCommit()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.9);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	std::vector<RuntimeLiveOscCommitRequest> commitRequests;
	RuntimeLiveStateApplyOptions options;
	options.allowCommit = true;
	options.smoothing = 0.0;
	options.commitDelay = std::chrono::milliseconds(0);
	options.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1);
	liveState.ApplyToLayerStates(states, options, &commitRequests);

	Expect(commitRequests.size() == 1, "live state queues a commit request once the overlay can settle");
	Expect(commitRequests[0].routeKey == "layer-one\namount", "commit request preserves route");
	Expect(commitRequests[0].generation == 1, "commit request carries overlay generation");

	liveState.ApplyOscCommitCompletions({ { commitRequests[0].routeKey, commitRequests[0].generation } });
	Expect(liveState.OverlayCount() == 0, "matching commit completion removes settled overlay");
}

void TestRuntimeLiveStateQueuesOneCommitPerGeneration()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.8);
	liveState.ApplyOscUpdates({ update });

	RuntimeLiveStateApplyOptions options;
	options.allowCommit = true;
	options.smoothing = 0.0;
	options.commitDelay = std::chrono::milliseconds(0);
	options.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1);

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	std::vector<RuntimeLiveOscCommitRequest> commitRequests;
	liveState.ApplyToLayerStates(states, options, &commitRequests);
	Expect(commitRequests.size() == 1, "first apply queues one commit for generation");
	Expect(commitRequests[0].generation == 1, "first commit uses generation one");

	commitRequests.clear();
	options.now += std::chrono::milliseconds(1);
	liveState.ApplyToLayerStates(states, options, &commitRequests);
	Expect(commitRequests.empty(), "second apply does not duplicate commit for same generation");

	RuntimeLiveOscUpdate newerUpdate = update;
	newerUpdate.targetValue = JsonValue(0.4);
	liveState.ApplyOscUpdates({ newerUpdate });

	commitRequests.clear();
	options.now += std::chrono::milliseconds(1);
	liveState.ApplyToLayerStates(states, options, &commitRequests);
	Expect(commitRequests.size() == 1, "newer update allows a new commit request");
	Expect(commitRequests[0].generation == 2, "new commit uses newer generation");
}

void TestRuntimeLiveStateSmoothingZeroAppliesTargetImmediately()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(1.0);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	RuntimeLiveStateApplyOptions options;
	options.smoothing = 0.0;
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("amount");
	Expect(valueIt != states[0].parameterValues.end(), "smoothing zero writes amount");
	Expect(!valueIt->second.numberValues.empty() && std::fabs(valueIt->second.numberValues[0] - 1.0) < 0.0001,
		"smoothing zero applies target immediately");
}

void TestRuntimeLiveStateSmoothingOneConvergesImmediately()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(1.0);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	RuntimeLiveStateApplyOptions options;
	options.smoothing = 1.0;
	options.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(16);
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("amount");
	Expect(valueIt != states[0].parameterValues.end(), "smoothing one writes amount");
	Expect(!valueIt->second.numberValues.empty() && std::fabs(valueIt->second.numberValues[0] - 1.0) < 0.0001,
		"smoothing one converges immediately");
}

void TestRuntimeLiveStateSmoothingPartiallyConverges()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(1.0);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	ShaderParameterValue amount;
	amount.numberValues = { 0.0 };
	states[0].parameterValues["amount"] = amount;

	RuntimeLiveStateApplyOptions options;
	options.smoothing = 0.5;
	options.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(16);
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("amount");
	Expect(valueIt != states[0].parameterValues.end(), "partial smoothing writes amount");
	Expect(!valueIt->second.numberValues.empty() &&
			valueIt->second.numberValues[0] > 0.0 &&
			valueIt->second.numberValues[0] < 1.0,
		"partial smoothing advances toward target without snapping");
}

void TestRuntimeLiveStateSmoothingVectorSizeMismatchUsesTargetShape()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\noffset";
	update.layerKey = "layer-one";
	update.parameterKey = "offset";
	update.targetValue = NumberArray({ 0.25, 0.75 });
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerStateWithDefinitions({ Vec2Definition("offset", "Offset") }) };
	ShaderParameterValue malformedOffset;
	malformedOffset.numberValues = { 0.1 };
	states[0].parameterValues["offset"] = malformedOffset;

	RuntimeLiveStateApplyOptions options;
	options.smoothing = 0.5;
	options.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(16);
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("offset");
	Expect(valueIt != states[0].parameterValues.end(), "vector mismatch writes offset");
	Expect(valueIt->second.numberValues.size() == 2, "vector mismatch restores target vector size");
	Expect(valueIt->second.numberValues.size() == 2 &&
			std::fabs(valueIt->second.numberValues[0] - 0.25) < 0.0001 &&
			std::fabs(valueIt->second.numberValues[1] - 0.75) < 0.0001,
		"vector mismatch snaps to validated target shape");
}

void TestRuntimeLiveStateTriggerOverlayIncrementsAndClears()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\npulse";
	update.layerKey = "layer-one";
	update.parameterKey = "pulse";
	update.targetValue = JsonValue(true);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerStateWithDefinitions({ TriggerDefinition("pulse", "Pulse") }) };
	states[0].timeSeconds = 42.0;
	ShaderParameterValue pulse;
	pulse.numberValues = { 2.0, 10.0 };
	states[0].parameterValues["pulse"] = pulse;

	std::vector<RuntimeLiveOscCommitRequest> commitRequests;
	RuntimeLiveStateApplyOptions options;
	options.allowCommit = true;
	options.smoothing = 0.0;
	options.commitDelay = std::chrono::milliseconds(0);
	liveState.ApplyToLayerStates(states, options, &commitRequests);

	const auto valueIt = states[0].parameterValues.find("pulse");
	Expect(valueIt != states[0].parameterValues.end(), "trigger overlay writes pulse");
	Expect(valueIt->second.numberValues.size() == 2 &&
			std::fabs(valueIt->second.numberValues[0] - 3.0) < 0.0001 &&
			std::fabs(valueIt->second.numberValues[1] - 42.0) < 0.0001,
		"trigger overlay increments count and stamps layer time");
	Expect(commitRequests.empty(), "trigger overlay does not queue commit");
	Expect(liveState.OverlayCount() == 0, "trigger overlay clears after apply");
}

void TestRuntimeLiveStateClearsOverlaysForLayerKey()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate first;
	first.routeKey = "layer-one\namount";
	first.layerKey = "layer-one";
	first.parameterKey = "amount";
	first.targetValue = JsonValue(0.5);

	RuntimeLiveOscUpdate second = first;
	second.routeKey = "layer-two\namount";
	second.layerKey = "layer-two";
	second.targetValue = JsonValue(0.75);

	liveState.ApplyOscUpdates({ first, second });
	liveState.ClearForLayerKey("layer-one");

	Expect(liveState.OverlayCount() == 1, "layer-scoped invalidation only removes matching overlays");

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	states[0].layerId = "layer-two";
	RuntimeLiveStateApplyOptions options;
	options.smoothing = 0.0;
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("amount");
	Expect(valueIt != states[0].parameterValues.end() &&
			!valueIt->second.numberValues.empty() &&
			std::fabs(valueIt->second.numberValues[0] - 0.75) < 0.0001,
		"unmatched layer invalidation preserves unrelated overlay");
}

void TestRuntimeLiveStatePrunesRemovedLayerOverlay()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "removed-layer\namount";
	update.layerKey = "removed-layer";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.5);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerState() };
	liveState.PruneIncompatibleOverlays(states);

	Expect(liveState.OverlayCount() == 0, "invalidation policy removes overlays for missing layers");
}

void TestRuntimeLiveStatePrunesIncompatibleParameterOverlay()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.5);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerStateWithDefinitions({ FloatDefinition("other", "Other") }) };
	liveState.PruneIncompatibleOverlays(states);

	Expect(liveState.OverlayCount() == 0, "invalidation policy removes overlays for missing parameters");
}

void TestRuntimeLiveStatePreservesCompatibleOverlayAfterShaderReload()
{
	RuntimeLiveState liveState;

	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "Amount";
	update.targetValue = JsonValue(0.5);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> states = { MakeLayerStateWithDefinitions({ FloatDefinition("amount-renamed", "Amount") }) };
	liveState.PruneIncompatibleOverlays(states);

	Expect(liveState.OverlayCount() == 1, "invalidation policy preserves overlays that still map by control key");

	RuntimeLiveStateApplyOptions options;
	options.smoothing = 0.0;
	liveState.ApplyToLayerStates(states, options, nullptr);

	const auto valueIt = states[0].parameterValues.find("amount-renamed");
	Expect(valueIt != states[0].parameterValues.end() &&
			!valueIt->second.numberValues.empty() &&
			std::fabs(valueIt->second.numberValues[0] - 0.5) < 0.0001,
		"compatible overlay applies to the reloaded parameter id");
}

void TestRenderStateComposerBuildsFrameState()
{
	RuntimeLiveState liveState;
	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "Test Shader";
	update.parameterKey = "Amount";
	update.targetValue = JsonValue(0.6);
	liveState.ApplyOscUpdates({ update });

	LayeredRenderStateInput input;
	std::vector<RuntimeRenderState> baseLayerStates = { MakeLayerState() };
	input.committedLiveLayerStates = &baseLayerStates;
	input.transientAutomationOverlay = &liveState;
	input.allowTransientAutomationCommits = false;
	input.transientAutomationSmoothing = 0.0;

	RenderStateComposer composer;
	RenderStateCompositionResult result = composer.BuildFrameState(input);

	Expect(result.hasLayerStates, "composer reports that it composed base layer states");
	Expect(result.layerStates.size() == 1, "composer returns composed layer state");
	const auto valueIt = result.layerStates[0].parameterValues.find("amount");
	Expect(valueIt != result.layerStates[0].parameterValues.end(), "composer applies live overlay through live state");
	Expect(!valueIt->second.numberValues.empty() && std::fabs(valueIt->second.numberValues[0] - 0.6) < 0.0001,
		"composer uses OSC key matching against shader names and labels");
	const auto baseValueIt = baseLayerStates[0].parameterValues.find("amount");
	Expect(baseValueIt != baseLayerStates[0].parameterValues.end() &&
		!baseValueIt->second.numberValues.empty() &&
		std::fabs(baseValueIt->second.numberValues[0] - 0.25) < 0.0001,
		"composer leaves base layer states unchanged");
}

void TestRenderStateComposerUsesCommittedLayerOverBaseLayer()
{
	std::vector<RuntimeRenderState> basePersistedLayerStates = { MakeLayerState() };
	std::vector<RuntimeRenderState> committedLiveLayerStates = { MakeLayerState() };
	committedLiveLayerStates[0].parameterValues["amount"].numberValues = { 0.4 };

	LayeredRenderStateInput input;
	input.basePersistedLayerStates = &basePersistedLayerStates;
	input.committedLiveLayerStates = &committedLiveLayerStates;

	RenderStateComposer composer;
	RenderStateCompositionResult result = composer.BuildFrameState(input);

	const auto valueIt = result.layerStates[0].parameterValues.find("amount");
	Expect(valueIt != result.layerStates[0].parameterValues.end() &&
			!valueIt->second.numberValues.empty() &&
			std::fabs(valueIt->second.numberValues[0] - 0.4) < 0.0001,
		"committed live layer overrides base persisted layer");
	const auto baseValueIt = basePersistedLayerStates[0].parameterValues.find("amount");
	Expect(baseValueIt != basePersistedLayerStates[0].parameterValues.end() &&
			!baseValueIt->second.numberValues.empty() &&
			std::fabs(baseValueIt->second.numberValues[0] - 0.25) < 0.0001,
		"committed override leaves base persisted layer unchanged");
}

void TestRenderStateComposerUsesBaseLayerWhenCommittedLayerMissing()
{
	std::vector<RuntimeRenderState> basePersistedLayerStates = { MakeLayerState() };

	LayeredRenderStateInput input;
	input.basePersistedLayerStates = &basePersistedLayerStates;

	RenderStateComposer composer;
	RenderStateCompositionResult result = composer.BuildFrameState(input);

	Expect(result.hasLayerStates, "composer can use base persisted layer states without committed layer states");
	const auto valueIt = result.layerStates[0].parameterValues.find("amount");
	Expect(valueIt != result.layerStates[0].parameterValues.end() &&
			!valueIt->second.numberValues.empty() &&
			std::fabs(valueIt->second.numberValues[0] - 0.25) < 0.0001,
		"base persisted value is used when no committed live value exists");
}

void TestRenderStateComposerQueuesCommitRequestsWhenEnabled()
{
	RuntimeLiveState liveState;
	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.8);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> baseLayerStates = { MakeLayerState() };
	LayeredRenderStateInput input;
	input.committedLiveLayerStates = &baseLayerStates;
	input.transientAutomationOverlay = &liveState;
	input.allowTransientAutomationCommits = true;
	input.collectTransientAutomationCommitRequests = true;
	input.transientAutomationSmoothing = 0.0;
	input.transientAutomationCommitDelay = std::chrono::milliseconds(0);
	input.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1);

	RenderStateComposer composer;
	RenderStateCompositionResult result = composer.BuildFrameState(input);

	Expect(result.commitRequests.size() == 1, "composer returns live commit requests when collection is enabled");
	Expect(result.commitRequests[0].routeKey == "layer-one\namount", "composer commit request preserves route");
	Expect(result.commitRequests[0].generation == 1, "composer commit request preserves generation");
}

void TestRenderStateComposerSuppressesCommitCollection()
{
	RuntimeLiveState liveState;
	RuntimeLiveOscUpdate update;
	update.routeKey = "layer-one\namount";
	update.layerKey = "layer-one";
	update.parameterKey = "amount";
	update.targetValue = JsonValue(0.7);
	liveState.ApplyOscUpdates({ update });

	std::vector<RuntimeRenderState> baseLayerStates = { MakeLayerState() };
	LayeredRenderStateInput input;
	input.committedLiveLayerStates = &baseLayerStates;
	input.transientAutomationOverlay = &liveState;
	input.allowTransientAutomationCommits = true;
	input.collectTransientAutomationCommitRequests = false;
	input.transientAutomationSmoothing = 0.0;
	input.transientAutomationCommitDelay = std::chrono::milliseconds(0);
	input.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1);

	RenderStateComposer composer;
	RenderStateCompositionResult result = composer.BuildFrameState(input);

	Expect(result.commitRequests.empty(), "composer can apply overlays without collecting commit requests");
	const auto valueIt = result.layerStates[0].parameterValues.find("amount");
	Expect(valueIt != result.layerStates[0].parameterValues.end() &&
		!valueIt->second.numberValues.empty() &&
		std::fabs(valueIt->second.numberValues[0] - 0.7) < 0.0001,
		"composer still applies overlays when commit collection is disabled");
}
}

int main()
{
	TestRuntimeLiveStateAppliesLatestOscOverlay();
	TestRuntimeLiveStateIgnoresStaleCommitCompletions();
	TestRuntimeLiveStateQueuesAndCompletesCommit();
	TestRuntimeLiveStateQueuesOneCommitPerGeneration();
	TestRuntimeLiveStateSmoothingZeroAppliesTargetImmediately();
	TestRuntimeLiveStateSmoothingOneConvergesImmediately();
	TestRuntimeLiveStateSmoothingPartiallyConverges();
	TestRuntimeLiveStateSmoothingVectorSizeMismatchUsesTargetShape();
	TestRuntimeLiveStateTriggerOverlayIncrementsAndClears();
	TestRuntimeLiveStateClearsOverlaysForLayerKey();
	TestRuntimeLiveStatePrunesRemovedLayerOverlay();
	TestRuntimeLiveStatePrunesIncompatibleParameterOverlay();
	TestRuntimeLiveStatePreservesCompatibleOverlayAfterShaderReload();
	TestRenderStateComposerBuildsFrameState();
	TestRenderStateComposerUsesCommittedLayerOverBaseLayer();
	TestRenderStateComposerUsesBaseLayerWhenCommittedLayerMissing();
	TestRenderStateComposerQueuesCommitRequestsWhenEnabled();
	TestRenderStateComposerSuppressesCommitCollection();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " RuntimeLiveState test failure(s).\n";
		return 1;
	}

	std::cout << "RuntimeLiveState tests passed.\n";
	return 0;
}
