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