75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
validation.py — Narrative quality validation for The Chaos engine.
|
||
|
||
Standalone functions — no dependency on GameEngine.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from collections import Counter
|
||
|
||
from .llm import call_llm
|
||
|
||
|
||
def auto_prompt(book_log: str = "") -> str:
|
||
"""Fallback player prompt."""
|
||
return "**What do you do?**"
|
||
|
||
|
||
def validate_narrative(
|
||
book_log: str,
|
||
*,
|
||
model: str,
|
||
temperature: float,
|
||
timeout: int,
|
||
on_debug: callable = None,
|
||
) -> tuple[bool, str]:
|
||
"""Check if book_log is acceptable narrative. Returns (ok, reason)."""
|
||
lines = book_log.strip().split("\n")
|
||
if not lines:
|
||
return False, "Empty narrative"
|
||
|
||
common = Counter(lines).most_common(1)
|
||
if common and common[0][1] >= 5:
|
||
return False, f"Repetition: '{common[0][0][:60]}' ×{common[0][1]}"
|
||
|
||
mech_lines = [l for l in lines if re.match(
|
||
r'^\*\*(?:Roll|Damage|Success|Failure|Check|Save|Hit|Miss|'
|
||
r'Strenght|Dexterity|Willpower|STR|DEX|WIL|'
|
||
r'(?:[A-Z][a-z]+(?: \(\w+\))?:))',
|
||
l
|
||
)]
|
||
if mech_lines:
|
||
ratio = len(mech_lines) / len(lines)
|
||
if ratio > 0.3:
|
||
return False, f"Game mechanics dominate ({len(mech_lines)}/{len(lines)} lines)"
|
||
|
||
if re.search(r'```(?:tool|json)', book_log):
|
||
return False, "Contains unprocessed tool blocks"
|
||
|
||
prose = re.sub(r'[*_#>`~\-\d]', '', book_log).strip()
|
||
if len(prose) < 50:
|
||
return False, "Too short to be meaningful"
|
||
|
||
text = call_llm([
|
||
{"role": "user", "content":
|
||
f"Rate this RPG narrative quality 1-5.\n"
|
||
f"1 = unreadable (spam, repetition, pure mechanics, garbled)\n"
|
||
f"2 = poor (mostly mechanics, little story)\n"
|
||
f"3 = acceptable (some narrative but rough)\n"
|
||
f"4 = good (solid prose, minor issues)\n"
|
||
f"5 = excellent (vivid, engaging)\n"
|
||
f"Reply with ONLY a single digit 1-5.\n\n"
|
||
f"{book_log[:600]}"}
|
||
], model=model, temperature=temperature, timeout=timeout,
|
||
max_tokens=2, label="Narrative validation", on_debug=on_debug)
|
||
|
||
if text and text.strip().isdigit():
|
||
score = int(text.strip())
|
||
if score < 3:
|
||
return False, f"Quality score: {score}/5"
|
||
|
||
return True, ""
|