tts-voice-creator-clone-and.../tests/test_character_species_isolation.py
mARTin-B78 4fd21b1d1c fix(characters): base dwarf body lock and species text on the character's own species assertion
Extraction notes in race_species that mention another character's species
("Mensch (oder Zwerg, ...)", "... 'Der Zwerg' wird separat erwähnt ...")
switched on the dwarf body lock (short legs, full beard) for human characters
and carried a sentence about another character into the Subject clause.
Confirmed on the real library: Marrad, Kolon and Arthag (book 02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:09:19 +02:00

100 lines
4.6 KiB
Python

"""A character's species comes only from its OWN species assertion.
Regression for the dwarf-trait leak found on the real library (2026-10-03):
extraction notes in ``race_species`` that merely mention another character's
species ("Mensch (oder Zwerg, je nach Kontext ...)", "Mensch (... 'Der Zwerg'
wird separat erwähnt ...)") switched on the dwarf body lock (short legs, thick
full beard) for human characters, and the note's text about the other
character reached the Subject clause.
"""
from __future__ import annotations
import os
import tempfile
import unittest
os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-species-test-config-"))
import routes.conversation as conv
# Shapes copied from real records (book 02): a thin human boy and a human whose
# species note reasons about a separate dwarf.
HUMAN_BOY_HEDGED = {
"race_species": "Mensch (oder Zwerg, je nach Kontext 'Amboßzwerg', hier als junger Krieger beschrieben)",
"gender": "male",
"physical": "Klein, dünn, schwach; hat dünne Arme.",
"clothing": "Ein roter Umhang aus Wolle.",
}
HUMAN_WITH_DWARF_NOTE = {
"race_species": (
"Mensch (oder Ork-Verbündeter, Kontext unklar, aber 'Der Zwerg' wird separat erwähnt. "
"Warte: 'Der Zwerg lächelte grimmig ... Es wäre allein sein Verdienst ...'. "
"Der Text identifiziert den Zwerg als den, der den Erfolg für sich reklamiert.)"
),
"gender": "male",
"physical": "Schlanker Mann mit kurzem Haar.",
"clothing": "Ein grauer Wollmantel.",
}
DWARF = {
"race_species": "Zwerg (Amboßzwerg)",
"gender": "male",
"physical": "Breit gebaut, mit langem rotem Bart.",
"clothing": "Ein Kettenhemd über einer Wolltunika.",
}
DWARF_TERMS = ("dwarf", "zwerg", "beard")
def _render(name: str, sheet: dict) -> list[str]:
sheet = conv._character_form_sheet(sheet)
portrait = conv._build_character_portrait_prompt(name, "Book", sheet, {}, "cinematic")
turnaround = conv._build_character_sheet_prompt(name, "Book", sheet, {}, "cinematic")
out = conv._build_compact_character_render_prompts(name, "Book", sheet, {}, "cinematic", portrait, turnaround)
return [*out[:5], *out[5]]
class SpeciesAssertionTests(unittest.TestCase):
def test_hedged_mention_of_dwarf_is_not_a_dwarf(self):
self.assertFalse(conv._character_is_dwarf(HUMAN_BOY_HEDGED))
self.assertFalse(conv._character_is_dwarf(HUMAN_WITH_DWARF_NOTE))
self.assertNotEqual(conv._age_body_lock(HUMAN_BOY_HEDGED), conv._DWARF_BODY_LOCK)
def test_real_dwarf_still_gets_dwarf_lock(self):
self.assertTrue(conv._character_is_dwarf(DWARF))
self.assertTrue(conv._character_is_dwarf({"race_species": "Erzzwerg"}))
self.assertTrue(conv._character_is_dwarf({"race_species": "Mensch", "visual_species": "Zwerg"}))
self.assertEqual(conv._age_body_lock(DWARF), conv._DWARF_BODY_LOCK)
def test_alternatives_are_not_asserted(self):
self.assertFalse(conv._character_is_dwarf({"race_species": "Mensch oder Zwerg"}))
self.assertFalse(conv._character_is_dwarf({"race_species": "Mensch (oder Zwerg)"}))
def test_species_reasoning_about_others_is_dropped(self):
self.assertEqual(conv._character_species_text(HUMAN_WITH_DWARF_NOTE), "Mensch")
self.assertEqual(conv._character_species_text(HUMAN_BOY_HEDGED), "Mensch")
# short, factual notes survive unchanged
self.assertEqual(conv._character_species_text({"race_species": "Mensch (Thorwaler)"}), "Mensch (Thorwaler)")
self.assertEqual(conv._character_species_text({"race_species": "Zwerg (Amboßzwerg)"}), "Zwerg (Amboßzwerg)")
facts = conv._character_visual_facts("Arthag", HUMAN_WITH_DWARF_NOTE)
self.assertNotIn("Zwerg", facts)
self.assertIn("species: Mensch", facts)
class ConsecutiveCharacterPromptTests(unittest.TestCase):
"""A dwarf and a human built back to back: no dwarf trait in the human's prompts."""
def test_no_dwarf_traits_in_human_prompts(self):
for order in ((("Garbaz", DWARF), ("Marrad", HUMAN_BOY_HEDGED), ("Arthag", HUMAN_WITH_DWARF_NOTE)),
(("Marrad", HUMAN_BOY_HEDGED), ("Garbaz", DWARF), ("Arthag", HUMAN_WITH_DWARF_NOTE))):
prompts = {name: _render(name, sheet) for name, sheet in order}
for name in ("Marrad", "Arthag"):
text = " ".join(prompts[name]).lower()
for term in DWARF_TERMS:
self.assertNotIn(term, text, f"{term!r} leaked into {name}")
dwarf_text = " ".join(prompts["Garbaz"]).lower()
self.assertIn("adult dwarf", dwarf_text)
self.assertIn("beard", dwarf_text)
if __name__ == "__main__":
unittest.main()