SET ANSI_NULLS ON; GO SET QUOTED_IDENTIFIER ON; GO -- Read-only composition inputs. The canonical view supplies the project-wide boundary. CREATE OR ALTER PROCEDURE dbo.StoryPoint_GetHistory @ProjectID int, @SceneID int AS BEGIN SET NOCOUNT ON; DECLARE @MaxIndex bigint = (SELECT GlobalSceneIndex FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND SceneID = @SceneID); IF @MaxIndex IS NULL THROW 50001, 'Scene does not belong to this project narrative.', 1; SELECT SceneID INTO #Scope FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND GlobalSceneIndex <= @MaxIndex; CREATE UNIQUE CLUSTERED INDEX IX_Scope ON #Scope(SceneID); SELECT ck.*, ch.CharacterName, ks.StateName AS KnowledgeStateName, sa.AssetName, pt.ThreadTitle FROM dbo.CharacterKnowledge ck INNER JOIN #Scope n ON n.SceneID = ck.SceneID INNER JOIN dbo.Characters ch ON ch.CharacterID = ck.CharacterID AND ch.ProjectID = @ProjectID AND ch.IsArchived = 0 INNER JOIN dbo.KnowledgeStates ks ON ks.KnowledgeStateID = ck.KnowledgeStateID LEFT JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ck.StoryAssetID AND sa.ProjectID = @ProjectID LEFT JOIN dbo.PlotThreads pt ON pt.PlotThreadID = ck.PlotThreadID; SELECT sal.*, sa.AssetName, lp.LocationName, lp.LocationPath FROM dbo.SceneAssetLocations sal INNER JOIN #Scope n ON n.SceneID = sal.SceneID INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0 INNER JOIN dbo.LocationPaths lp ON lp.LocationID = sal.LocationID; SELECT ace.*, sa.AssetName, acet.TypeName AS CustodyEventTypeName, holders.CustodianSummary FROM dbo.AssetCustodyEvents ace INNER JOIN #Scope n ON n.SceneID = ace.SceneID INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0 INNER JOIN dbo.AssetCustodyEventTypes acet ON acet.AssetCustodyEventTypeID = ace.AssetCustodyEventTypeID OUTER APPLY (SELECT STRING_AGG(CONCAT(COALESCE(ch.CharacterName, aec.CharacterNameText), N' (', cr.RoleName, N')'), N', ') AS CustodianSummary FROM dbo.AssetCustodyEventCharacters aec LEFT JOIN dbo.Characters ch ON ch.CharacterID = aec.CharacterID LEFT JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = aec.CustodyRoleID WHERE aec.AssetCustodyEventID = ace.AssetCustodyEventID) holders; SELECT ace.AssetCustodyEventID, ace.StoryAssetID, ace.SceneID, aec.CharacterID, ch.CharacterName, aec.CharacterNameText, cr.RoleName, ace.UpdatedDate FROM dbo.AssetCustodyEvents ace INNER JOIN #Scope n ON n.SceneID = ace.SceneID INNER JOIN dbo.AssetCustodyEventCharacters aec ON aec.AssetCustodyEventID = ace.AssetCustodyEventID LEFT JOIN dbo.Characters ch ON ch.CharacterID = aec.CharacterID LEFT JOIN dbo.CustodyRoles cr ON cr.CustodyRoleID = aec.CustodyRoleID; SELECT pt.*, pl.ProjectID, pl.PlotLineName, tt.TypeName AS ThreadTypeName FROM dbo.PlotThreads pt INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0 AND pt.IsArchived = 0 AND (EXISTS (SELECT 1 FROM #Scope WHERE SceneID = pt.IntroducedSceneID) OR (pt.IntroducedSceneID IS NULL AND EXISTS (SELECT 1 FROM dbo.ThreadEvents te INNER JOIN #Scope n ON n.SceneID = te.SceneID WHERE te.PlotThreadID = pt.PlotThreadID))); SELECT te.*, tet.TypeName AS EventTypeName FROM dbo.ThreadEvents te INNER JOIN #Scope n ON n.SceneID = te.SceneID INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0 INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0 INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID; END; GO