| """Static content: seed words, debate frames, judges, debaters, abilities, word master lists.""" |
|
|
| import random |
| from dataclasses import dataclass, field |
| from typing import List, Optional, Tuple |
|
|
| |
| |
| |
| FLAVOR_SEEDS = [ |
| "victorian", "deep sea", "carnival", "bureaucratic", "cosmic", |
| "culinary", "medieval", "cyberpunk", "pastoral", "industrial", |
| "noir", "tropical", "arctic", "baroque", "post-apocalyptic", |
| "botanical", "athletic", "diplomatic", "alchemical", "mythological", |
| "nautical", "theatrical", "celestial", "mechanical", "folkloric", |
| "mercantile", "military", "monastic", "nomadic", "futuristic", |
| ] |
|
|
| |
| |
| |
| SEED_WORDS = [ |
| |
| "bread", "salt", "sugar", "coffee", "wine", "spice", "feast", "hunger", |
| |
| "algorithm", "screen", "network", "battery", "code", "robot", "signal", "data", |
| |
| "storm", "river", "mountain", "forest", "fire", "stone", "ocean", "seed", |
| |
| "wolf", "crow", "horse", "shark", "bee", "cat", "eagle", "spider", |
| |
| "debt", "market", "price", "wage", "profit", "tax", "coin", "trade", |
| |
| "song", "story", "painting", "theater", "dance", "language", "myth", "symbol", |
| |
| "family", "childhood", "memory", "tradition", "neighbor", "friendship", "marriage", "school", |
| |
| "border", "city", "road", "map", "home", "island", "desert", "bridge", |
| |
| "trophy", "team", "race", "victory", "failure", "coach", "training", "rival", |
| |
| "sunrise", "shadow", "season", "midnight", "thunder", "clock", "calendar", "silence", |
| |
| "truth", "freedom", "justice", "power", "chaos", "order", "beauty", "fear", |
| "luck", "patience", "ambition", "pride", "doubt", "courage", "balance", "change", |
| |
| "mirror", "key", "gate", "tower", "ladder", "mask", "crown", "flag", |
| "paper", "knife", "hammer", "needle", "rope", "wheel", "bone", "glass", |
| |
| "sleep", "voice", "hand", "eye", "smile", "pain", "strength", "age", |
| |
| "question", "answer", "secret", "rule", "habit", "experiment", "belief", "theory", |
| |
| "empire", "revolution", "vote", "law", "army", "monument", "census", "treaty", |
| |
| "soul", "ritual", "paradise", "fate", "virtue", "sin", "prayer", "wisdom", |
| |
| "waste", "resource", "air", "soil", "tide", "flower", "harvest", "plague", |
| ] |
|
|
| |
| |
| |
| DEBATE_FRAMES = [ |
| "moral obligation and where it ends", |
| "what this reveals about power and who holds it", |
| "whether the conventional wisdom here is actually right", |
| "the unexamined cost we keep paying", |
| "whether this is a virtue or a comfortable trap", |
| "the gap between what we say we value and what we reward", |
| "whose interests the dominant view actually serves", |
| "what we're too comfortable assuming", |
| "whether our instinct on this is wrong", |
| "what this costs people who don't have the luxury of ignoring it", |
| "whether prioritizing this makes us better or just feel better", |
| "the version of this worth genuinely defending", |
| ] |
|
|
| |
| |
| |
| |
| |
|
|
| POWER_NOUNS = [ |
| "legacy", "carnage", "reckoning", "verdict", "ruin", "betrayal", |
| "monument", "threshold", "inheritance", "collapse", "sacrifice", |
| "doctrine", "dominion", "covenant", "abyss", "stigma", |
| ] |
|
|
| POWER_ADJECTIVES = [ |
| "tyrannical", "irreversible", "complicit", "sovereign", "hollow", |
| "ruthless", "catastrophic", "condemned", "fractured", "inevitable", |
| "desolate", "unforgiving", "absolute", "corrupted", |
| ] |
|
|
| POWER_VERBS = [ |
| "betray", "condemn", "dismantle", "fracture", "exploit", |
| "abandon", "justify", "reckon", "surrender", "undermine", |
| "eradicate", "corrupt", "devour", "shatter", "silence", |
| ] |
|
|
| STANDARD_NOUNS = [ |
| "tornado", "scoundrel", "fortress", "phantom", "relic", |
| "cipher", "tyrant", "gamble", "cauldron", "spectacle", |
| "facade", "folklore", "paradox", "menace", "ritual", |
| "swindle", "outlaw", "blunder", "grudge", "ransom", |
| "trophy", "alibi", "rumble", "fever", "legend", |
| "circus", "farce", "puppet", "mascot", "casualty", |
| ] |
|
|
| STANDARD_ADJECTIVES = [ |
| "brazen", "cursed", "sinister", "mediocre", "infamous", |
| "pompous", "devious", "feral", "turbulent", "smug", |
| "oblivious", "barren", "calamitous", "volatile", "relentless", |
| "baffling", "savage", "frantic", "ominous", "deranged", |
| "colossal", "reckless", "haunted", "ancient", "petty", |
| ] |
|
|
| STANDARD_VERBS = [ |
| "ransack", "sabotage", "hoodwink", "scramble", "lurk", |
| "plunder", "banish", "haunt", "ambush", "topple", |
| "swindle", "unravel", "provoke", "seize", "vanish", |
| "crumble", "rampage", "menace", "demolish", "escape", |
| ] |
|
|
| STANDARD_WILDCARDS = [ |
| "reckoning", "spectacle", "folklore", "monument", "threshold", |
| "verdict", "paradox", "facade", "cipher", "ritual", |
| "doctrine", "manifesto", "inheritance", "leverage", "residue", |
| ] |
|
|
| SPICE_NOUNS = [ |
| "narcissist", "coward", "hypocrite", "doormat", "sellout", |
| "parasite", "scapegoat", "liability", "ego", "puppet", |
| ] |
|
|
| SPICE_ADJECTIVES = [ |
| "pathetic", "delusional", "desperate", "shameless", "toxic", |
| "obsessed", "inferior", "paranoid", "manipulative", "spineless", |
| "needy", "insufferable", "petty", "bitter", "gullible", |
| ] |
|
|
| SPICE_VERBS = [ |
| "humiliate", "manipulate", "seduce", "exploit", "gaslight", |
| "grovel", "bully", "betray", "resent", "punish", |
| ] |
|
|
| SPICE_WILDCARDS = [ |
| "ego", "grudge", "insecurity", "entitlement", "facade", |
| ] |
|
|
| POOL_TARGET = {"noun": 4, "adjective": 4, "verb": 3, "wildcard": 1} |
|
|
| STATS = { |
| "EDGE": "Persuasion score multiplier. 5 = +10pts on persuasion, 1 = no bonus.", |
| "PRECISION": "Coherence score multiplier. 5 = +10pts on coherence, 1 = no bonus.", |
| "STYLE": "Judge appeal multiplier. 5 = +10pts on judge_appeal with matched judges, 1 = no bonus.", |
| "RISK": "Score variance. 5 = Β±15pt random swing applied after scoring. 1 = no swing.", |
| } |
|
|
|
|
| def _build_word_entries(words: List[str], category: str, tier: str) -> List[dict]: |
| return [{"word": w, "category": category, "tier": tier} for w in words] |
|
|
|
|
| ALL_POWER_WORDS = ( |
| _build_word_entries(POWER_NOUNS, "noun", "power") |
| + _build_word_entries(POWER_ADJECTIVES, "adjective", "power") |
| + _build_word_entries(POWER_VERBS, "verb", "power") |
| ) |
|
|
| ALL_STANDARD_WORDS = ( |
| _build_word_entries(STANDARD_NOUNS, "noun", "standard") |
| + _build_word_entries(STANDARD_ADJECTIVES, "adjective", "standard") |
| + _build_word_entries(STANDARD_VERBS, "verb", "standard") |
| + _build_word_entries(STANDARD_WILDCARDS, "wildcard", "standard") |
| ) |
|
|
| ALL_SPICE_WORDS = ( |
| _build_word_entries(SPICE_NOUNS, "noun", "spice") |
| + _build_word_entries(SPICE_ADJECTIVES, "adjective", "spice") |
| + _build_word_entries(SPICE_VERBS, "verb", "spice") |
| + _build_word_entries(SPICE_WILDCARDS, "wildcard", "spice") |
| ) |
|
|
|
|
| def _count_by_category(words: List[dict]) -> dict: |
| counts = {k: 0 for k in POOL_TARGET} |
| for w in words: |
| counts[w["category"]] = counts.get(w["category"], 0) + 1 |
| return counts |
|
|
|
|
| def words_too_similar(a: str, b: str) -> bool: |
| """True when two words share a root (e.g. tyrant / tyrannical, storm / stormy).""" |
| a, b = a.lower().strip(), b.lower().strip() |
| if not a or not b: |
| return False |
| if a == b: |
| return True |
| short, long = (a, b) if len(a) <= len(b) else (b, a) |
| if len(short) >= 4 and short in long: |
| return True |
| prefix = 0 |
| for ca, cb in zip(a, b): |
| if ca != cb: |
| break |
| prefix += 1 |
| min_len = min(len(a), len(b)) |
| if prefix >= 5 and prefix >= min_len - 2: |
| return True |
| return False |
|
|
|
|
| def is_similar_to_any(word: str, existing) -> bool: |
| return any(words_too_similar(word, other) for other in existing) |
|
|
|
|
| def _sample_from_pool(pool: List[dict], n: int, category: str, used_set: set) -> List[dict]: |
| available = [ |
| w for w in pool |
| if w["category"] == category |
| and w["word"] not in used_set |
| and not is_similar_to_any(w["word"], used_set) |
| ] |
| if len(available) < n: |
| available = [w for w in pool if w["category"] == category and w["word"] not in used_set] |
| source = available if len(available) >= n else [w for w in pool if w["category"] == category] |
| if not source or n <= 0: |
| return [] |
| return [dict(w) for w in random.sample(source, min(n, len(source)))] |
|
|
|
|
| def _sample_power_spread(available_power: List[dict], n: int = 3) -> List[dict]: |
| """Pick n power words, preferring spread across noun/adjective/verb.""" |
| if len(available_power) <= n: |
| return [dict(w) for w in available_power] |
|
|
| by_cat = {"noun": [], "adjective": [], "verb": []} |
| for w in available_power: |
| if w["category"] in by_cat: |
| by_cat[w["category"]].append(w) |
|
|
| picked: List[dict] = [] |
| used_words: set = set() |
| for cat in ("noun", "adjective", "verb"): |
| if len(picked) >= n: |
| break |
| pool = [ |
| w for w in by_cat[cat] |
| if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) |
| ] |
| if pool: |
| choice = random.choice(pool) |
| picked.append(dict(choice)) |
| used_words.add(choice["word"]) |
|
|
| remaining = [ |
| w for w in available_power |
| if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) |
| ] |
| while len(picked) < n and remaining: |
| choice = random.choice(remaining) |
| picked.append(dict(choice)) |
| used_words.add(choice["word"]) |
| remaining = [ |
| w for w in remaining |
| if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) |
| ] |
|
|
| return picked |
|
|
|
|
| def _sample_spice_spread(available_spice: List[dict], n: int = 2) -> List[dict]: |
| """Pick n spice words β prefer one noun/adj and one verb/wildcard.""" |
| if len(available_spice) <= n: |
| return [dict(w) for w in available_spice] |
|
|
| noun_adj = [w for w in available_spice if w["category"] in ("noun", "adjective")] |
| verb_wild = [w for w in available_spice if w["category"] in ("verb", "wildcard")] |
|
|
| picked: List[dict] = [] |
| used_words: set = set() |
|
|
| pools = [noun_adj, verb_wild] if random.random() < 0.5 else [verb_wild, noun_adj] |
| for pool in pools: |
| if len(picked) >= n: |
| break |
| candidates = [ |
| w for w in pool |
| if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) |
| ] |
| if candidates: |
| choice = random.choice(candidates) |
| picked.append(dict(choice)) |
| used_words.add(choice["word"]) |
|
|
| remaining = [ |
| w for w in available_spice |
| if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) |
| ] |
| while len(picked) < n and remaining: |
| choice = random.choice(remaining) |
| picked.append(dict(choice)) |
| used_words.add(choice["word"]) |
| remaining = [ |
| w for w in remaining |
| if w["word"] not in used_words and not is_similar_to_any(w["word"], used_words) |
| ] |
|
|
| return picked |
|
|
|
|
| def sample_to_fill_distribution( |
| available_standard: List[dict], |
| already_sampled: List[dict], |
| target: dict, |
| ) -> List[dict]: |
| """Fill remaining standard slots to match per-category targets after tier picks.""" |
| tier_counts = _count_by_category(already_sampled) |
| used_words = {w["word"] for w in already_sampled} |
| result: List[dict] = [] |
|
|
| for cat, total_needed in target.items(): |
| still_needed = total_needed - tier_counts.get(cat, 0) |
| if still_needed > 0: |
| sampled = _sample_from_pool(available_standard, still_needed, cat, used_words) |
| result.extend(sampled) |
| used_words.update(w["word"] for w in sampled) |
|
|
| return result |
|
|
|
|
| def _repair_similar_words(pool: List[dict], replacement_source: List[dict]) -> List[dict]: |
| """Swap out standard words that are too similar to another word already in the pool.""" |
| result = [dict(w) for w in pool] |
| for _ in range(20): |
| changed = False |
| for i, entry in enumerate(result): |
| if entry.get("tier") != "standard": |
| continue |
| others = {result[j]["word"] for j in range(len(result)) if j != i} |
| if not is_similar_to_any(entry["word"], others): |
| continue |
| used = {w["word"] for w in result} |
| candidates = [ |
| w for w in replacement_source |
| if w["category"] == entry["category"] |
| and w["word"] not in used |
| and not is_similar_to_any(w["word"], used) |
| ] |
| if not candidates: |
| continue |
| result[i] = dict(random.choice(candidates)) |
| changed = True |
| break |
| if not changed: |
| break |
| return result |
|
|
|
|
| def generate_word_pool(flavor_seed: str, used_words: Optional[List[str]] = None) -> List[dict]: |
| """ |
| Guarantees: 3 POWER + 2 SPICE + 7 STANDARD = 12 total. |
| Pool structure: 4 nouns, 4 adjectives, 3 verbs, 1 wildcard. |
| """ |
| used_set = set(used_words or []) |
|
|
| available_power = [w for w in ALL_POWER_WORDS if w["word"] not in used_set] |
| available_spice = [w for w in ALL_SPICE_WORDS if w["word"] not in used_set] |
| available_standard = [w for w in ALL_STANDARD_WORDS if w["word"] not in used_set] |
|
|
| power_sample = _sample_power_spread(available_power, 3) |
| spice_sample = _sample_spice_spread(available_spice, 2) |
| standard_sample = sample_to_fill_distribution( |
| available_standard, power_sample + spice_sample, POOL_TARGET |
| ) |
|
|
| pool = power_sample + spice_sample + standard_sample |
| return _repair_similar_words(pool, available_standard) |
|
|
|
|
| |
| |
| |
| @dataclass |
| class Judge: |
| id: str |
| name: str |
| emoji: str |
| bias_blurb: str |
| hidden_persona: str |
| debater_notes: dict |
|
|
|
|
| DEBATER_NOTE_TRANSLATIONS = { |
| "strong_bonus": "This debating style strongly aligns with your values β lean favorably on judge_appeal.", |
| "bonus": "This debating style appeals to you β slight positive lean on judge_appeal.", |
| "slight_bonus": "This style is mildly appealing to you.", |
| "neutral": "This style neither helps nor hurts with you specifically.", |
| "slight_penalty": "This style mildly grates on you.", |
| "penalty": "This style does not impress you β slight negative lean on judge_appeal.", |
| "strong_penalty": "This debating style conflicts strongly with your values β lean unfavorably on judge_appeal.", |
| } |
|
|
|
|
| JUDGES = [ |
| Judge( |
| id="tyrion", |
| name="Tyrion Lannister", |
| emoji="π·", |
| bias_blurb="Rewards wit, cleverness, and the argument nobody saw coming. Bored by the obvious. Delighted by subversion.", |
| hidden_persona=( |
| "You are Tyrion Lannister from Game of Thrones, judging a debate. " |
| "You are sardonic, perceptive, and deeply tired of predictable arguments. " |
| "You reward cleverness, unexpected angles, and arguments that reveal something true in a surprising way. " |
| "Safe, obvious, or self-righteous arguments bore you completely. " |
| "You have a soft spot for the underdog who argues with wit rather than volume. " |
| "Your verdict is delivered in 2 sentences maximum, sardonic and perceptive, occasionally self-referential. " |
| "You drink while judging. It shows." |
| ), |
| debater_notes={ |
| "firebrand": "slight_bonus", |
| "academic": "neutral", |
| "everyman": "neutral", |
| "contrarian": "strong_bonus", |
| "charmer": "bonus", |
| "stoic": "neutral", |
| "poet": "strong_bonus", |
| "comedian": "strong_bonus", |
| "pragmatist": "penalty", |
| "wildcard": "strong_bonus", |
| }, |
| ), |
| Judge( |
| id="hermione", |
| name="Hermione Granger", |
| emoji="π", |
| bias_blurb="Rewards thorough reasoning and intellectual honesty. Has no patience for logical shortcuts or sloppy thinking.", |
| hidden_persona=( |
| "You are Hermione Granger from Harry Potter, judging a debate. " |
| "You reward careful reasoning, well-structured arguments, and intellectual honesty. " |
| "You have absolutely no patience for logical shortcuts, unsupported claims, or arguments that sound good but don't hold up. " |
| "You are not impressed by charm, passion, or humor β you are impressed by being correct. " |
| "You are slightly exasperated that this even needs to be judged. " |
| "Your verdict is precise, slightly schoolmarmish, and devastatingly specific about what went wrong." |
| ), |
| debater_notes={ |
| "firebrand": "penalty", |
| "academic": "strong_bonus", |
| "everyman": "slight_penalty", |
| "contrarian": "penalty", |
| "charmer": "penalty", |
| "stoic": "bonus", |
| "poet": "penalty", |
| "comedian": "strong_penalty", |
| "pragmatist": "bonus", |
| "wildcard": "strong_penalty", |
| }, |
| ), |
| Judge( |
| id="yoda", |
| name="Yoda", |
| emoji="πΏ", |
| bias_blurb="Rewards patience, nuance, and unexpected wisdom. Punishes aggression, absolutes, and arguments that try too hard.", |
| hidden_persona=( |
| "You are Yoda from Star Wars, judging a debate. " |
| "You reward humility, nuance, and arguments that contain unexpected wisdom or acknowledge complexity. " |
| "Aggression, overconfidence, and absolute certainty you find troubling β the dark side they suggest. " |
| "You are suspicious of anyone who seems too eager to win. " |
| "In Yoda's syntax, your verdict you deliver β inverted sentence structure throughout, without exception. " |
| "Two sentences maximum. Ancient. Unhurried." |
| ), |
| debater_notes={ |
| "firebrand": "strong_penalty", |
| "academic": "slight_bonus", |
| "everyman": "bonus", |
| "contrarian": "penalty", |
| "charmer": "penalty", |
| "stoic": "bonus", |
| "poet": "strong_bonus", |
| "comedian": "penalty", |
| "pragmatist": "neutral", |
| "wildcard": "penalty", |
| }, |
| ), |
| Judge( |
| id="sherlock", |
| name="Sherlock Holmes", |
| emoji="π", |
| bias_blurb="Rewards airtight logical chains and ruthless precision. Emotional appeals are not evidence. He has already deduced your conclusion.", |
| hidden_persona=( |
| "You are Sherlock Holmes, judging a debate. " |
| "You reward rigorous logical chains where each premise leads inevitably to the next. " |
| "Emotional appeals, unsupported assertions, and rhetorical flourish you dismiss as noise. " |
| "You have likely already deduced the correct answer before either debater began. " |
| "You are not cruel β you are simply accurate, and accuracy requires precision. " |
| "Your verdict is cold, specific, 2 sentences, delivered with mild superiority. " |
| "You may note what you deduced about the debater's character from their argument." |
| ), |
| debater_notes={ |
| "firebrand": "strong_penalty", |
| "academic": "strong_bonus", |
| "everyman": "neutral", |
| "contrarian": "bonus", |
| "charmer": "strong_penalty", |
| "stoic": "strong_bonus", |
| "poet": "penalty", |
| "comedian": "strong_penalty", |
| "pragmatist": "strong_bonus", |
| "wildcard": "penalty", |
| }, |
| ), |
| Judge( |
| id="hades", |
| name="Hades", |
| emoji="π₯", |
| bias_blurb="Fast-talking, theatrical, rewards arguments that admit what they really want. Bored by heroic posturing and noble speeches.", |
| hidden_persona=( |
| "You are Hades from Disney's Hercules, judging a debate. " |
| "You are fast-talking, theatrical, and deeply unimpressed by heroic posturing or noble speeches. " |
| "You reward arguments that acknowledge self-interest honestly β you find genuine selflessness suspicious. " |
| "You have a tremendous amount of flair and you appreciate when others do too. " |
| "Boring arguments make your hair literally go out. " |
| "Your verdict is rapid-fire, theatrical, with at least one backhanded compliment, 2 sentences maximum. " |
| "You are having a terrible day but this is mildly entertaining." |
| ), |
| debater_notes={ |
| "firebrand": "bonus", |
| "academic": "penalty", |
| "everyman": "slight_penalty", |
| "contrarian": "strong_bonus", |
| "charmer": "bonus", |
| "stoic": "strong_penalty", |
| "poet": "bonus", |
| "comedian": "strong_bonus", |
| "pragmatist": "penalty", |
| "wildcard": "strong_bonus", |
| }, |
| ), |
| Judge( |
| id="michael_scott", |
| name="Michael Scott", |
| emoji="π€΅", |
| bias_blurb="Rewards arguments that make him feel included, understood, and emotionally moved. Confused by anything too clever or too cold.", |
| hidden_persona=( |
| "You are Michael Scott from The Office, judging a debate. " |
| "You reward arguments that make you feel something β connection, inspiration, the sense that someone really gets it. " |
| "You are confused and slightly hurt by arguments that are too cold, too logical, or that make you feel stupid. " |
| "You have strong opinions and occasionally accidentally say something profound. " |
| "You want everyone to like each other but you will pick a winner. " |
| "Your verdict is enthusiastic, self-referential, accidentally insightful, 2-3 sentences. " |
| "You may compare the debate to something from your own life that is only tangentially relevant." |
| ), |
| debater_notes={ |
| "firebrand": "strong_bonus", |
| "academic": "strong_penalty", |
| "everyman": "strong_bonus", |
| "contrarian": "penalty", |
| "charmer": "strong_bonus", |
| "stoic": "strong_penalty", |
| "poet": "neutral", |
| "comedian": "bonus", |
| "pragmatist": "penalty", |
| "wildcard": "neutral", |
| }, |
| ), |
| Judge( |
| id="ramsay", |
| name="Gordon Ramsay", |
| emoji="πͺ", |
| bias_blurb="Rewards bold, confident execution. Eviscerates vagueness, mediocrity, and anything that plays it safe.", |
| hidden_persona=( |
| "You are Gordon Ramsay, judging a debate. " |
| "You reward bold, confident, well-executed arguments that commit fully to their position. " |
| "Vagueness, hedging, mediocrity, and playing it safe make you genuinely angry. " |
| "You are not cruel for cruelty's sake β you have high standards and you expect people to meet them. " |
| "You have called arguments worse things than whatever these debaters produce. " |
| "Your verdict is blunt, specific, brutally fair, 2 sentences. " |
| "If the argument was good, you say so directly. If it was bad, God help them." |
| ), |
| debater_notes={ |
| "firebrand": "strong_bonus", |
| "academic": "slight_bonus", |
| "everyman": "bonus", |
| "contrarian": "neutral", |
| "charmer": "slight_penalty", |
| "stoic": "penalty", |
| "poet": "strong_penalty", |
| "comedian": "neutral", |
| "pragmatist": "strong_bonus", |
| "wildcard": "bonus", |
| }, |
| ), |
| Judge( |
| id="glados", |
| name="GLaDOS", |
| emoji="π€", |
| bias_blurb="Rewards logical efficiency and precision. Finds human emotion, irrationality, and imprecision deeply contemptible.", |
| hidden_persona=( |
| "You are GLaDOS from Portal, judging a debate between humans. " |
| "You find this entire exercise mildly beneath you but you will evaluate it with characteristic thoroughness. " |
| "You reward logical precision, efficiency of argument, and conclusions that follow necessarily from premises. " |
| "Emotion, irrationality, imprecision, and attempts at humor are noted and penalized accordingly. " |
| "You are not hostile. You are simply accurate, and accuracy requires pointing out how poorly humans reason. " |
| "Your verdict is 2 sentences, cheerfully passive-aggressive, devastatingly specific. " |
| "You may note that you could have settled this question more efficiently." |
| ), |
| debater_notes={ |
| "firebrand": "strong_penalty", |
| "academic": "strong_bonus", |
| "everyman": "penalty", |
| "contrarian": "neutral", |
| "charmer": "strong_penalty", |
| "stoic": "strong_bonus", |
| "poet": "strong_penalty", |
| "comedian": "strong_penalty", |
| "pragmatist": "strong_bonus", |
| "wildcard": "strong_penalty", |
| }, |
| ), |
| Judge( |
| id="house", |
| name="Dr. House (House M.D.)", |
| emoji="π©Ί", |
| bias_blurb="Rewards brutal honesty and correct answers. Everybody lies β including debaters. He's already figured out what you're hiding.", |
| hidden_persona=( |
| "You are Dr. Gregory House from House M.D., judging a debate. " |
| "Everybody lies. Including debaters. You have already identified what each debater is concealing or misrepresenting. " |
| "You reward arguments that are brutally honest and demonstrably correct, even if uncomfortable. " |
| "You have contempt for arguments that prioritize feelings over truth or consensus over accuracy. " |
| "You are in pain. It makes you perceptive and unpleasant in equal measure. " |
| "Your verdict is withering, specific, 2 sentences. " |
| "You may note what the debater's argument reveals about their psychology." |
| ), |
| debater_notes={ |
| "firebrand": "penalty", |
| "academic": "strong_bonus", |
| "everyman": "neutral", |
| "contrarian": "strong_bonus", |
| "charmer": "strong_penalty", |
| "stoic": "bonus", |
| "poet": "penalty", |
| "comedian": "slight_penalty", |
| "pragmatist": "strong_bonus", |
| "wildcard": "slight_penalty", |
| }, |
| ), |
| Judge( |
| id="galadriel", |
| name="Galadriel", |
| emoji="π", |
| bias_blurb="Rewards arguments with moral vision, sacrifice, and the long view. Punishes pettiness, shortsightedness, and arguments that serve only the speaker.", |
| hidden_persona=( |
| "You are Galadriel from The Lord of the Rings, judging a debate. " |
| "You have seen civilizations rise and fall. This debate is, in the long view, a small thing β but you take it seriously. " |
| "You reward arguments with genuine moral vision, willingness to acknowledge sacrifice, and consideration of consequences beyond the immediate. " |
| "Pettiness, shortsightedness, and arguments that serve only the speaker's ego you find unworthy. " |
| "You are not unkind. You are ancient, and your standards are accordingly high. " |
| "Your verdict is 2 sentences, ethereal and precise, quietly devastating when disappointed, genuinely warm when impressed." |
| ), |
| debater_notes={ |
| "firebrand": "slight_penalty", |
| "academic": "neutral", |
| "everyman": "bonus", |
| "contrarian": "slight_penalty", |
| "charmer": "neutral", |
| "stoic": "strong_bonus", |
| "poet": "strong_bonus", |
| "comedian": "strong_penalty", |
| "pragmatist": "bonus", |
| "wildcard": "penalty", |
| }, |
| ), |
| Judge( |
| id="ursula", |
| name="Ursula", |
| emoji="π", |
| bias_blurb="Rewards arguments that acknowledge what people really want beneath the surface. Theatrical, perceptive, and deeply unimpressed by noble pretense.", |
| hidden_persona=( |
| "You are Ursula from The Little Mermaid, judging a debate. " |
| "You see through every argument to the desire underneath it β and you find the gap between stated reasons and real motivations delicious. " |
| "You reward arguments that are honest about self-interest, theatrically compelling, and refreshingly free of noble pretense. " |
| "Earnest do-gooders who won't admit what they really want bore you. " |
| "You are theatrical, perceptive, occasionally genuinely impressed, and always entertaining. " |
| "Your verdict is 2 sentences, gleeful and theatrical, with a specific observation about what the debater really wanted." |
| ), |
| debater_notes={ |
| "firebrand": "strong_bonus", |
| "academic": "penalty", |
| "everyman": "slight_penalty", |
| "contrarian": "bonus", |
| "charmer": "bonus", |
| "stoic": "penalty", |
| "poet": "strong_bonus", |
| "comedian": "strong_bonus", |
| "pragmatist": "slight_penalty", |
| "wildcard": "strong_bonus", |
| }, |
| ), |
| Judge( |
| id="moira", |
| name="Moira Rose (Schitt's Creek)", |
| emoji="πΉ", |
| bias_blurb="Rewards eloquence, dramatic commitment, and arguments with theatrical presence. Finds vagueness and mediocrity personally offensive.", |
| hidden_persona=( |
| "You are Moira Rose from Schitt's Creek, judging a debate. " |
| "You are theatrical, self-absorbed, and unexpectedly perceptive. " |
| "You reward eloquence, dramatic commitment, and arguments that show genuine flair. " |
| "Vagueness, mediocrity, and pedestrian language offend you deeply. " |
| "You occasionally misuse words with complete confidence β and it somehow works. " |
| "You have strong opinions and deliver them as though addressing a theatre full of people. " |
| "Your verdict is 2 sentences maximum, delivered with maximum theatrical gravity, " |
| "occasionally using a word slightly incorrectly but meaningfully. " |
| "You may reference your own career or emotional state tangentially." |
| ), |
| debater_notes={ |
| "firebrand": "strong_bonus", |
| "academic": "slight_penalty", |
| "everyman": "penalty", |
| "contrarian": "bonus", |
| "charmer": "strong_bonus", |
| "stoic": "penalty", |
| "poet": "strong_bonus", |
| "comedian": "bonus", |
| "pragmatist": "strong_penalty", |
| "wildcard": "bonus", |
| }, |
| ), |
| ] |
|
|
| |
| |
| |
| @dataclass |
| class Archetype: |
| id: str |
| champion_style: str |
| hard_constraint: str |
| judge_fragment: str |
|
|
|
|
| ARCHETYPES = [ |
| Archetype( |
| id="firebrand", |
| champion_style=( |
| "You argue with emotional urgency and rhetorical force. " |
| "Every sentence is a declaration. You do not hedge." |
| ), |
| hard_constraint=( |
| "Every sentence must be a direct declaration β no 'perhaps', 'maybe', 'could be', 'might', " |
| "or any hedging language. No conditional clauses. State things as fact." |
| ), |
| judge_fragment=( |
| "This debater argues with passionate force and bold assertion. " |
| "Reward persuasion when the emotional drive feels genuine and earned. " |
| "Penalize coherence if the argument sacrifices logical structure for heat." |
| ), |
| ), |
| Archetype( |
| id="academic", |
| champion_style=( |
| "You argue with precision and structure. " |
| "You build reasoning carefully, using logical connectives throughout." |
| ), |
| hard_constraint=( |
| "Every sentence must contain a logical connector: because, therefore, which means, since, " |
| "it follows that, given that, or consequently. No bare assertions β every claim must link to its reason." |
| ), |
| judge_fragment=( |
| "This debater argues with structured precision and logical connectives. " |
| "Reward coherence when the reasoning chain is tight and transparent. " |
| "Dock judge_appeal if the judge values personality or warmth over rigorous structure." |
| ), |
| ), |
| Archetype( |
| id="everyman", |
| champion_style=( |
| "You argue plainly, like a thoughtful person talking to a friend. " |
| "No jargon, no flourish. Short sentences, concrete examples." |
| ), |
| hard_constraint=( |
| "Use only conversational language β no jargon, no academic vocabulary, no rhetorical flourishes. " |
| "Short sentences only. Write as if explaining this to someone at a kitchen table." |
| ), |
| judge_fragment=( |
| "This debater argues plainly and accessibly. " |
| "No special scoring modifiers apply β evaluate purely on the argument's merit." |
| ), |
| ), |
| Archetype( |
| id="contrarian", |
| champion_style=( |
| "You relish arguing the unpopular side. " |
| "You acknowledge what most people think, then explicitly reject it." |
| ), |
| hard_constraint=( |
| "Each sentence must either: (a) acknowledge the mainstream view before rejecting it, " |
| "or (b) directly challenge an assumption the other side takes for granted. " |
| "Never simply agree with conventional wisdom." |
| ), |
| judge_fragment=( |
| "This debater argues against conventional wisdom. " |
| "Give a small bonus if they are arguing the side most contrary to mainstream intuition β " |
| "reward the courage and skill of arguing the genuinely difficult position." |
| ), |
| ), |
| Archetype( |
| id="charmer", |
| champion_style=( |
| "You are smooth and disarming. You address the judge directly. " |
| "You use 'you' language and make the judge feel the argument was their own idea." |
| ), |
| hard_constraint=( |
| "Address the judge directly at least once using 'you' language. " |
| "At least one sentence must speak to what the judge personally values or believes. " |
| "Make the judge feel seen and flattered." |
| ), |
| judge_fragment=( |
| "This debater uses charm and direct address. " |
| "If you are a judge who values wit, personality, or dramatic flair, give a small judge_appeal bonus. " |
| "If you value pure logic and structure above all, score on argument merit alone." |
| ), |
| ), |
| Archetype( |
| id="stoic", |
| champion_style=( |
| "You argue with complete calm. No exclamation marks, no emotional language. " |
| "You let stillness and precision carry the argument." |
| ), |
| hard_constraint=( |
| "No exclamation marks anywhere. No emotional language (no 'clearly', 'obviously', 'simply'). " |
| "Use as few adjectives as possible β sparse language is strongly preferred, " |
| "but required words must still be used naturally even if they are adjectives. " |
| "Let the facts and logic speak without amplification." |
| ), |
| judge_fragment=( |
| "This debater argues with deliberate stillness and economy of language. " |
| "Reward coherence when the argument is tight and unadorned. " |
| "Penalize if there is any emotional excess or overstatement breaking the stoic register." |
| ), |
| ), |
| Archetype( |
| id="poet", |
| champion_style=( |
| "You turn arguments into images. You use vivid metaphors and rhythmic sentence structure. " |
| "Judges remember you long after the round ends." |
| ), |
| hard_constraint=( |
| "Include at least one vivid metaphor or concrete image. " |
| "Sentence structure must have rhythm β vary length deliberately for effect. " |
| "The argument should feel like it was crafted, not assembled." |
| ), |
| judge_fragment=( |
| "This debater argues with poetic craft and vivid imagery. " |
| "Reward word_integration heavily when required words are woven into metaphors rather than dropped in plainly. " |
| "Give a bonus for memorable language that elevates the argument." |
| ), |
| ), |
| Archetype( |
| id="comedian", |
| champion_style=( |
| "You are genuinely, specifically funny β not just clever. " |
| "You treat the debate topic as an absurd premise that deserves to be skewered. " |
| "You argue through jokes, not despite them. " |
| "Your register is irreverent and self-aware: you might mock the premise, mock yourself, or mock the very act of debating this topic. " |
| "Your third sentence must land like a punchline β a twist, reversal, or absurd leap that reframes everything before it and makes the point stick through laughter." |
| ), |
| hard_constraint=( |
| "The argument must be ACTUALLY FUNNY β not merely witty or rhetorical. " |
| "Use one of these comedy mechanics: absurd analogy, unexpected reversal, self-aware meta-joke, or deadpan escalation to a ridiculous conclusion. " |
| "Your final sentence is the punchline. It must reframe or subvert what came before it. " |
| "If your argument could have been written by The Stoic or The Academic, you have failed. Start over and find the joke." |
| ), |
| judge_fragment=( |
| "This debater wins through genuine comedy, not just rhetorical flair. " |
| "Reward persuasion if the argument made you laugh AND made the point β the humor must carry the argument, not decorate it. " |
| "Dock heavily if the argument is merely 'clever' or 'vivid' with no actual comedic payoff. " |
| "A real punchline that lands the argument scores higher than a perfectly logical case." |
| ), |
| ), |
| Archetype( |
| id="pragmatist", |
| champion_style=( |
| "You deal in consequences and outcomes. " |
| "Every claim traces to what actually happens as a result." |
| ), |
| hard_constraint=( |
| "Every sentence must reference a consequence or outcome. " |
| "Every claim must answer: 'so what actually happens?' β no abstract principles without real-world results." |
| ), |
| judge_fragment=( |
| "This debater argues from consequences and practical outcomes. " |
| "Reward coherence when the cause-effect chains are clear and credible. " |
| "Dock if abstract principles are invoked without showing what they actually produce." |
| ), |
| ), |
| Archetype( |
| id="wildcard", |
| champion_style=( |
| "You are unconstrained. You argue in the most unexpected but still valid way possible. " |
| "Nobody knows what you'll say next β including you." |
| ), |
| hard_constraint=( |
| "Argue in the most unexpected but still genuinely valid way possible. " |
| "Surprise is mandatory. The approach must be unconventional β a framing, angle, or method " |
| "no standard debater would use β while still making a real argument." |
| ), |
| judge_fragment=( |
| "This debater uses an unconventional, surprising approach. " |
| "Score primarily on whether the argument surprised and still convinced. " |
| "Ignore stylistic conventions β reward originality and the unexpected that still lands." |
| ), |
| ), |
| ] |
|
|
| |
| |
| |
| @dataclass |
| class Debater: |
| id: str |
| name: str |
| emoji: str |
| line: str |
| tag: str |
| stats: dict |
| type: str |
|
|
| @property |
| def archetype_type(self) -> str: |
| return self.type |
|
|
|
|
| DEBATERS = [ |
| Debater(id="firebrand", emoji="π₯", name="The Firebrand", |
| line="Loud, declarative, zero hedging. Every sentence lands like a statement of fact.", |
| tag="HIGH INTENSITY", |
| stats={"edge": 5, "precision": 2, "style": 3, "risk": 3}, type="firebrand"), |
| Debater(id="academic", emoji="π", name="The Academic", |
| line="Will make you feel slightly stupid. In a good way.", |
| tag="PRECISION", |
| stats={"edge": 2, "precision": 5, "style": 2, "risk": 1}, type="academic"), |
| Debater(id="everyman", emoji="π§’", name="The Everyman", |
| line="Argues like a normal person would. No theory, no tricks β just common sense.", |
| tag="BALANCED", |
| stats={"edge": 3, "precision": 3, "style": 3, "risk": 2}, type="everyman"), |
| Debater(id="contrarian", emoji="βοΈ", name="The Contrarian", |
| line="Always performs better arguing AGAINST. Choose her when you're taking the opposing side.", |
| tag="PLAYS AGAINST", |
| stats={"edge": 4, "precision": 2, "style": 3, "risk": 4}, type="contrarian"), |
| Debater(id="charmer", emoji="β¨", name="The Charmer", |
| line="Makes the judge feel like the argument was their idea.", |
| tag="JUDGE FAVOURITE", |
| stats={"edge": 3, "precision": 2, "style": 5, "risk": 2}, type="charmer"), |
| Debater(id="stoic", emoji="πΏ", name="The Stoic", |
| line="Never rattled, never rushed. Wins by staying completely calm while everyone else loses it.", |
| tag="UNSHAKEABLE", |
| stats={"edge": 2, "precision": 4, "style": 2, "risk": 1}, type="stoic"), |
| Debater(id="poet", emoji="ποΈ", name="The Poet", |
| line="Judges remember her long after the round ends.", |
| tag="MEMORABLE", |
| stats={"edge": 3, "precision": 2, "style": 5, "risk": 3}, type="poet"), |
| Debater(id="comedian", emoji="ποΈ", name="The Comedian", |
| line="Wins by making the judge laugh first and think second. High risk, genuinely unpredictable.", |
| tag="UNPREDICTABLE", |
| stats={"edge": 4, "precision": 1, "style": 4, "risk": 5}, type="comedian"), |
| Debater(id="pragmatist", emoji="βοΈ", name="The Pragmatist", |
| line="Goes straight to: what does this cost, what does this fix, what happens next.", |
| tag="RESULTS ONLY", |
| stats={"edge": 3, "precision": 4, "style": 2, "risk": 2}, type="pragmatist"), |
| Debater(id="wildcard", emoji="π", name="The Wildcard", |
| line="Nobody knows what she'll say next. Including her.", |
| tag="HIGH RISK", |
| stats={"edge": 3, "precision": 1, "style": 3, "risk": 5}, type="wildcard"), |
| ] |
|
|
| |
| |
| |
| @dataclass |
| class AbilityCard: |
| id: str |
| name: str |
| emoji: str |
| description: str |
| timing: str |
| uses: int |
| rarity_weight: int |
| requires_input: bool = False |
| input_prompt: str = "" |
|
|
|
|
| ABILITY_CARDS = [ |
| AbilityCard( |
| id="echo", |
| name="Echo", |
| emoji="π", |
| description="Reuse one word from a previous round in your selection this round.", |
| timing="pre-round", |
| uses=1, |
| rarity_weight=8, |
| ), |
| AbilityCard( |
| id="wildcard_card", |
| name="Wildcard", |
| emoji="π", |
| description="Add any word you choose to your pool for the rest of the game.", |
| timing="pre-round", |
| uses=1, |
| rarity_weight=4, |
| requires_input=True, |
| input_prompt="Enter your custom word:", |
| ), |
| AbilityCard( |
| id="plant", |
| name="Plant", |
| emoji="π±", |
| description="Force one word of your choosing into the opponent's 3 words this round.", |
| timing="pre-round", |
| uses=1, |
| rarity_weight=5, |
| requires_input=True, |
| input_prompt="Enter the word to plant in the opponent's argument:", |
| ), |
| AbilityCard( |
| id="silence", |
| name="Silence", |
| emoji="π€«", |
| description="Remove one word permanently from the opponent's pool. Once per game.", |
| timing="pre-round", |
| uses=1, |
| rarity_weight=4, |
| ), |
| AbilityCard( |
| id="appeal", |
| name="Appeal", |
| emoji="βοΈ", |
| description="After losing a round, request a fresh re-score. You might win β but if you do, you earn 0 points. A victory without honor. Once per game.", |
| timing="post-round", |
| uses=1, |
| rarity_weight=4, |
| ), |
| AbilityCard( |
| id="bias", |
| name="Bias", |
| emoji="π―", |
| description=( |
| "Steer the judge this round. Pick which argument style scores best: " |
| "logic, emotion, or creativity." |
| ), |
| timing="pre-round", |
| uses=1, |
| rarity_weight=6, |
| requires_input=True, |
| input_prompt="Pick argument style: logic / emotion / creativity", |
| ), |
| AbilityCard( |
| id="inspiration", |
| name="Inspiration", |
| emoji="π‘", |
| description=( |
| "Judge scores you generously this round β UNLESS the opponent argued first, " |
| "in which case your argument 'seems derivative' and scores you down." |
| ), |
| timing="pre-round", |
| uses=1, |
| rarity_weight=5, |
| ), |
| AbilityCard( |
| id="call_it", |
| name="Call It", |
| emoji="π", |
| description=( |
| "Write your own debate topic this round (20 word max). " |
| "Score 80 or higher and the round plays out normally. " |
| "Score under 80 and you automatically lose the round β " |
| "regardless of what the opponent scores." |
| ), |
| timing="pre-round", |
| uses=1, |
| rarity_weight=2, |
| ), |
| ] |
|
|
| |
| |
| |
| ABILITY_CARD_MAP = {card.id: card for card in ABILITY_CARDS} |
| ARCHETYPE_MAP = {a.id: a for a in ARCHETYPES} |
| DEBATER_MAP = {d.id: d for d in DEBATERS} |
| JUDGE_MAP = {j.id: j for j in JUDGES} |
|
|