From c9a82347453bb264a56c68ae5aaba1943fd73f51 Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Thu, 30 Jul 2026 19:18:32 +0000 Subject: [PATCH] Fix road-family location image demand rules --- PlotLine.Tests/Program.cs | 3 ++ ...ryIntelligenceIllustrationCompatibility.cs | 35 ++++++++++--------- ...IntelligenceIllustrationMatchingService.cs | 16 ++++++--- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/PlotLine.Tests/Program.cs b/PlotLine.Tests/Program.cs index 9e8589f..d708746 100644 --- a/PlotLine.Tests/Program.cs +++ b/PlotLine.Tests/Program.cs @@ -1135,6 +1135,7 @@ static void Phase21UEnforcesAliasAgeSemanticAndUiResetRules() var adminDiagnostics = File.ReadAllText(Path.Combine(root, "Views/Admin/StoryIntelligenceDiagnostics.cshtml")); var program = File.ReadAllText(Path.Combine(root, "Program.cs")); var storyMemory = File.ReadAllText(Path.Combine(root, "Services/StoryMemoryServices.cs")); + var matchingService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceIllustrationMatchingService.cs")); var snapshot = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceVisualisationSnapshotService.cs")); var developmentReset = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceDevelopmentResetService.cs")); var persistedRunner = File.ReadAllText(Path.Combine(root, "Services/PersistedStoryIntelligenceRunner.cs")); @@ -1165,6 +1166,8 @@ static void Phase21UEnforcesAliasAgeSemanticAndUiResetRules() Assert(!storyMemory.Contains("or IllustrationLibraryStatuses.Generated\r\n or IllustrationLibraryStatuses.Approved", StringComparison.Ordinal), "Generated or approved preserved images must not permanently fill the demand queue cap."); Assert(storyMemory.Contains("\"Flyover\"", StringComparison.Ordinal) && storyMemory.Contains("concrete overpass", StringComparison.Ordinal), "Location prompts must distinguish road-under-flyover scenes from generic roads."); Assert(storyMemory.Contains("semanticType is \"Road\" or \"Lane\" or \"Street\"", StringComparison.Ordinal), "Road-family locations must be treated as exterior scenes."); + Assert(matchingService.Contains("type is \"Road\" or \"Lane\" or \"Street\"", StringComparison.Ordinal), "Live location demand generation must treat lane/street/road as exterior transport scenes."); + Assert(matchingService.Contains("no indoor corridor", StringComparison.Ordinal), "Road-family location prompts must explicitly reject indoor corridor imagery."); Assert(storyMemory.Contains("evidence.HairColour", StringComparison.Ordinal), "Character demand keys/specs must include durable hair colour evidence."); Assert(storyMemory.Contains("Maximum one primary POV", StringComparison.Ordinal) || storyMemory.Contains("ResolveScenePov", StringComparison.Ordinal), "Story Memory must use a single POV resolver."); Assert(!snapshot.Contains(".Where(character => !sceneCharacterNames.Contains(character.Name))", StringComparison.Ordinal), "Visualisation must not fill scene character slots from memory-only characters."); diff --git a/PlotLine/Services/StoryIntelligenceIllustrationCompatibility.cs b/PlotLine/Services/StoryIntelligenceIllustrationCompatibility.cs index b0ae425..dac1bdf 100644 --- a/PlotLine/Services/StoryIntelligenceIllustrationCompatibility.cs +++ b/PlotLine/Services/StoryIntelligenceIllustrationCompatibility.cs @@ -274,6 +274,24 @@ public static class StoryIntelligenceIllustrationCompatibility { var text = (value ?? string.Empty).ToLowerInvariant(); var padded = $" {text} "; + if (ContainsAny(text, "dual carriageway")) return "DualCarriageway"; + if (ContainsAny(text, "motorway", "highway")) return "Motorway"; + if (ContainsAny(text, "flyover")) return "Flyover"; + if (ContainsAny(text, "underpass")) return "Underpass"; + if (ContainsAny(text, " lane ") || text.EndsWith(" lane", StringComparison.OrdinalIgnoreCase)) return "Lane"; + if (ContainsAny(text, " street ") || text.EndsWith(" street", StringComparison.OrdinalIgnoreCase)) return "Street"; + if (ContainsAny(text, " avenue ") || text.EndsWith(" avenue", StringComparison.OrdinalIgnoreCase)) return "Avenue"; + if (ContainsAny(text, " drive ") || text.EndsWith(" drive", StringComparison.OrdinalIgnoreCase)) return "Drive"; + if (ContainsAny(text, " close ") || text.EndsWith(" close", StringComparison.OrdinalIgnoreCase)) return "Close"; + if (ContainsAny(text, + "road", "court", "way", "terrace", "gardens", + "roundabout", "junction", "crossroads", + "track", "footpath", " path ", "cycleway", "boulevard") + || RoadSuffixRegex.IsMatch(padded)) + { + return "Road"; + } + if (ContainsAny(text, "driving test centre", "driving test center", "test centre", "test center")) return "DrivingTestCentre"; if (ContainsAny(text, "police station")) return "PoliceStation"; if (ContainsAny(text, "driver seat", "driver's seat")) return "DriverSeat"; @@ -301,25 +319,8 @@ public static class StoryIntelligenceIllustrationCompatibility if (text.Contains("school", StringComparison.OrdinalIgnoreCase)) return "School"; if (text.Contains("waiting room", StringComparison.OrdinalIgnoreCase)) return "WaitingRoom"; if (ContainsAny(text, "car park", "parking", "forecourt")) return "CarPark"; - if (ContainsAny(text, "dual carriageway")) return "DualCarriageway"; - if (ContainsAny(text, "motorway", "highway")) return "Motorway"; if (ContainsAny(text, "bridge")) return "Bridge"; if (ContainsAny(text, "tunnel")) return "Tunnel"; - if (ContainsAny(text, "flyover")) return "Flyover"; - if (ContainsAny(text, "underpass")) return "Underpass"; - if (ContainsAny(text, " lane ") || text.EndsWith(" lane", StringComparison.OrdinalIgnoreCase)) return "Lane"; - if (ContainsAny(text, " street ") || text.EndsWith(" street", StringComparison.OrdinalIgnoreCase)) return "Street"; - if (ContainsAny(text, " avenue ") || text.EndsWith(" avenue", StringComparison.OrdinalIgnoreCase)) return "Avenue"; - if (ContainsAny(text, " drive ") || text.EndsWith(" drive", StringComparison.OrdinalIgnoreCase)) return "Drive"; - if (ContainsAny(text, " close ") || text.EndsWith(" close", StringComparison.OrdinalIgnoreCase)) return "Close"; - if (ContainsAny(text, - "road", "court", "way", "terrace", "gardens", - "roundabout", "junction", "crossroads", - "track", "footpath", " path ", "cycleway", "boulevard") - || RoadSuffixRegex.IsMatch(padded)) - { - return "Road"; - } if (text.Contains("shop", StringComparison.OrdinalIgnoreCase)) return "Shop"; if (ContainsAny(text, "bench", "bus stop")) return "StreetFurniture"; if (ContainsAny(text, "flat", "apartment", "room")) return "FlatInterior"; diff --git a/PlotLine/Services/StoryIntelligenceIllustrationMatchingService.cs b/PlotLine/Services/StoryIntelligenceIllustrationMatchingService.cs index 4879261..81d0662 100644 --- a/PlotLine/Services/StoryIntelligenceIllustrationMatchingService.cs +++ b/PlotLine/Services/StoryIntelligenceIllustrationMatchingService.cs @@ -618,18 +618,25 @@ public sealed class StoryIntelligenceIllustrationMatchingService( } private static IllustrationGenerationSpecification LocationDemandSpecification(string code, string demandKey, string type) - => new() + { + var exteriorTransport = type is "Road" or "Lane" or "Street" or "Avenue" or "Drive" or "Close" or "DualCarriageway" or "Motorway" or "Bridge" or "Flyover" or "Underpass"; + var readableType = ReadableToken(type).ToLowerInvariant(); + return new() { Category = IllustrationLibraryCategories.Location, Code = code, Title = $"{ReadableToken(type)} location", Description = "Demand-created reusable location illustration for Story Intelligence matching.", - VisualSubject = $"A clear reusable {ReadableToken(type).ToLowerInvariant()} environment suitable for manuscript visualisation.", - Composition = "single readable environment, no prominent people, strong central focal point, suitable for circular and wide crops", + VisualSubject = exteriorTransport + ? $"A clear reusable exterior {readableType} road environment suitable for manuscript visualisation, with visible road surface, kerbs or verges, exterior daylight or street lighting, and no indoor corridor, hallway, doors-lined passage or building interior." + : $"A clear reusable {readableType} environment suitable for manuscript visualisation.", + Composition = exteriorTransport + ? "single readable exterior transport environment, road type obvious at thumbnail size, no prominent people, no readable text, not an indoor corridor" + : "single readable environment, no prominent people, strong central focal point, suitable for circular and wide crops", Mood = "story-ready, atmospheric but clear, visually specific rather than generic", LocationType = type, Era = "Contemporary or late twentieth century", - Setting = type is "Road" ? "Exterior" : type is "CarPark" or "HouseExterior" ? "Exterior" : "Interior", + Setting = exteriorTransport || type is "CarPark" or "HouseExterior" or "Beach" or "Canal" or "StreetFurniture" ? "Exterior" : "Interior", Palette = ["clear blue", "warm practical light", "balanced neutral accents"], MetadataTags = ["location", "demand-generated", "story-intelligence"], Metadata = new(StringComparer.OrdinalIgnoreCase) @@ -642,6 +649,7 @@ public sealed class StoryIntelligenceIllustrationMatchingService( ["locationType"] = type } }; + } private static IllustrationGenerationSpecification AssetDemandSpecification(string code, string demandKey, string type, string colour) => new()