Strengthen pronoun presentation evidence

This commit is contained in:
Nick Beckley 2026-07-26 15:38:31 +00:00
parent 17144c64d3
commit b2b7fbc735
4 changed files with 13 additions and 6 deletions

View File

@ -1090,6 +1090,8 @@ static void Phase21UEnforcesAliasAgeSemanticAndUiResetRules()
new StoryMemoryCharacterAttribute { AttributeType = "AgeBand", NormalisedValue = StoryIntelligenceIllustrationCompatibility.AgeBands.Child, Confidence = 0.98m, IsExplicit = true, EvidenceSummary = "Deterministic evidence inference" },
new StoryMemoryCharacterAttribute { AttributeType = "AgeBand", NormalisedValue = StoryIntelligenceIllustrationCompatibility.AgeBands.YoungAdult, Confidence = 0.90m, IsExplicit = true, EvidenceSummary = "She may be eighteen now, but she is still the baby of the group." }
]);
var alisonPronoun = StoryIntelligenceIllustrationCompatibility.NormalizePresentation("She was a nurse at Selly Oak Hospital.");
var alisonName = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Alison", ["Alison"], []);
var gregFromRosieAction = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Greg", ["Greg offers to take Rosie to hospital."], []);
var childCandidate = Metadata("char-child", "Child", "Feminine");
var darkRedCandidate = Metadata("char-red-dark", "OlderTeen", "Feminine", hairColour: "Red", skinTone: "Dark");
@ -1097,6 +1099,8 @@ static void Phase21UEnforcesAliasAgeSemanticAndUiResetRules()
Assert(bethFromLeakyEvidence.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"Name/title guard should repair leaked Beth presentation, got {bethFromLeakyEvidence.Presentation}.");
Assert(mrsWithTeenContext.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Adult, $"Adult-role titles must not be overridden by primary age context, got {mrsWithTeenContext.AgeBand}.");
Assert(rosieBirthday.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.YoungAdult, $"Rosie's explicit eighteen-year-old evidence must beat weak child leakage, got {rosieBirthday.AgeBand}.");
Assert(alisonPronoun == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"Direct she/her pronoun evidence should infer Feminine, got {alisonPronoun}.");
Assert(alisonName.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"Common feminine name Alison should infer Feminine, got {alisonName.Presentation}.");
Assert(gregFromRosieAction.Presentation != StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, "Greg must not inherit feminine presentation from action text mentioning Rosie.");
Assert(Rejected(mum, childCandidate).Any(reason => reason.Contains("age hard mismatch", StringComparison.OrdinalIgnoreCase)), "Parent role should reject child portraits.");
Assert(Rejected(redHairedMaggie, darkRedCandidate).Any(reason => reason.Contains("skin tone hard mismatch", StringComparison.OrdinalIgnoreCase)), "Red-haired light-skin evidence must reject dark-skin generated portraits.");
@ -1183,8 +1187,8 @@ static void Phase21RSceneBrowserAndPanelsAreReplayFriendly()
Assert(css.Contains("--story-side-body-font-size: 0.72rem;", StringComparison.Ordinal), "Left and right side panels should share body text sizing.");
Assert(!view.Contains("data-assets", StringComparison.Ordinal), "Asset visualisation lane should not be rendered on the main stage.");
Assert(script.Contains("Assets are recorded in insights/diagnostics but not rendered as stage nodes.", StringComparison.Ordinal), "Asset data should remain available without drawing asset stage nodes.");
Assert(script.Contains("width * 0.70 - size / 2", StringComparison.Ordinal), "Location should use the former asset space once asset nodes are removed.");
Assert(css.Contains(".story-exp-zone--location .story-exp-zone-label {\n left: 67%;", StringComparison.Ordinal), "Location lane label should follow the rebalanced stage.");
Assert(script.Contains("width * 0.76 - size / 2", StringComparison.Ordinal), "Location should use the former asset space once asset nodes are removed.");
Assert(css.Contains(".story-exp-zone--location .story-exp-zone-label {\n left: 73%;", StringComparison.Ordinal), "Location lane label should follow the rebalanced stage.");
Assert(!css.Contains("grid-template-rows: minmax(218px, 1.35fr) minmax(185px, 1.08fr) auto", StringComparison.Ordinal), "Right rail must not use fixed-height panel tracks.");
Assert(!css.Contains("grid-template-rows: auto auto minmax(170px, 1fr) minmax(96px, 0.56fr)", StringComparison.Ordinal), "Left rail must not use fixed-height panel tracks.");
Assert(script.Contains("shouldUseCharacterPortrait", StringComparison.Ordinal), "Characters should use evidence cards until portrait evidence is strong enough.");

View File

@ -222,6 +222,9 @@ public static class StoryIntelligenceIllustrationCompatibility
public static string NormalizePresentation(string? value)
{
var text = (value ?? string.Empty).ToLowerInvariant();
var padded = $" {text} ";
if (ContainsAny(padded, " she ", " her ", " hers ", " herself ")) return Presentations.Feminine;
if (ContainsAny(padded, " he ", " him ", " his ", " himself ")) return Presentations.Masculine;
if (ContainsAny(text, "feminine", "female", "woman", "girl", "mother", "mum", "mrs", "miss", "ms", "aunt", "sister", "daughter", "wife", "grandmother")) return Presentations.Feminine;
if (ContainsAny(text, "masculine", "male", "man", "boy", "father", "dad", "mr", "uncle", "brother", "son", "husband", "grandfather")) return Presentations.Masculine;
if (text.Contains("androgynous", StringComparison.OrdinalIgnoreCase)) return Presentations.Androgynous;
@ -512,7 +515,7 @@ public static class StoryIntelligenceIllustrationCompatibility
private static EvidenceSignal NamePresentation(string text)
{
if (ContainsAny(text, " beth ", " maggie ", " rosie ", " grace ", " zoe ", " rebecca ", " annie ", " mary ", " sharon ", " catherine ", " susan ", " helen ", " elen ")) return new(Presentations.Feminine, 0.82m);
if (ContainsAny(text, " alison ", " beth ", " maggie ", " rosie ", " grace ", " zoe ", " rebecca ", " annie ", " mary ", " sharon ", " catherine ", " susan ", " helen ", " elen ")) return new(Presentations.Feminine, 0.82m);
if (ContainsAny(text, " adam ", " colin ", " david ", " kevin ", " simon ", " graham ", " gareth ", " john ", " michael ", " peter ", " richard ", " rick ", " george ")) return new(Presentations.Masculine, 0.82m);
return EvidenceSignal.Unknown;
}
@ -555,7 +558,7 @@ public static class StoryIntelligenceIllustrationCompatibility
}
if (StartsWithAny(clean, "mrs", "miss", "ms", "mother", "mum", "mom", "aunt", "sister", "daughter", "girl", "woman", "female")
|| clean is "beth" or "maggie" or "grace" or "rosie" or "rebecca" or "annie" or "mary" or "susan" or "helen" or "elen" or "zoe" or "sharon" or "catherine")
|| clean is "alison" or "beth" or "maggie" or "grace" or "rosie" or "rebecca" or "annie" or "mary" or "susan" or "helen" or "elen" or "zoe" or "sharon" or "catherine")
{
return new(Presentations.Feminine, 0.98m);
}

View File

@ -777,7 +777,7 @@ input:focus-visible {
}
.story-exp-zone--location .story-exp-zone-label {
left: 67%;
left: 73%;
top: 22px;
}

View File

@ -907,7 +907,7 @@
const height = Math.max(500, bounds?.height || 560);
const size = Math.min(220, Math.max(176, width * 0.18));
return {
x: Math.round(width * 0.70 - size / 2),
x: Math.round(width * 0.76 - size / 2),
y: Math.round(height * 0.46 - size / 2),
size
};