Fix road-family location image demand rules

This commit is contained in:
Nick Beckley 2026-07-30 19:18:32 +00:00
parent cea45d1278
commit c9a8234745
3 changed files with 33 additions and 21 deletions

View File

@ -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.");

View File

@ -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";

View File

@ -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()