Repository: RetroAchievements/RAIntegration
Pull request #645 (2020-12-12 23:07:55)

improve asset versioning
fixes issue where refreshing an unmodified achievement wouldn't pick up changes from the local file.


Patch:
diff --git a/src/data/DataModelBase.cpp b/src/data/DataModelBase.cpp
index a5cd5c35..6e4ce732 100644
--- a/src/data/DataModelBase.cpp
+++ b/src/data/DataModelBase.cpp
@@ -7,10 +7,28 @@ const BoolModelProperty DataModelBase::IsModifiedProperty("DataModelBase", "IsMo
 
 void DataModelBase::OnValueChanged(const BoolModelProperty::ChangeArgs& args)
 {
-    if (m_pTransaction != nullptr && IsTransactional(args.Property))
+    if (&args != m_pEndUpdateChangeArgs)
     {
-        m_pTransaction->ValueChanged(args);
-        SetValue(IsModifiedProperty, m_pTransaction->IsModified());
+        if (m_pTransaction != nullptr && IsTransactional(args.Property))
+        {
+            m_pTransaction->ValueChanged(args);
+            SetValue(IsModifiedProperty, m_pTransaction->IsModified());
+        }
+
+        if (m_pUpdateTransaction)
+        {
+            for (auto& pChange : m_pUpdateTransaction->m_vDelayedBoolChanges)
+            {
+                if (pChange.pProperty == &args.Property)
+                {
+                    pChange.tNewValue = args.tNewValue;
+                    return;
+                }
+            }
+
+            m_pUpdateTransaction->m_vDelayedBoolChanges.push_back({ &args.Property, args.tOldValue, args.tNewValue });
+            return;
+        }
     }
 
     if (!m_vNotifyTargets.empty())
@@ -46,10 +64,28 @@ void DataModelBase::Transaction::ValueChanged(const BoolModelProperty::ChangeArg
 
 void DataModelBase::OnValueChanged(const StringModelProperty::ChangeArgs& args)
 {
-    if (m_pTransaction != nullptr && IsTransactional(args.Property))
+    if (&args != m_pEndUpdateChangeArgs)
     {
-        m_pTransaction->ValueChanged(args);
-        SetValue(IsModifiedProperty, m_pTransaction->IsModified());
+        if (m_pTransaction != nullptr && IsTransactional(args.Property))
+        {
+            m_pTransaction->ValueChanged(args);
+            SetValue(IsModifiedProperty, m_pTransaction->IsModified());
+        }
+
+        if (m_pUpdateTransaction)
+        {
+            for (auto& pChange : m_pUpdateTransaction->m_vDelayedStringChanges)
+            {
+                if (pChange.pProperty == &args.Property)
+                {
+                    pChange.tNewValue = args.tNewValue;
+                    return;
+                }
+            }
+
+            m_pUpdateTransaction->m_vDelayedStringChanges.push_back({ &args.Property, args.tOldValue, args.tNewValue });
+            return;
+        }
     }
 
     if (!m_vNotifyTargets.empty())
@@ -85,10 +121,28 @@ void DataModelBase::Transaction::ValueChanged(const StringModelProperty::ChangeA
 
 void DataModelBase::OnValueChanged(const IntModelProperty::ChangeArgs& args)
 {
-    if (m_pTransaction != nullptr && IsTransactional(args.Property))
+    if (&args != m_pEndUpdateChangeArgs)
     {
-        m_pTransaction->ValueChanged(args);
-        SetValue(IsModifiedProperty, m_pTransaction->IsModified());
+        if (m_pTransaction != nullptr && IsTransactional(args.Property))
+        {
+            m_pTransaction->ValueChanged(args);
+            SetValue(IsModifiedProperty, m_pTransaction->IsModified());
+        }
+
+        if (m_pUpdateTransaction)
+        {
+            for (auto& pChange : m_pUpdateTransaction->m_vDelayedIntChanges)
+            {
+                if (pChange.pProperty == &args.Property)
+                {
+                    pChange.tNewValue = args.tNewValue;
+                    return;
+                }
+            }
+
+            m_pUpdateTransaction->m_vDelayedIntChanges.push_back({ &args.Property, args.tOldValue, args.tNewValue });
+            return;
+        }
     }
 
     if (!m_vNotifyTargets.empty())
@@ -122,6 +176,58 @@ void DataModelBase::Transaction::ValueChanged(const IntModelProperty::ChangeArgs
     }
 }
 
+
+void DataModelBase::BeginUpdate()
+{
+    if (m_pUpdateTransaction == nullptr)
+        m_pUpdateTransaction = std::make_unique<UpdateTransaction>();
+
+    ++m_pUpdateTransaction->m_nUpdateCount;
+}
+
+void DataModelBase::EndUpdate()
+{
+    if (m_pUpdateTransaction && --m_pUpdateTransaction->m_nUpdateCount == 0)
+    {
+        std::unique_ptr<UpdateTransaction> pUpdateTransaction = std::move(m_pUpdateTransaction);
+
+        // m_pEndUpdateChangeArgs is set to each ChangeArgs as we call OnValueChanged so it doesn't try to
+        // apply the change to the transaction - the transaction was updated the first time it was called
+
+        for (const auto& pChange : pUpdateTransaction->m_vDelayedIntChanges)
+        {
+            if (pChange.tNewValue != pChange.tOldValue)
+            {
+                IntModelProperty::ChangeArgs args{ *pChange.pProperty, pChange.tOldValue, pChange.tNewValue };
+                m_pEndUpdateChangeArgs = &args;
+                OnValueChanged(args);
+            }
+        }
+
+        for (const auto& pChange : pUpdateTransaction->m_vDelayedBoolChanges)
+        {
+            if (pChange.tNewValue != pChange.tOldValue)
+            {
+                BoolModelProperty::ChangeArgs args{ *pChange.pProperty, pChange.tOldValue, pChange.tNewValue };
+                m_pEndUpdateChangeArgs = &args;
+                OnValueChanged(args);
+            }
+        }
+
+        for (const auto& pChange : pUpdateTransaction->m_vDelayedStringChanges)
+        {
+            if (pChange.tNewValue != pChange.tOldValue)
+            {
+                StringModelProperty::ChangeArgs args{ *pChange.pProperty, pChange.tOldValue, pChange.tNewValue };
+                m_pEndUpdateChangeArgs = &args;
+                OnValueChanged(args);
+            }
+        }
+
+        m_pEndUpdateChangeArgs = nullptr;
+    }
+}
+
 void DataModelBase::BeginTransaction()
 {
     auto pTransaction = std::make_unique<Transaction>();
@@ -171,7 +277,7 @@ void DataModelBase::RevertTransaction()
     }
 }
 
-void DataModelBase::Transaction::Revert(DataModelBase& vmViewModel)
+void DataModelBase::Transaction::Revert(DataModelBase& pModel)
 {
     // swap out the map while we process it to prevent re-entrant calls to ValueChanged from
     // modifying it while we're iterating
@@ -183,14 +289,14 @@ void DataModelBase::Transaction::Revert(DataModelBase& vmViewModel)
         const IntModelProperty* pIntProperty = dynamic_cast<const IntModelProperty*>(pProperty);
         if (pIntProperty != nullptr)
         {
-            vmViewModel.SetValue(*pIntProperty, pPair.second);
+            pModel.SetValue(*pIntProperty, pPair.second);
         }
         else
         {
             const BoolModelProperty* pBoolProperty = dynamic_cast<const BoolModelProperty*>(pProperty);
             if (pBoolProperty != nullptr)
             {
-                vmViewModel.SetValue(*pBoolProperty, pPair.second);
+                pModel.SetValue(*pBoolProperty, pPair.second);
             }
         }
     }
@@ -203,12 +309,12 @@ void DataModelBase::Transaction::Revert(DataModelBase& vmViewModel)
         const StringModelProperty* pStringProperty = dynamic_cast<const StringModelProperty*>(pProperty);
         if (pStringProperty != nullptr)
         {
-            vmViewModel.SetValue(*pStringProperty, pPair.second);
+            pModel.SetValue(*pStringProperty, pPair.second);
         }
     }
 }
 
-void DataModelBase::Transaction::Commit(const DataModelBase& vmViewModel)
+void DataModelBase::Transaction::Commit(const DataModelBase& pModel)
 {
     if (!m_pNext)
         return;
@@ -229,7 +335,7 @@ void DataModelBase::Transaction::Commit(const DataModelBase& vmViewModel)
             const IntModelProperty* pIntProperty = dynamic_cast<const IntModelProperty*>(pProperty);
             if (pIntProperty != nullptr)
             {
-                if (vmViewModel.GetValue(*pIntProperty) == pScan->second)
+                if (pModel.GetValue(*pIntProperty) == pScan->second)
                     m_pNext->m_mOriginalIntValues.erase(pScan);
             }
             else
@@ -237,7 +343,7 @@ void DataModelBase::Transaction::Commit(const DataModelBase& vmViewModel)
                 const BoolModelProperty* pBoolProperty = dynamic_cast<const BoolModelProperty*>(pProperty);
                 if (pBoolProperty != nullptr)
                 {
-                    if (vmViewModel.GetValue(*pBoolProperty) == gsl::narrow_cast<bool>(pScan->second))
+                    if (pModel.GetValue(*pBoolProperty) == gsl::narrow_cast<bool>(pScan->second))
                         m_pNext->m_mOriginalIntValues.erase(pScan);
                 }
             }
@@ -260,7 +366,7 @@ void DataModelBase::Transaction::Commit(const DataModelBase& vmViewModel)
             const StringModelProperty* pStringProperty = dynamic_cast<const StringModelProperty*>(pProperty);
             if (pStringProperty != nullptr)
             {
-                if (vmViewModel.GetValue(*pStringProperty) == pScan->second)
+                if (pModel.GetValue(*pStringProperty) == pScan->second)
                     m_pNext->m_mOriginalStringValues.erase(pScan);
             }
         }
diff --git a/src/data/DataModelBase.hh b/src/data/DataModelBase.hh
index d4fdf2bb..8011118c 100644
--- a/src/data/DataModelBase.hh
+++ b/src/data/DataModelBase.hh
@@ -252,6 +252,29 @@ protected:
     void OnValueChanged(const StringModelProperty::ChangeArgs& args) override;
     void OnValueChanged(const IntModelProperty::ChangeArgs& args) override;
 
+    /// <summary>
+    /// Disables change notifications.
+    /// </summary>
+    /// <remarks>
+    /// Should not be called unless making multiple changes to the same property. 
+    /// OnValueChanged will be called once after EndUpdate is called instead of for each intermediate change.
+    /// </remarks>
+    void BeginUpdate();
+
+    /// <summary>
+    /// Re-enables change notifications.
+    /// </summary>
+    void EndUpdate();
+
+    /// <summary>
+    /// Determines if change notifications are disabled.
+    /// </summary>
+    /// <remarks>
+    /// Can be used in OnValueChanged overrides to determine if change notifications are disabled. If they are, OnValueChanged
+    /// will be called again once the notifications are re-enabled, so processing should be delayed if this returns true.
+    /// </remarks>
+    virtual bool IsUpdating() const noexcept(false) { return (m_pUpdateTransaction != nullptr); }
+
 private:
     void DiscardTransaction();
 
@@ -271,6 +294,24 @@ private:
     }
 
     std::set<int> m_vTransactionalProperties;
+
+    struct UpdateTransaction
+    {
+        unsigned int m_nUpdateCount = 0;
+
+        template<class T>
+        struct DelayedChange
+        {
+            const ModelProperty<T>* pProperty;
+            T tOldValue;
+            T tNewValue;
+        };
+        std::vector<DelayedChange<int>> m_vDelayedIntChanges;
+        std::vector<DelayedChange<std::wstring>> m_vDelayedStringChanges;
+        std::vector<DelayedChange<bool>> m_vDelayedBoolChanges;
+    };
+    std::unique_ptr<UpdateTransaction> m_pUpdateTransaction;
+    const void* m_pEndUpdateChangeArgs = nullptr;
 };
 
 } // namespace data
diff --git a/src/data/ModelCollectionBase.cpp b/src/data/ModelCollectionBase.cpp
index cc06e6f5..c440f9f4 100644
--- a/src/data/ModelCollectionBase.cpp
+++ b/src/data/ModelCollectionBase.cpp
@@ -254,7 +254,7 @@ void ModelCollectionBase::UpdateIndices()
         std::sort(vDeletedIndices.rbegin(), vDeletedIndices.rend());
 
         // remove the deleted items from the collection
-        m_vItems.erase(m_vItems.begin() + m_nSize);
+        m_vItems.erase(m_vItems.begin() + m_nSize, m_vItems.end());
 
         // update the indices of any items after the deleted items
         for (auto& pItem : m_vItems)
diff --git a/src/data/context/GameAssets.cpp b/src/data/context/GameAssets.cpp
index 8bab390f..5f0bc2e7 100644
--- a/src/data/context/GameAssets.cpp
+++ b/src/data/context/GameAssets.cpp
@@ -126,7 +126,6 @@ void GameAssets::ReloadAssets(const std::vector<ra::data::models::AssetModelBase
                 {
                     pAsset = pAssetToReset;
                     vRemainingAssetsToReload.erase(pIter);
-                    pAsset->RestoreServerCheckpoint();
                     break;
                 }
             }
@@ -140,8 +139,7 @@ void GameAssets::ReloadAssets(const std::vector<ra::data::models::AssetModelBase
         if (pAsset)
         {
             // merge the data from the file
-            pAsset->Deserialize(pTokenizer);
-            pAsset->UpdateLocalCheckpoint();
+            pAsset->ResetLocalCheckpoint(pTokenizer);
         }
         else if (bReloadAll)
         {
diff --git a/src/data/models/AchievementModel.cpp b/src/data/models/AchievementModel.cpp
index 0dd0ff78..091df5ba 100644
--- a/src/data/models/AchievementModel.cpp
+++ b/src/data/models/AchievementModel.cpp
@@ -16,7 +16,7 @@ const IntModelProperty AchievementModel::PointsProperty("AchievementModel", "Poi
 const StringModelProperty AchievementModel::BadgeProperty("AchievementModel", "Badge", L"00000");
 const BoolModelProperty AchievementModel::PauseOnResetProperty("AchievementModel", "PauseOnReset", false);
 const BoolModelProperty AchievementModel::PauseOnTriggerProperty("AchievementModel", "PauseOnTrigger", false);
-const IntModelProperty AchievementModel::TriggerProperty("AchievementModel", "Trigger", ra::etoi(AssetChanges::None));
+const IntModelProperty AchievementModel::TriggerProperty("AchievementModel", "Trigger", 0);
 
 AchievementModel::AchievementModel() noexcept
 {
@@ -41,27 +41,39 @@ void AchievementModel::Deactivate()
 
 void AchievementModel::OnValueChanged(const IntModelProperty::ChangeArgs& args)
 {
-    if (args.Property == StateProperty)
+    // if we're still loading the asset, ignore the event. we'll get recalled once loading finishes
+    if (!IsUpdating())
     {
-        const bool bWasActive = IsActive(ra::itoe<AssetState>(args.tOldValue));
-        const bool bIsActive = IsActive(ra::itoe<AssetState>(args.tNewValue));
-        if (bWasActive != bIsActive)
+        if (args.Property == StateProperty)
         {
-            auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
-            const bool bRuntimeActive = (pRuntime.GetAchievementTrigger(GetID()) != nullptr);
-            if (bRuntimeActive != bIsActive)
+            const bool bWasActive = IsActive(ra::itoe<AssetState>(args.tOldValue));
+            const bool bIsActive = IsActive(ra::itoe<AssetState>(args.tNewValue));
+            if (bWasActive != bIsActive)
             {
-                if (bIsActive)
-                    pRuntime.ActivateAchievement(GetID(), GetTrigger());
-                else
-                    pRuntime.DeactivateAchievement(GetID());
+                auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
+                const bool bRuntimeActive = (pRuntime.GetAchievementTrigger(GetID()) != nullptr);
+                if (bRuntimeActive != bIsActive)
+                {
+                    if (bIsActive)
+                        pRuntime.ActivateAchievement(GetID(), GetTrigger());
+                    else
+                        pRuntime.DeactivateAchievement(GetID());
+                }
             }
-        }
 
-        if (ra::itoe<AssetState>(args.tNewValue) == AssetState::Triggered)
+            if (ra::itoe<AssetState>(args.tNewValue) == AssetState::Triggered)
+            {
+                const auto& pClock = ra::services::ServiceLocator::Get<ra::services::IClock>();
+                m_tUnlock = pClock.Now();
+            }
+        }
+        else if (args.Property == TriggerProperty)
         {
-            const auto& pClock = ra::services::ServiceLocator::Get<ra::services::IClock>();
-            m_tUnlock = pClock.Now();
+            if (IsActive())
+            {
+                auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
+                pRuntime.ActivateAchievement(GetID(), GetTrigger());
+            }
         }
     }
 
@@ -70,10 +82,14 @@ void AchievementModel::OnValueChanged(const IntModelProperty::ChangeArgs& args)
 
 void AchievementModel::OnValueChanged(const StringModelProperty::ChangeArgs& args)
 {
-    // call base first so the transaction gets updated, updating the LocalBadgesModel is just bookkeeping.
+    // call base first so the transaction gets updated for GetLocalValue to work properly
     AssetModelBase::OnValueChanged(args);
 
-    if (args.Property == BadgeProperty && m_pTransaction && m_pTransaction->m_pNext) /* ignore until we've created a checkpoint */
+    // if we're still loading the asset, ignore the event. we'll get recalled once loading finishes
+    if (IsUpdating())
+        return;
+
+    if (args.Property == BadgeProperty)
     {
         if (ra::StringStartsWith(args.tOldValue, L"local\\"))
         {
diff --git a/src/data/models/AchievementModel.hh b/src/data/models/AchievementModel.hh
index 99d160d8..10f5275a 100644
--- a/src/data/models/AchievementModel.hh
+++ b/src/data/models/AchievementModel.hh
@@ -74,7 +74,7 @@ public:
     void SetPauseOnTrigger(bool nValue) { SetValue(PauseOnTriggerProperty, nValue); }
 
     /// <summary>
-    /// The <see cref="ModelProperty" /> for the trigger modification state.
+    /// The <see cref="ModelProperty" /> for the trigger version.
     /// </summary>
     static const IntModelProperty TriggerProperty;
 
diff --git a/src/data/models/AssetModelBase.cpp b/src/data/models/AssetModelBase.cpp
index aaf2a634..bd30beac 100644
--- a/src/data/models/AssetModelBase.cpp
+++ b/src/data/models/AssetModelBase.cpp
@@ -169,7 +169,7 @@ void AssetModelBase::CreateLocalCheckpoint()
     Expects(m_pTransaction != nullptr);
     Expects(m_pTransaction->m_pNext == nullptr);
     const bool bModified = m_pTransaction->IsModified();
-    BeginTransaction();
+    BeginTransaction();       // start transaction for in-memory changes
 
     SetValue(ChangesProperty, bModified ? ra::etoi(AssetChanges::Unpublished) : ra::etoi(AssetChanges::None));
 }
@@ -178,21 +178,18 @@ void AssetModelBase::UpdateLocalCheckpoint()
 {
     Expects(m_pTransaction != nullptr);
     Expects(m_pTransaction->m_pNext != nullptr);
-    CommitTransaction();
-    BeginTransaction();
-
-    const bool bModified = m_pTransaction->m_pNext->IsModified();
-    SetValue(ChangesProperty, bModified ? ra::etoi(AssetChanges::Unpublished) : ra::etoi(AssetChanges::None));
+    CommitTransaction();      // commit in-memory changes
+    CreateLocalCheckpoint();  // start transaction for in-memory changes
 }
 
 void AssetModelBase::UpdateServerCheckpoint()
 {
     Expects(m_pTransaction != nullptr);
     Expects(m_pTransaction->m_pNext != nullptr);
-    CommitTransaction();
-    CommitTransaction();
-    BeginTransaction();
-    BeginTransaction();
+    CommitTransaction();     // commit in-memory changes
+    CommitTransaction();     // commit unpublished changes
+    BeginTransaction();      // start transaction for unpublished changes
+    BeginTransaction();      // start transaction for in-memory changes
 
     SetValue(ChangesProperty, ra::etoi(AssetChanges::None));
 }
@@ -201,23 +198,38 @@ void AssetModelBase::RestoreLocalCheckpoint()
 {
     Expects(m_pTransaction != nullptr);
     Expects(m_pTransaction->m_pNext != nullptr);
-    RevertTransaction();
-    BeginTransaction();
-
-    const bool bModified = m_pTransaction->m_pNext->IsModified();
-    SetValue(ChangesProperty, bModified ? ra::etoi(AssetChanges::Unpublished) : ra::etoi(AssetChanges::None));
+    RevertTransaction();      // discard in-memory changes
+    CreateLocalCheckpoint();  // start transaction for in-memory changes
 }
 
 void AssetModelBase::RestoreServerCheckpoint()
 {
     Expects(m_pTransaction != nullptr);
     Expects(m_pTransaction->m_pNext != nullptr);
-    RevertTransaction();
-    RevertTransaction();
-    BeginTransaction();
-    BeginTransaction();
+    BeginUpdate();
+    RevertTransaction();     // discard in-memory changes
+    RevertTransaction();     // discard unpublished changes
+    BeginTransaction();      // start transaction for unpublished changes
+    BeginTransaction();      // start transaction for in-memory changes
 
     SetValue(ChangesProperty, ra::etoi(AssetChanges::None));
+    EndUpdate();
+}
+
+void AssetModelBase::ResetLocalCheckpoint(ra::Tokenizer& pTokenizer)
+{
+    // this function is a conglomeration of RestoreServerCheckpoint and UpdateLocalCheckpoint that calls
+    // Deserialize to populate the local checkpoint instead of making local changes and commiting them.
+    Expects(m_pTransaction != nullptr);
+    Expects(m_pTransaction->m_pNext != nullptr);
+
+    BeginUpdate();           // disable notifications while rebuilding
+    RevertTransaction();     // discard in-memory changes
+    RevertTransaction();     // discard unpublished changes
+    BeginTransaction();      // start transaction for unpublished changes
+    Deserialize(pTokenizer); // load unpublished changes
+    CreateLocalCheckpoint(); // start transaction for in-memory changes
+    EndUpdate();             // re-enable notifications
 }
 
 void AssetModelBase::SetNew()
@@ -230,6 +242,16 @@ bool AssetModelBase::HasUnpublishedChanges() const noexcept
     return (m_pTransaction && m_pTransaction->m_pNext && m_pTransaction->m_pNext->IsModified());
 }
 
+bool AssetModelBase::IsUpdating() const
+{
+    if (DataModelBase::IsUpdating())
+        return true;
+
+    // IsUpdating is used to prevent processing change notifications while things are in flux.
+    // leverage that to also prevent processing change notifications while initializing the asset.
+    return (!m_pTransaction || !m_pTransaction->m_pNext);
+}
+
 void AssetModelBase::OnValueChanged(const IntModelProperty::ChangeArgs& args)
 {
     DataModelBase::OnValueChanged(args);
@@ -242,11 +264,11 @@ void AssetModelBase::OnValueChanged(const StringModelProperty::ChangeArgs& args)
 
 void AssetModelBase::OnValueChanged(const BoolModelProperty::ChangeArgs& args)
 {
-    if (args.Property == IsModifiedProperty)
+    // if IsUpdating is true, we're either setting up the object or updating the checkpoints.
+    // the Changes state will be updated appropriately later.
+    if (!IsUpdating())
     {
-        // if either transaction doesn't exist, we're either setting up the object
-        // or updating the checkpoints. the state will be updated appropriately later
-        if (m_pTransaction && m_pTransaction->m_pNext)
+        if (args.Property == IsModifiedProperty)
         {
             if (args.tNewValue)
             {
@@ -313,9 +335,35 @@ int AssetModelBase::GetLocalValue(const IntModelProperty& pProperty) const
     return GetValue(pProperty);
 }
 
+AssetChanges AssetModelBase::GetAssetDefinitionState(const AssetDefinition& pAsset) const
+{
+    return ra::itoe<AssetChanges>(GetValue(*pAsset.m_pProperty) & 0x03);
+}
+
+void AssetModelBase::UpdateAssetDefinitionVersion(const AssetDefinition& pAsset, AssetChanges nState)
+{
+    const auto& sDefinition = GetAssetDefinition(pAsset, nState);
+
+    // DJB2 hash the definition
+    int nHash = 5381;
+    for (const auto c : sDefinition)
+        nHash = ((nHash << 5) + nHash) + c; /* hash * 33 + c */
+
+    // inject the state into the two lowermost bits - we want the state in the checkpoints so it gets restored
+    // when reverted. we can reconstruct the other properties from the state (see RevertTransaction).
+    nHash <<= 2; // make sure to shift, as DJB2 is sensitive to the last character of the string.
+    nHash |= ra::etoi(nState);
+
+    SetValue(*pAsset.m_pProperty, nHash);
+}
+
 const std::string& AssetModelBase::GetAssetDefinition(const AssetDefinition& pAsset) const
 {
-    const auto nState = ra::itoe<AssetChanges>(GetValue(*pAsset.m_pProperty));
+    return GetAssetDefinition(pAsset, GetAssetDefinitionState(pAsset));
+}
+
+const std::string& AssetModelBase::GetAssetDefinition(const AssetDefinition& pAsset, AssetChanges nState) noexcept
+{
     switch (nState)
     {
         case AssetChanges::None:
@@ -342,22 +390,29 @@ void AssetModelBase::SetAssetDefinition(AssetDefinition& pAsset, const std::stri
     if (m_pTransaction == nullptr)
     {
         // before core checkpoint
-        pAsset.m_sCoreDefinition = sValue;
+        if (pAsset.m_sCoreDefinition != sValue)
+        {
+            pAsset.m_sCoreDefinition = sValue;
+            UpdateAssetDefinitionVersion(pAsset, AssetChanges::None);
+        }
     }
     else if (m_pTransaction->m_pNext == nullptr)
     {
         // before local checkpoint
         if (sValue == pAsset.m_sCoreDefinition)
         {
-            pAsset.m_sLocalDefinition.clear();
-            SetValue(*pAsset.m_pProperty, ra::etoi(AssetChanges::None));
-            pAsset.m_bLocalModified = false;
+            if (!pAsset.m_sLocalDefinition.empty())
+            {
+                pAsset.m_sLocalDefinition.clear();
+                pAsset.m_bLocalModified = false;
+                UpdateAssetDefinitionVersion(pAsset, AssetChanges::None);
+            }
         }
-        else
+        else if (sValue != pAsset.m_sLocalDefinition)
         {
             pAsset.m_sLocalDefinition = sValue;
-            SetValue(*pAsset.m_pProperty, ra::etoi(AssetChanges::Unpublished));
             pAsset.m_bLocalModified = true;
+            UpdateAssetDefinitionVersion(pAsset, AssetChanges::Unpublished);
         }
     }
     else
@@ -365,18 +420,24 @@ void AssetModelBase::SetAssetDefinition(AssetDefinition& pAsset, const std::stri
         // after local checkpoint
         if (pAsset.m_bLocalModified && sValue == pAsset.m_sLocalDefinition)
         {
-            pAsset.m_sCurrentDefinition.clear();
-            SetValue(*pAsset.m_pProperty, ra::etoi(AssetChanges::Unpublished));
+            if (!pAsset.m_sCurrentDefinition.empty())
+            {
+                pAsset.m_sCurrentDefinition.clear();
+                UpdateAssetDefinitionVersion(pAsset, AssetChanges::Unpublished);
+            }
         }
         else if (!pAsset.m_bLocalModified && sValue == pAsset.m_sCoreDefinition)
         {
-            pAsset.m_sCurrentDefinition.clear();
-            SetValue(*pAsset.m_pProperty, ra::etoi(AssetChanges::None));
+            if (!pAsset.m_sCurrentDefinition.empty())
+            {
+                pAsset.m_sCurrentDefinition.clear();
+                UpdateAssetDefinitionVersion(pAsset, AssetChanges::None);
+            }
         }
-        else
+        else if (pAsset.m_sCurrentDefinition != sValue)
         {
             pAsset.m_sCurrentDefinition = sValue;
-            SetValue(*pAsset.m_pProperty, ra::etoi(AssetChanges::Modified));
+            UpdateAssetDefinitionVersion(pAsset, AssetChanges::Modified);
         }
     }
 }
@@ -392,17 +453,15 @@ void AssetModelBase::CommitTransaction()
         {
             Expects(pAsset != nullptr);
 
-            const auto nState = ra::itoe<AssetChanges>(GetValue(*pAsset->m_pProperty));
-            if (nState == AssetChanges::Unpublished)
+            if (GetAssetDefinitionState(*pAsset) == AssetChanges::Unpublished)
             {
                 if (pAsset->m_bLocalModified)
                 {
                     pAsset->m_sCoreDefinition.swap(pAsset->m_sLocalDefinition);
                     pAsset->m_sLocalDefinition.clear();
                     pAsset->m_bLocalModified = false;
+                    UpdateAssetDefinitionVersion(*pAsset, AssetChanges::None);
                 }
-
-                SetValue(*pAsset->m_pProperty, ra::etoi(AssetChanges::None));
             }
         }
     }
@@ -416,22 +475,21 @@ void AssetModelBase::CommitTransaction()
         {
             Expects(pAsset != nullptr);
 
-            const auto nState = ra::itoe<AssetChanges>(GetValue(*pAsset->m_pProperty));
-            if (nState == AssetChanges::Modified)
+            if (GetAssetDefinitionState(*pAsset) == AssetChanges::Modified)
             {
                 if (pAsset->m_sCurrentDefinition == pAsset->m_sCoreDefinition)
                 {
                     pAsset->m_sCurrentDefinition.clear();
                     pAsset->m_sLocalDefinition.clear();
                     pAsset->m_bLocalModified = false;
-                    SetValue(*pAsset->m_pProperty, ra::etoi(AssetChanges::None));
+                    UpdateAssetDefinitionVersion(*pAsset, AssetChanges::None);
                 }
-                else
+                else if (pAsset->m_sLocalDefinition != pAsset->m_sCurrentDefinition)
                 {
                     pAsset->m_sLocalDefinition.swap(pAsset->m_sCurrentDefinition);
                     pAsset->m_sCurrentDefinition.clear();
                     pAsset->m_bLocalModified = true;
-                    SetValue(*pAsset->m_pProperty, ra::etoi(AssetChanges::Unpublished));
+                    UpdateAssetDefinitionVersion(*pAsset, AssetChanges::Unpublished);
                 }
             }
         }
@@ -450,7 +508,7 @@ void AssetModelBase::RevertTransaction()
     {
         Expects(pAsset != nullptr);
 
-        const auto nState = ra::itoe<AssetChanges>(GetValue(*pAsset->m_pProperty));
+        const auto nState = GetAssetDefinitionState(*pAsset);
         switch (nState)
         {
             case AssetChanges::None:
diff --git a/src/data/models/AssetModelBase.hh b/src/data/models/AssetModelBase.hh
index 2b55fd85..4b7463e6 100644
--- a/src/data/models/AssetModelBase.hh
+++ b/src/data/models/AssetModelBase.hh
@@ -158,17 +158,17 @@ public:
     /// <summary>
     /// Activates the asset.
     /// </summary>
-    virtual void Activate() noexcept(false) { }
+    virtual void Activate() noexcept(false) {}
 
     /// <summary>
     /// Activates the asset.
     /// </summary>
-    virtual void Deactivate() noexcept(false) { }
+    virtual void Deactivate() noexcept(false) {}
 
     /// <summary>
     /// Updates the asset for the current frame.
     /// </summary>
-    virtual void DoFrame() noexcept(false) { }
+    virtual void DoFrame() noexcept(false) {}
 
     /// <summary>
     /// The <see cref="ModelProperty" /> for the asset state.
@@ -224,6 +224,12 @@ public:
     /// </summary>
     void RestoreServerCheckpoint();
 
+    /// <summary>
+    /// Discards any changes made since the server checkpoint and repopulates the local checkpoint using Deserialize.
+    /// </summary>
+    /// <param name="pTokenizer"></param>
+    void ResetLocalCheckpoint(ra::Tokenizer& pTokenizer);
+
 protected:
     /// <summary>
     /// Helper class for versioned ASCII strings to avoid overhead of UNICODE characters
@@ -266,6 +272,8 @@ protected:
     static bool ReadPossiblyQuoted(ra::Tokenizer& pTokenizer, std::string& sText);
     static bool ReadPossiblyQuoted(ra::Tokenizer& pTokenizer, std::wstring& sText);
 
+    bool IsUpdating() const override;
+
     void OnValueChanged(const IntModelProperty::ChangeArgs& args) override;
     void OnValueChanged(const StringModelProperty::ChangeArgs& args) override;
     void OnValueChanged(const BoolModelProperty::ChangeArgs& args) override;
@@ -279,6 +287,11 @@ protected:
     void RevertTransaction() override;
 
     static bool IsActive(AssetState nState) noexcept;
+
+private:
+    static const std::string& GetAssetDefinition(const AssetDefinition& pAsset, AssetChanges nState) noexcept;
+    AssetChanges GetAssetDefinitionState(const AssetDefinition& pAsset) const;
+    void UpdateAssetDefinitionVersion(const AssetDefinition&, AssetChanges nState);
 };
 
 } // namespace models
diff --git a/src/data/models/LocalBadgesModel.hh b/src/data/models/LocalBadgesModel.hh
index 3fd5a418..8f73a96b 100644
--- a/src/data/models/LocalBadgesModel.hh
+++ b/src/data/models/LocalBadgesModel.hh
@@ -33,8 +33,8 @@ public:
 private:
 	struct BadgeReferenceCount
 	{
-		int nUncommittedCount;
-		int nCommittedCount;
+		int nUncommittedCount; // references not written to XXX-user.txt
+		int nCommittedCount;   // references written to XXX-user.txt
 	};
 
 	mutable std::map<std::wstring, BadgeReferenceCount> m_mReferences;
diff --git a/src/ui/viewmodels/AssetEditorViewModel.cpp b/src/ui/viewmodels/AssetEditorViewModel.cpp
index f9660dff..7ca478e0 100644
--- a/src/ui/viewmodels/AssetEditorViewModel.cpp
+++ b/src/ui/viewmodels/AssetEditorViewModel.cpp
@@ -146,8 +146,8 @@ void AssetEditorViewModel::OnDataModelStringValueChanged(const StringModelProper
 
 void AssetEditorViewModel::OnDataModelIntValueChanged(const IntModelProperty::ChangeArgs& args)
 {
-    if (args.Property == TriggerViewModel::VersionProperty)
-        OnTriggerChanged();
+    if (args.Property == ra::data::models::AchievementModel::TriggerProperty)
+        UpdateTriggerBinding();
     else if (args.Property == ra::data::models::AssetModelBase::StateProperty)
         SetState(ra::itoe<ra::data::models::AssetState>(args.tNewValue));
     else if (args.Property == ra::data::models::AssetModelBase::CategoryProperty)
@@ -261,12 +261,7 @@ void AssetEditorViewModel::OnTriggerChanged()
         const std::string& sTrigger = Trigger().Serialize();
         pAchievement->SetTrigger(sTrigger);
 
-        if (m_pAsset->IsActive())
-        {
-            auto& pRuntime = ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>();
-            pRuntime.ActivateAchievement(GetID(), sTrigger);
-            UpdateTriggerBinding();
-        }
+        // if trigger has actually changed, code should call back through UpdateTriggerBinding to update the UI
     }
 }
 
@@ -275,7 +270,10 @@ void AssetEditorViewModel::UpdateTriggerBinding()
     const auto* pAchievement = dynamic_cast<ra::data::models::AchievementModel*>(m_pAsset);
     if (pAchievement != nullptr)
     {
-        const auto* pTrigger = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetAchievementTrigger(pAchievement->GetID());
+        const rc_trigger_t* pTrigger = nullptr;
+        if (pAchievement->IsActive())
+            pTrigger = ra::services::ServiceLocator::Get<ra::services::AchievementRuntime>().GetAchievementTrigger(pAchievement->GetID());
+
         if (pTrigger != nullptr)
             Trigger().UpdateFrom(*pTrigger);
         else
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index 860ff23c..d7242a6f 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -349,7 +349,7 @@ static void AddAltGroup(ViewModelCollection<TriggerViewModel::GroupViewModel>& v
     int nId, rc_condset_t* pConditionSet)
 {
     auto& vmAltGroup = vGroups.Add();
-    vmAltGroup.SetId(++nId);
+    vmAltGroup.SetId(nId);
     vmAltGroup.SetLabel(ra::StringPrintf(L"Alt %d", nId));
     vmAltGroup.m_pConditionSet = pConditionSet;
 }
@@ -421,7 +421,7 @@ void TriggerViewModel::UpdateFrom(const rc_trigger_t& pTrigger)
         if (vmAltGroup != nullptr)
             vmAltGroup->m_pConditionSet = pConditionSet;
         else
-            AddAltGroup(m_vGroups, gsl::narrow_cast<int>(nIndex), pConditionSet);
+            AddAltGroup(m_vGroups, gsl::narrow_cast<int>(++nIndex), pConditionSet);
     }
 
     // remove any extra alt groups
@@ -478,10 +478,17 @@ void TriggerViewModel::ConditionsMonitor::OnViewModelIntValueChanged(gsl::index,
 
 void TriggerViewModel::ConditionsMonitor::OnViewModelAdded(gsl::index)
 {
-    UpdateCurrentGroup();
+    if (!m_vmTrigger->Conditions().IsUpdating())
+        UpdateCurrentGroup();
 }
 
 void TriggerViewModel::ConditionsMonitor::OnViewModelRemoved(gsl::index)
+{
+    if (!m_vmTrigger->Conditions().IsUpdating())
+        UpdateCurrentGroup();
+}
+
+void TriggerViewModel::ConditionsMonitor::OnEndViewModelCollectionUpdate()
 {
     UpdateCurrentGroup();
 }
@@ -536,19 +543,28 @@ void TriggerViewModel::UpdateConditions(const GroupViewModel* pGroup)
     m_vConditions.RemoveNotifyTarget(m_pConditionsMonitor);
     m_vConditions.BeginUpdate();
 
-    m_vConditions.Clear();
+    int nIndex = 0;
 
     if (pGroup != nullptr && pGroup->m_pConditionSet)
     {
         rc_condition_t* pCondition = pGroup->m_pConditionSet->conditions;
         for (; pCondition != nullptr; pCondition = pCondition->next)
         {
-            auto& vmCondition = m_vConditions.Add();
-            vmCondition.SetIndex(gsl::narrow_cast<int>(m_vConditions.Count()));
-            vmCondition.InitializeFrom(*pCondition);
+            auto* vmCondition = m_vConditions.GetItemAt(gsl::narrow_cast<gsl::index>(nIndex));
+            if (vmCondition == nullptr)
+            {
+                vmCondition = &m_vConditions.Add();
+                Ensures(vmCondition != nullptr);
+            }
+
+            vmCondition->SetIndex(++nIndex);
+            vmCondition->InitializeFrom(*pCondition);
         }
     }
 
+    for (gsl::index nScan = m_vConditions.Count() - 1; nScan >= nIndex; --nScan)
+        m_vConditions.RemoveAt(nScan);
+
     m_vConditions.EndUpdate();
     m_vConditions.AddNotifyTarget(m_pConditionsMonitor);
 }
diff --git a/src/ui/viewmodels/TriggerViewModel.hh b/src/ui/viewmodels/TriggerViewModel.hh
index 430e284f..4541f7ec 100644
--- a/src/ui/viewmodels/TriggerViewModel.hh
+++ b/src/ui/viewmodels/TriggerViewModel.hh
@@ -148,6 +148,7 @@ private:
         void OnViewModelIntValueChanged(gsl::index nIndex, const IntModelProperty::ChangeArgs& args) override;
         void OnViewModelAdded(gsl::index nIndex) override;
         void OnViewModelRemoved(gsl::index nIndex) override;
+        void OnEndViewModelCollectionUpdate() override;
 
         void UpdateCurrentGroup();
 


Test patch:
diff --git a/tests/data/DataModelBase_Tests.cpp b/tests/data/DataModelBase_Tests.cpp
index 6b983a8a..ef091cb5 100644
--- a/tests/data/DataModelBase_Tests.cpp
+++ b/tests/data/DataModelBase_Tests.cpp
@@ -48,6 +48,10 @@ TEST_CLASS(DataModelBase_Tests)
         const bool* GetPreviousTransactonalBool() const { return m_pTransaction->GetPreviousValue(TransactionalBoolProperty); }
 
         bool TransactionIsModified() const noexcept { return m_pTransaction->IsModified(); }
+
+        using DataModelBase::BeginUpdate;
+        using DataModelBase::EndUpdate;
+        using DataModelBase::IsUpdating;
     };
 
     class NotifyTargetHarness : public DataModelBase::NotifyTarget
@@ -177,220 +181,220 @@ TEST_CLASS(DataModelBase_Tests)
 public:
     TEST_METHOD(TestTransactionalBoolProperty)
     {
-        DataModelHarness vmViewModel;
-        Assert::IsFalse(vmViewModel.GetBool());
-        Assert::IsFalse(vmViewModel.GetTransactionalBool());
-        vmViewModel.BeginTransaction();
+        DataModelHarness pModel;
+        Assert::IsFalse(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+        pModel.BeginTransaction();
 
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
-
-        vmViewModel.SetBool(true);
-        Assert::IsTrue(vmViewModel.GetBool());
-        Assert::IsFalse(vmViewModel.GetTransactionalBool());
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::BoolProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.SetBool(true);
+        Assert::IsTrue(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::BoolProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertBoolChanged(DataModelHarness::BoolProperty, false, true);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalBool(true);
-        Assert::IsTrue(vmViewModel.GetBool());
-        Assert::IsTrue(vmViewModel.GetTransactionalBool());
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::BoolProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.SetTransactionalBool(true);
+        Assert::IsTrue(pModel.GetBool());
+        Assert::IsTrue(pModel.GetTransactionalBool());
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::BoolProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertBoolChanged(DataModelHarness::TransactionalBoolProperty, false, true);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalBool(true);
-        Assert::IsTrue(vmViewModel.GetBool());
-        Assert::IsTrue(vmViewModel.GetTransactionalBool());
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::BoolProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.SetTransactionalBool(true);
+        Assert::IsTrue(pModel.GetBool());
+        Assert::IsTrue(pModel.GetTransactionalBool());
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::BoolProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalBool(false);
-        Assert::IsTrue(vmViewModel.GetBool());
-        Assert::IsFalse(vmViewModel.GetTransactionalBool());
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::BoolProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.SetTransactionalBool(false);
+        Assert::IsTrue(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::BoolProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertBoolChanged(DataModelHarness::TransactionalBoolProperty, true, false);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
     }
 
     TEST_METHOD(TestTransactionalStringProperty)
     {
-        DataModelHarness vmViewModel;
-        Assert::AreEqual(std::wstring(), vmViewModel.GetString());
-        Assert::AreEqual(std::wstring(), vmViewModel.GetTransactionalString());
-        vmViewModel.BeginTransaction();
+        DataModelHarness pModel;
+        Assert::AreEqual(std::wstring(), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+        pModel.BeginTransaction();
 
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
-
-        vmViewModel.SetString(L"Test");
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetString());
-        Assert::AreEqual(std::wstring(), vmViewModel.GetTransactionalString());
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::StringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.SetString(L"Test");
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::StringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
         oNotify.AssertStringChanged(DataModelHarness::StringProperty, L"", L"Test");
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalString(L"Test");
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetString());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::StringProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        pModel.SetTransactionalString(L"Test");
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetString());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::StringProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"", L"Test");
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalString(L"Test");
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetString());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::StringProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        pModel.SetTransactionalString(L"Test");
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetString());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::StringProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalString(L"");
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetString());
-        Assert::AreEqual(std::wstring(L""), vmViewModel.GetTransactionalString());
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::StringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        pModel.SetTransactionalString(L"");
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetString());
+        Assert::AreEqual(std::wstring(L""), pModel.GetTransactionalString());
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::StringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"Test", L"");
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
     }
 
     TEST_METHOD(TestTransactionalIntProperty)
     {
-        DataModelHarness vmViewModel;
-        Assert::AreEqual(0, vmViewModel.GetInt());
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        vmViewModel.BeginTransaction();
+        DataModelHarness pModel;
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        pModel.BeginTransaction();
 
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
-
-        vmViewModel.SetInt(99);
-        Assert::AreEqual(99, vmViewModel.GetInt());
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::IntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.SetInt(99);
+        Assert::AreEqual(99, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::IntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
         oNotify.AssertIntChanged(DataModelHarness::IntProperty, 0, 99);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalInt(99);
-        Assert::AreEqual(99, vmViewModel.GetInt());
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::IntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        pModel.SetTransactionalInt(99);
+        Assert::AreEqual(99, pModel.GetInt());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::IntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 0, 99);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalInt(99);
-        Assert::AreEqual(99, vmViewModel.GetInt());
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::IntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        pModel.SetTransactionalInt(99);
+        Assert::AreEqual(99, pModel.GetInt());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::IntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalInt(0);
-        Assert::AreEqual(99, vmViewModel.GetInt());
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::IntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        pModel.SetTransactionalInt(0);
+        Assert::AreEqual(99, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::IntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 99, 0);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
     }
 
     TEST_METHOD(TestRevertTransaction)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
-        vmViewModel.BeginTransaction();
-
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
-
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.AddNotifyTarget(oNotify);
+        pModel.BeginTransaction();
+
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.Reset();
 
-        vmViewModel.RevertTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.RevertTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L""), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L""), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // no transaction to revert - modifications should be kept, but not tracked
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
-        vmViewModel.RevertTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
-
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
+        pModel.RevertTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
     }
 
     TEST_METHOD(TestRevertTransactionNested)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
+        pModel.AddNotifyTarget(oNotify);
 
         // create a transaction and modify it
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.BeginTransaction();
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.Reset();
 
         // nested transaction is not initially considered modified
-        vmViewModel.BeginTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.BeginTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -398,16 +402,16 @@ public:
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
         oNotify.Reset();
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // revert nested transaction, will pick up modifications from first transaction
-        vmViewModel.RevertTransaction();
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.RevertTransaction();
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -416,24 +420,24 @@ public:
         oNotify.Reset();
 
         // another nested transacion, this time, we'll modify it
-        vmViewModel.BeginTransaction();
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
-
-        vmViewModel.SetTransactionalInt(50);
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.BeginTransaction();
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
+
+        pModel.SetTransactionalInt(50);
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.Reset();
 
         // revert nested transaction, will pick up modifications from first transaction
-        vmViewModel.RevertTransaction();
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.RevertTransaction();
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 50, 99);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -441,93 +445,93 @@ public:
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // revert primary transaction
-        vmViewModel.RevertTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.RevertTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 99, 0);
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"Test", L"");
         oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
 
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L""), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L""), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
     }
 
     TEST_METHOD(TestRevertTransactionRepeated)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
+        pModel.AddNotifyTarget(oNotify);
 
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
 
-        vmViewModel.BeginTransaction();
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalInt(99);
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
+        pModel.BeginTransaction();
+        pModel.BeginTransaction();
+        pModel.SetTransactionalInt(99);
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
 
-        vmViewModel.RevertTransaction();
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        Assert::IsFalse(vmViewModel.IsModified());
+        pModel.RevertTransaction();
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::IsFalse(pModel.IsModified());
 
-        vmViewModel.RevertTransaction();
-        Assert::AreEqual(0, vmViewModel.GetTransactionalInt());
-        Assert::IsFalse(vmViewModel.IsModified());
+        pModel.RevertTransaction();
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::IsFalse(pModel.IsModified());
     }
 
     TEST_METHOD(TestRevertTransactionMultipleChanges)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
+        pModel.AddNotifyTarget(oNotify);
 
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
-        vmViewModel.BeginTransaction();
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
+        pModel.BeginTransaction();
         oNotify.Reset();
 
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
-
-        vmViewModel.SetTransactionalInt(50);
-        vmViewModel.SetTransactionalString(L"Test2");
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+
+        pModel.SetTransactionalInt(50);
+        pModel.SetTransactionalString(L"Test2");
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"Test", L"Test2");
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 99, 50);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
         oNotify.Reset();
 
-        vmViewModel.SetTransactionalInt(75);
-        vmViewModel.SetTransactionalString(L"Test3");
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.SetTransactionalInt(75);
+        pModel.SetTransactionalString(L"Test3");
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"Test2", L"Test3");
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 50, 75);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
         // revert should return to original state, regardless of how many times values have changed within the transaction
-        vmViewModel.RevertTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.RevertTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"Test3", L"Test");
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 75, 99);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
@@ -535,70 +539,70 @@ public:
 
     TEST_METHOD(TestCommitTransaction)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
-        vmViewModel.BeginTransaction();
-
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
-
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.AddNotifyTarget(oNotify);
+        pModel.BeginTransaction();
+
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.Reset();
 
-        vmViewModel.CommitTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.CommitTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // no transaction to commit - modifications should be kept, but not tracked
-        vmViewModel.SetTransactionalInt(50);
-        vmViewModel.SetTransactionalString(L"Test2");
-        vmViewModel.RevertTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
-
-        Assert::AreEqual(50, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test2"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        pModel.SetTransactionalInt(50);
+        pModel.SetTransactionalString(L"Test2");
+        pModel.RevertTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+
+        Assert::AreEqual(50, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test2"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
     }
 
     TEST_METHOD(TestCommitTransactionNested)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
+        pModel.AddNotifyTarget(oNotify);
 
         // create a transaction and modify it
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.BeginTransaction();
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.Reset();
 
         // nested transaction is not initially considered modified
-        vmViewModel.BeginTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.BeginTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -606,16 +610,16 @@ public:
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
         oNotify.Reset();
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // commit (unmodified) nested transaction, will pick up modifications from first transaction
-        vmViewModel.CommitTransaction();
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.CommitTransaction();
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -624,24 +628,24 @@ public:
         oNotify.Reset();
 
         // another nested transacion, this time, we'll modify it
-        vmViewModel.BeginTransaction();
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
-
-        vmViewModel.SetTransactionalInt(50);
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.BeginTransaction();
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
+
+        pModel.SetTransactionalInt(50);
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
         oNotify.Reset();
 
         // revert nested transaction, will pick up modifications from first transaction
-        vmViewModel.CommitTransaction();
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.CommitTransaction();
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -649,69 +653,69 @@ public:
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
         oNotify.Reset();
 
-        Assert::AreEqual(50, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(50, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // commit primary transaction
-        vmViewModel.CommitTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.CommitTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
 
-        Assert::AreEqual(50, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(50, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
     }
 
     TEST_METHOD(TestCommitTransactionNestedOriginalValues)
     {
-        DataModelHarness vmViewModel;
+        DataModelHarness pModel;
         NotifyTargetHarness oNotify;
-        vmViewModel.AddNotifyTarget(oNotify);
+        pModel.AddNotifyTarget(oNotify);
 
         // set some initial values
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
         oNotify.Reset();
 
         // create a transaction and modify it
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalInt(50);
-        vmViewModel.SetTransactionalString(L"Test2");
+        pModel.BeginTransaction();
+        pModel.SetTransactionalInt(50);
+        pModel.SetTransactionalString(L"Test2");
         oNotify.Reset();
 
         // create another transaction and set the values back to the original values
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalInt(99);
-        vmViewModel.SetTransactionalString(L"Test");
+        pModel.BeginTransaction();
+        pModel.SetTransactionalInt(99);
+        pModel.SetTransactionalString(L"Test");
 
-        Assert::IsTrue(vmViewModel.IsModified());
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsTrue(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        Assert::IsTrue(pModel.IsModified());
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsTrue(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 50, 99);
         oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"Test2", L"Test");
         oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
         oNotify.Reset();
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // commit nested transaction, since the values match the original values, the original transaction will appear unmodified
-        vmViewModel.CommitTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.CommitTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
@@ -719,90 +723,471 @@ public:
         oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, true, false);
         oNotify.Reset();
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
 
         // commiting the now-unmodified original transaction should have no effect
-        vmViewModel.CommitTransaction();
-        Assert::IsFalse(vmViewModel.IsModified());
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalIntProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalStringProperty));
-        Assert::IsFalse(vmViewModel.IsModified(DataModelHarness::TransactionalBoolProperty));
+        pModel.CommitTransaction();
+        Assert::IsFalse(pModel.IsModified());
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalIntProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalStringProperty));
+        Assert::IsFalse(pModel.IsModified(DataModelHarness::TransactionalBoolProperty));
 
         oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
         oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
         oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
 
-        Assert::AreEqual(99, vmViewModel.GetTransactionalInt());
-        Assert::AreEqual(std::wstring(L"Test"), vmViewModel.GetTransactionalString());
-        Assert::AreEqual(false, vmViewModel.GetTransactionalBool());
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        Assert::AreEqual(false, pModel.GetTransactionalBool());
     }
 
     TEST_METHOD(TestPreviousValues)
     {
-        DataModelHarness vmViewModel;
-        vmViewModel.BeginTransaction();
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
 
         // no previous value captured
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalBool());
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalInt());
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalString());
+        Assert::IsNull(pModel.GetPreviousTransactonalBool());
+        Assert::IsNull(pModel.GetPreviousTransactonalInt());
+        Assert::IsNull(pModel.GetPreviousTransactonalString());
 
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalBool(true);
-        vmViewModel.SetTransactionalInt(50);
-        vmViewModel.SetTransactionalString(L"Test2");
+        pModel.BeginTransaction();
+        pModel.SetTransactionalBool(true);
+        pModel.SetTransactionalInt(50);
+        pModel.SetTransactionalString(L"Test2");
 
         // original values captured
-        Assert::AreEqual(false, *vmViewModel.GetPreviousTransactonalBool());
-        Assert::AreEqual(0, *vmViewModel.GetPreviousTransactonalInt());
-        Assert::AreEqual(std::wstring(), *vmViewModel.GetPreviousTransactonalString());
+        Assert::AreEqual(false, *pModel.GetPreviousTransactonalBool());
+        Assert::AreEqual(0, *pModel.GetPreviousTransactonalInt());
+        Assert::AreEqual(std::wstring(), *pModel.GetPreviousTransactonalString());
 
-        vmViewModel.BeginTransaction();
+        pModel.BeginTransaction();
 
         // no previous values captured for this transaction
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalBool());
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalInt());
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalString());
+        Assert::IsNull(pModel.GetPreviousTransactonalBool());
+        Assert::IsNull(pModel.GetPreviousTransactonalInt());
+        Assert::IsNull(pModel.GetPreviousTransactonalString());
 
-        vmViewModel.SetTransactionalBool(false);
-        vmViewModel.SetTransactionalInt(100);
-        vmViewModel.SetTransactionalString(L"Test3");
+        pModel.SetTransactionalBool(false);
+        pModel.SetTransactionalInt(100);
+        pModel.SetTransactionalString(L"Test3");
 
         // previous values captured
-        Assert::AreEqual(true, *vmViewModel.GetPreviousTransactonalBool());
-        Assert::AreEqual(50, *vmViewModel.GetPreviousTransactonalInt());
-        Assert::AreEqual(std::wstring(L"Test2"), *vmViewModel.GetPreviousTransactonalString());
+        Assert::AreEqual(true, *pModel.GetPreviousTransactonalBool());
+        Assert::AreEqual(50, *pModel.GetPreviousTransactonalInt());
+        Assert::AreEqual(std::wstring(L"Test2"), *pModel.GetPreviousTransactonalString());
 
-        vmViewModel.CommitTransaction();
+        pModel.CommitTransaction();
 
         // previous values updated from commit - since the boolean matches the original value
         // it returns null. the other older original values are maintained
-        Assert::IsNull(vmViewModel.GetPreviousTransactonalBool());
-        Assert::AreEqual(0, *vmViewModel.GetPreviousTransactonalInt());
-        Assert::AreEqual(std::wstring(), *vmViewModel.GetPreviousTransactonalString());
+        Assert::IsNull(pModel.GetPreviousTransactonalBool());
+        Assert::AreEqual(0, *pModel.GetPreviousTransactonalInt());
+        Assert::AreEqual(std::wstring(), *pModel.GetPreviousTransactonalString());
     }
 
     TEST_METHOD(TestIsModified)
     {
-        DataModelHarness vmViewModel;
-        vmViewModel.BeginTransaction();
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+
+        Assert::IsFalse(pModel.TransactionIsModified());
+
+        pModel.BeginTransaction();
+        pModel.SetTransactionalBool(true);
+        pModel.SetTransactionalInt(50);
+        pModel.SetTransactionalString(L"Test2");
+        Assert::IsTrue(pModel.TransactionIsModified());
+
+        pModel.BeginTransaction();
+        Assert::IsFalse(pModel.TransactionIsModified());
+
+        pModel.SetTransactionalBool(false);
+        Assert::IsTrue(pModel.TransactionIsModified());
+    }
+
+    TEST_METHOD(TestBeginUpdate)
+    {
+        DataModelHarness pModel;
+        Assert::IsFalse(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+        pModel.BeginTransaction();
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        Assert::IsFalse(pModel.IsUpdating());
+        pModel.BeginUpdate();
+        Assert::IsTrue(pModel.IsUpdating());
+
+        pModel.SetBool(true);
+        Assert::IsTrue(pModel.GetBool());
+        Assert::IsFalse(pModel.IsModified());
+        oNotify.AssertNotChanged(DataModelHarness::BoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        pModel.SetTransactionalBool(true);
+        Assert::IsTrue(pModel.GetTransactionalBool());
+        Assert::IsTrue(pModel.IsModified());
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty); // this change also queued
+
+        pModel.SetString(L"Test");
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetString());
+        pModel.SetTransactionalString(L"Test");
+        Assert::AreEqual(std::wstring(L"Test"), pModel.GetTransactionalString());
+        oNotify.AssertNotChanged(DataModelHarness::StringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
+
+        pModel.SetInt(99);
+        Assert::AreEqual(99, pModel.GetInt());
+        pModel.SetTransactionalInt(99);
+        Assert::AreEqual(99, pModel.GetTransactionalInt());
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+
+        oNotify.Reset();
+        Assert::IsTrue(pModel.IsUpdating());
+        pModel.EndUpdate();
+        Assert::IsFalse(pModel.IsUpdating());
+
+        oNotify.AssertBoolChanged(DataModelHarness::BoolProperty, false, true);
+        oNotify.AssertBoolChanged(DataModelHarness::TransactionalBoolProperty, false, true);
+        oNotify.AssertIntChanged(DataModelHarness::IntProperty, 0, 99);
+        oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 0, 99);
+        oNotify.AssertStringChanged(DataModelHarness::StringProperty, L"", L"Test");
+        oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"", L"Test");
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
+    }
+
+    TEST_METHOD(TestBeginUpdateNested)
+    {
+        DataModelHarness pModel;
+        Assert::IsFalse(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        Assert::AreEqual(std::wstring(), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+        pModel.BeginTransaction();
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        Assert::IsFalse(pModel.IsUpdating());
+        pModel.BeginUpdate();
+        Assert::IsTrue(pModel.IsUpdating());
+        pModel.BeginUpdate();
+        Assert::IsTrue(pModel.IsUpdating());
+
+        pModel.SetBool(true);
+        pModel.SetTransactionalBool(true);
+        pModel.SetString(L"Test");
+        pModel.SetTransactionalString(L"Test");
+        pModel.SetInt(99);
+        pModel.SetTransactionalInt(99);
+
+        oNotify.AssertNotChanged(DataModelHarness::BoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::StringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        oNotify.Reset();
+        Assert::IsTrue(pModel.IsUpdating());
+        pModel.EndUpdate();
+        Assert::IsTrue(pModel.IsUpdating());
+
+        oNotify.AssertNotChanged(DataModelHarness::BoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::StringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        oNotify.Reset();
+        Assert::IsTrue(pModel.IsUpdating());
+        pModel.EndUpdate();
+        Assert::IsFalse(pModel.IsUpdating());
+
+        oNotify.AssertBoolChanged(DataModelHarness::BoolProperty, false, true);
+        oNotify.AssertBoolChanged(DataModelHarness::TransactionalBoolProperty, false, true);
+        oNotify.AssertIntChanged(DataModelHarness::IntProperty, 0, 99);
+        oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 0, 99);
+        oNotify.AssertStringChanged(DataModelHarness::StringProperty, L"", L"Test");
+        oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"", L"Test");
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
+    }
+
+
+    TEST_METHOD(TestBeginUpdateNestedChangesBetweenBeginUpdatesInt)
+    {
+        DataModelHarness pModel;
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        pModel.BeginTransaction();
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+
+        pModel.SetInt(66);
+        pModel.SetTransactionalInt(66);
+
+        pModel.BeginUpdate();
+
+        pModel.SetInt(99);
+        pModel.SetTransactionalInt(99);
+
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        oNotify.Reset();
+        pModel.EndUpdate();
+
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        oNotify.Reset();
+        pModel.EndUpdate();
+
+        oNotify.AssertIntChanged(DataModelHarness::IntProperty, 0, 99);
+        oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 0, 99);
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
+    }
+
+    TEST_METHOD(TestBeginUpdateNestedChangesBetweenEndUpdatesInt)
+    {
+        DataModelHarness pModel;
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+        pModel.BeginTransaction();
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+        pModel.BeginUpdate();
+
+        pModel.SetInt(66);
+        pModel.SetTransactionalInt(66);
+
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        oNotify.Reset();
+        pModel.EndUpdate();
+
+        pModel.SetInt(99);
+        pModel.SetTransactionalInt(99);
+
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        oNotify.Reset();
+        pModel.EndUpdate();
+
+        oNotify.AssertIntChanged(DataModelHarness::IntProperty, 0, 99);
+        oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 0, 99);
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
+    }
+
+    TEST_METHOD(TestBeginUpdateChangeAndChangeBackBool)
+    {
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+        Assert::IsFalse(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+
+        pModel.SetBool(true);
+        pModel.SetTransactionalBool(true);
+
+        pModel.SetBool(false);
+        pModel.SetTransactionalBool(false);
+
+        pModel.EndUpdate();
+
+        oNotify.AssertNotChanged(DataModelHarness::BoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalBoolProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        Assert::IsFalse(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+    }
+
+    TEST_METHOD(TestBeginUpdateChangeAndChangeBackInt)
+    {
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+
+        pModel.SetInt(5);
+        pModel.SetTransactionalInt(5);
+
+        pModel.SetInt(99);
+        pModel.SetTransactionalInt(99);
+
+        pModel.SetInt(0);
+        pModel.SetTransactionalInt(0);
+
+        pModel.EndUpdate();
+
+        oNotify.AssertNotChanged(DataModelHarness::IntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalIntProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+    }
+
+    TEST_METHOD(TestBeginUpdateChangeAndChangeBackString)
+    {
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+        Assert::AreEqual(std::wstring(), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
 
-        Assert::IsFalse(vmViewModel.TransactionIsModified());
+        pModel.BeginUpdate();
 
-        vmViewModel.BeginTransaction();
-        vmViewModel.SetTransactionalBool(true);
-        vmViewModel.SetTransactionalInt(50);
-        vmViewModel.SetTransactionalString(L"Test2");
-        Assert::IsTrue(vmViewModel.TransactionIsModified());
+        pModel.SetString(L"Test");
+        pModel.SetTransactionalString(L"Test");
 
-        vmViewModel.BeginTransaction();
-        Assert::IsFalse(vmViewModel.TransactionIsModified());
+        pModel.SetString(L"Test2");
+        pModel.SetTransactionalString(L"Test2");
+
+        pModel.SetString(L"");
+        pModel.SetTransactionalString(L"");
+
+        pModel.EndUpdate();
+
+        oNotify.AssertNotChanged(DataModelHarness::StringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::TransactionalStringProperty);
+        oNotify.AssertNotChanged(DataModelHarness::IsModifiedProperty);
+
+        Assert::AreEqual(std::wstring(), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+    }
+
+    TEST_METHOD(TestBeginUpdateMultipleChangesBool)
+    {
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+        Assert::IsFalse(pModel.GetBool());
+        Assert::IsFalse(pModel.GetTransactionalBool());
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+
+        pModel.SetBool(true);
+        pModel.SetTransactionalBool(true);
+
+        pModel.SetBool(false);
+        pModel.SetTransactionalBool(false);
+
+        pModel.SetBool(true);
+        pModel.SetTransactionalBool(true);
+
+        pModel.EndUpdate();
+
+        oNotify.AssertBoolChanged(DataModelHarness::BoolProperty, false, true);
+        oNotify.AssertBoolChanged(DataModelHarness::TransactionalBoolProperty, false, true);
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
+
+        Assert::IsTrue(pModel.GetBool());
+        Assert::IsTrue(pModel.GetTransactionalBool());
+    }
+
+    TEST_METHOD(TestBeginUpdateMultipleChangesInt)
+    {
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+        Assert::AreEqual(0, pModel.GetInt());
+        Assert::AreEqual(0, pModel.GetTransactionalInt());
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+
+        pModel.SetInt(5);
+        pModel.SetTransactionalInt(5);
+
+        pModel.SetInt(99);
+        pModel.SetTransactionalInt(99);
+
+        pModel.SetInt(67);
+        pModel.SetTransactionalInt(68);
+
+        pModel.EndUpdate();
+
+        oNotify.AssertIntChanged(DataModelHarness::IntProperty, 0, 67);
+        oNotify.AssertIntChanged(DataModelHarness::TransactionalIntProperty, 0, 68);
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
+
+        Assert::AreEqual(67, pModel.GetInt());
+        Assert::AreEqual(68, pModel.GetTransactionalInt());
+    }
+
+    TEST_METHOD(TestBeginUpdateMultipleChangesString)
+    {
+        DataModelHarness pModel;
+        pModel.BeginTransaction();
+        Assert::AreEqual(std::wstring(), pModel.GetString());
+        Assert::AreEqual(std::wstring(), pModel.GetTransactionalString());
+
+        NotifyTargetHarness oNotify;
+        pModel.AddNotifyTarget(oNotify);
+
+        pModel.BeginUpdate();
+
+        pModel.SetString(L"Test");
+        pModel.SetTransactionalString(L"Test");
+
+        pModel.SetString(L"Test2");
+        pModel.SetTransactionalString(L"Test2");
+
+        pModel.SetString(L"Test3a");
+        pModel.SetTransactionalString(L"Test3b");
+
+        pModel.EndUpdate();
+
+        oNotify.AssertStringChanged(DataModelHarness::StringProperty, L"", L"Test3a");
+        oNotify.AssertStringChanged(DataModelHarness::TransactionalStringProperty, L"", L"Test3b");
+        oNotify.AssertBoolChanged(DataModelHarness::IsModifiedProperty, false, true);
 
-        vmViewModel.SetTransactionalBool(false);
-        Assert::IsTrue(vmViewModel.TransactionIsModified());
+        Assert::AreEqual(std::wstring(L"Test3a"), pModel.GetString());
+        Assert::AreEqual(std::wstring(L"Test3b"), pModel.GetTransactionalString());
     }
 };
 
diff --git a/tests/data/models/AchievementModel_Tests.cpp b/tests/data/models/AchievementModel_Tests.cpp
index 4b5ff98a..13db9eae 100644
--- a/tests/data/models/AchievementModel_Tests.cpp
+++ b/tests/data/models/AchievementModel_Tests.cpp
@@ -68,6 +68,8 @@ public:
         AchievementModelHarness achievement;
         achievement.SetID(1U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         Assert::AreEqual(AssetState::Inactive, achievement.GetState());
         Assert::IsNull(achievement.mockRuntime.GetAchievementTrigger(1U));
@@ -91,6 +93,8 @@ public:
         AchievementModelHarness achievement;
         achievement.SetID(1U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
         achievement.Activate();
 
         auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
@@ -117,6 +121,8 @@ public:
         AchievementModelHarness achievement;
         achievement.SetID(1U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
         achievement.Activate();
 
         auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
@@ -144,6 +150,8 @@ public:
         AchievementModelHarness achievement;
         achievement.SetID(1U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
         achievement.Activate();
 
         auto* pTrigger = achievement.mockRuntime.GetAchievementTrigger(1U);
diff --git a/tests/data/models/AssetModelBase_Tests.cpp b/tests/data/models/AssetModelBase_Tests.cpp
index 2f9ab4d1..62d3de85 100644
--- a/tests/data/models/AssetModelBase_Tests.cpp
+++ b/tests/data/models/AssetModelBase_Tests.cpp
@@ -13,13 +13,13 @@ namespace data {
 namespace models {
 namespace tests {
 
-TEST_CLASS(AssetViewModelBase_Tests)
+TEST_CLASS(AssetModelBase_Tests)
 {
 private:
-    class AssetViewModelHarness : public AssetModelBase
+    class AssetModelHarness : public AssetModelBase
     {
     public:
-        AssetViewModelHarness() noexcept
+        AssetModelHarness() noexcept
         {
             SetTransactional(StringProperty);
             SetTransactional(IntProperty);
@@ -96,7 +96,7 @@ private:
 public:
     TEST_METHOD(TestInitialValues)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         Assert::AreEqual(AssetType::Achievement, asset.GetType());
         Assert::AreEqual(0U, asset.GetID());
@@ -109,7 +109,7 @@ public:
 
     TEST_METHOD(TestIsActive)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         asset.SetState(AssetState::Active);
         Assert::IsTrue(asset.IsActive());
@@ -129,7 +129,7 @@ public:
 
     TEST_METHOD(TestSerializeDefaults)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         asset.Serialize(asset.textWriter);
         Assert::AreEqual(std::string(":\"\"::0:\"\":"), asset.textWriter.GetString());
@@ -137,7 +137,7 @@ public:
 
     TEST_METHOD(TestSerializeSimpleValues)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         asset.SetName(L"Name");
         asset.SetString(L"String");
@@ -151,7 +151,7 @@ public:
 
     TEST_METHOD(TestSerializeColon)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         asset.SetName(L"Mission: Impossible");
         asset.SetString(L"Mission: Impossible");
@@ -164,7 +164,7 @@ public:
 
     TEST_METHOD(TestSerializeQuotes)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         asset.SetName(L"A \"Test\" B");
         asset.SetString(L"A \"Test\" B");
@@ -177,7 +177,7 @@ public:
 
     TEST_METHOD(TestSerializeBackslash)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         asset.SetName(L"A\\B");
         asset.SetString(L"A\\B");
@@ -190,7 +190,7 @@ public:
 
     TEST_METHOD(TestDeserializeSimpleValues)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"Name\":String:99:\"UTF-8-Quoted\":UTF-8";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -206,7 +206,7 @@ public:
 
     TEST_METHOD(TestDeserializeColon)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"Mission: Impossible\":\"Mission: Impossible\":0:\"Mission: Impossible\":\"Mission: Impossible\"";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -222,7 +222,7 @@ public:
 
     TEST_METHOD(TestDeserializeQuotes)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"A \\\"Test\\\" B\":\"A \\\"Test\\\" B\":0:\"A \\\"Test\\\" B\":\"A \\\"Test\\\" B\"";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -238,7 +238,7 @@ public:
 
     TEST_METHOD(TestDeserializeBackslash)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"A\\\\B\":\"A\\\\B\":0:\"A\\\\B\":\"A\\\\B\"";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -254,7 +254,7 @@ public:
 
     TEST_METHOD(TestDeserializeInvalidNumber)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"A\":B:C:\"D\":E";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -270,7 +270,7 @@ public:
 
     TEST_METHOD(TestDeserializeMissingField)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"A\":B:5:\"D\"";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -286,7 +286,7 @@ public:
 
     TEST_METHOD(TestDeserializeNotQuoted)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":A:B:5:D:E";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -302,7 +302,7 @@ public:
 
     TEST_METHOD(TestDeserializeBadlyQuoted)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
 
         std::string sSerialized = ":\"A\":\"B\"C:5:\"D\":E";
         ra::Tokenizer pTokenizer(sSerialized);
@@ -318,7 +318,7 @@ public:
 
     TEST_METHOD(TestUpdateCheckpoint)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
         asset.SetName(L"ServerName");
         asset.SetCategory(AssetCategory::Core);
         asset.CreateServerCheckpoint();
@@ -340,7 +340,7 @@ public:
 
     TEST_METHOD(TestRestoreCheckpoint)
     {
-        AssetViewModelHarness asset;
+        AssetModelHarness asset;
         asset.SetName(L"ServerName");
         asset.SetCategory(AssetCategory::Core);
         asset.CreateServerCheckpoint();
@@ -414,11 +414,29 @@ public:
         Assert::AreEqual(AssetChanges::None, asset.GetChanges());
         Assert::AreEqual(std::string("ServerDefinition"), asset.GetDefinition());
     }
+
+    TEST_METHOD(TestResetLocalCheckpoint)
+    {
+        AssetModelHarness asset;
+        asset.SetName(L"ServerName");
+        asset.CreateServerCheckpoint();
+        asset.SetName(L"LocalName");
+        asset.CreateLocalCheckpoint();
+        asset.SetName(L"MemoryName");
+
+        std::string sSerialized = ":\"Name\":String:99:\"UTF-8-Quoted\":UTF-8";
+        ra::Tokenizer pTokenizer(sSerialized);
+        pTokenizer.Advance(); // skip leading colon
+        asset.ResetLocalCheckpoint(pTokenizer);
+
+        Assert::AreEqual(std::wstring(L"Name"), asset.GetName());
+        Assert::AreEqual(AssetChanges::Unpublished, asset.GetChanges());
+    }
 };
 
-const StringModelProperty AssetViewModelBase_Tests::AssetViewModelHarness::StringProperty("AssetViewModelHarness", "String", L"");
-const IntModelProperty AssetViewModelBase_Tests::AssetViewModelHarness::IntProperty("AssetViewModelHarness", "Int", 0);
-const IntModelProperty AssetViewModelBase_Tests::AssetDefinitionViewModelHarness::DefinitionProperty("AssetDefinitionViewModelHarness", "Int", ra::etoi(AssetChanges::None));
+const StringModelProperty AssetModelBase_Tests::AssetModelHarness::StringProperty("AssetModelHarness", "String", L"");
+const IntModelProperty AssetModelBase_Tests::AssetModelHarness::IntProperty("AssetModelHarness", "Int", 0);
+const IntModelProperty AssetModelBase_Tests::AssetDefinitionViewModelHarness::DefinitionProperty("AssetDefinitionViewModelHarness", "Int", ra::etoi(AssetChanges::None));
 
 } // namespace tests
 } // namespace models
diff --git a/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp b/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp
index e7ebeb8e..f25ecc63 100644
--- a/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp
@@ -76,6 +76,8 @@ public:
         achievement.SetCategory(AssetCategory::Unofficial);
         achievement.SetPoints(10);
         achievement.SetBadge(L"58329");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
 
@@ -111,6 +113,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetID(1234U);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(1234U, editor.GetID());
@@ -127,6 +131,8 @@ public:
         AchievementModel achievement;
         achievement.SetCategory(AssetCategory::Local);
         achievement.SetID(11100002U);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(0U, editor.GetID());
@@ -142,6 +148,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetName(L"Test Achievement");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(std::wstring(L"Test Achievement"), editor.GetName());
@@ -165,6 +173,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetDescription(L"Do something cool");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(std::wstring(L"Do something cool"), editor.GetDescription());
@@ -188,6 +198,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetState(AssetState::Active);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(AssetState::Active, editor.GetState());
@@ -211,6 +223,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetCategory(AssetCategory::Unofficial);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(AssetCategory::Unofficial, editor.GetCategory());
@@ -234,6 +248,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetPoints(10);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(10, editor.GetPoints());
@@ -257,6 +273,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetBadge(L"58329");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::AreEqual(std::wstring(L"58329"), editor.GetBadge());
@@ -280,6 +298,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetPauseOnReset(true);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::IsTrue(editor.IsPauseOnReset());
@@ -303,6 +323,8 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetPauseOnTrigger(true);
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement);
         Assert::IsTrue(editor.IsPauseOnTrigger());
@@ -326,8 +348,12 @@ public:
         AssetEditorViewModelHarness editor;
         AchievementModel achievement1;
         achievement1.SetName(L"Achievement1");
+        achievement1.CreateServerCheckpoint();
+        achievement1.CreateLocalCheckpoint();
         AchievementModel achievement2;
         achievement2.SetName(L"Achievement2");
+        achievement2.CreateServerCheckpoint();
+        achievement2.CreateLocalCheckpoint();
 
         editor.LoadAsset(&achievement1);
         Assert::AreEqual(std::wstring(L"Achievement1"), editor.GetName());
@@ -361,6 +387,8 @@ public:
         AchievementModel achievement;
         achievement.SetID(1234U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
         achievement.Activate();
 
         editor.LoadAsset(&achievement);
@@ -410,6 +438,8 @@ public:
         AchievementModel achievement;
         achievement.SetID(1234U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
         achievement.Deactivate();
 
         editor.LoadAsset(&achievement);
@@ -451,12 +481,65 @@ public:
         Assert::AreEqual(2U, pCondition->GetTargetValue());
     }
 
+    TEST_METHOD(TestTriggerUpdated)
+    {
+        AssetEditorViewModelHarness editor;
+        AchievementModel achievement;
+        achievement.SetID(1234U);
+        achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
+        achievement.Activate();
+
+        editor.LoadAsset(&achievement);
+
+        // make sure the record got loaded into the runtime
+        const auto* pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
+        Expects(pTrigger != nullptr);
+        Assert::AreEqual(1U, pTrigger->requirement->conditions->operand2.value.num);
+
+        Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
+        auto* pCondition = editor.Trigger().Conditions().GetItemAt(0);
+        Expects(pCondition != nullptr);
+        Assert::AreEqual(MemSize::EightBit, pCondition->GetSourceSize());
+        Assert::AreEqual((int)TriggerOperandType::Address, (int)pCondition->GetSourceType());
+        Assert::AreEqual(0x1234U, pCondition->GetSourceValue());
+        Assert::AreEqual((int)TriggerOperatorType::Equals, (int)pCondition->GetOperator());
+        Assert::AreEqual(MemSize::ThirtyTwoBit, pCondition->GetTargetSize());
+        Assert::AreEqual((int)TriggerOperandType::Value, (int)pCondition->GetTargetType());
+        Assert::AreEqual(1U, pCondition->GetTargetValue());
+
+        achievement.SetTrigger("0xH1234=2");
+
+        // make sure the trigger definition got updated
+        Assert::AreEqual(std::string("0xH1234=2"), achievement.GetTrigger());
+
+        // make sure the runtime record got updated
+        pTrigger = editor.mockRuntime.GetAchievementTrigger(1234U);
+        Expects(pTrigger != nullptr);
+        Assert::AreEqual(2U, pTrigger->requirement->conditions->operand2.value.num);
+
+        // make sure the UI picked up the updated value
+        Assert::AreEqual({ 1U }, editor.Trigger().Conditions().Count());
+        pCondition = editor.Trigger().Conditions().GetItemAt(0);
+        Expects(pCondition != nullptr);
+        Assert::AreEqual(MemSize::EightBit, pCondition->GetSourceSize());
+        Assert::AreEqual((int)TriggerOperandType::Address, (int)pCondition->GetSourceType());
+        Assert::AreEqual(0x1234U, pCondition->GetSourceValue());
+        Assert::AreEqual((int)TriggerOperatorType::Equals, (int)pCondition->GetOperator());
+        Assert::AreEqual(MemSize::ThirtyTwoBit, pCondition->GetTargetSize());
+        Assert::AreEqual((int)TriggerOperandType::Value, (int)pCondition->GetTargetType());
+        Assert::AreEqual(2U, pCondition->GetTargetValue());
+    }
+
     TEST_METHOD(TestDoFrameUpdatesHitsFromActiveAchievement)
     {
         AssetEditorViewModelHarness editor;
         AchievementModel achievement;
         achievement.SetID(1234U);
         achievement.SetTrigger("0xH1234=1");
+        achievement.CreateServerCheckpoint();
+        achievement.CreateLocalCheckpoint();
         achievement.Activate();
 
         editor.LoadAsset(&achievement);