class_id is the rulebook's word; the world has callings — that was the whole §17
reconcile. The contract the Narrator reads should say what the world says. There
are no saves (M9) and no deployed clients, so this is as cheap as it will ever be
and strictly more expensive every milestone after.
race_id reaches the log because the AI must be able to describe the player:
api/app/prompts.py rendered 'a sellsword' and now renders 'a beastfolk cutpurse'.
It humanizes the id (the model must never read 'hedge_mage') and picks the right
article ('an elf', not 'a elf').
LogPlayer's hardcoded CLASSES const is deleted — the roster is Callings.IDS. A
parity test reads the JSON Schema from the client and asserts its enum equals the
code, so the two sides of the HTTP boundary cannot drift.
The deserter allowed three callings that no longer exist. He now allows all seven;
a thematic gate is a content decision for the Greywater authoring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
58 lines
1.8 KiB
Python
58 lines
1.8 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import app.narrate as narrate
|
|
import app.npc as npc
|
|
|
|
VALID = json.loads((Path(__file__).parent / "fixtures" / "canon_log_valid.json").read_text())
|
|
|
|
|
|
@pytest.mark.live
|
|
def test_narrator_returns_real_prose():
|
|
"""Hits the real Ollama (OLLAMA_BASE_URL). Proves the wire end-to-end:
|
|
prompt assembly → model → non-empty prose. Not asserted: tag content
|
|
(nondeterministic). Run with: pytest --run-live
|
|
"""
|
|
text = narrate.run(VALID)
|
|
assert isinstance(text, str)
|
|
assert len(text.strip()) > 40 # real prose, not a blip
|
|
|
|
|
|
@pytest.mark.live
|
|
def test_live_npc_speak_fenn():
|
|
"""Hits the real Ollama (OLLAMA_BASE_URL). Proves the /npc/speak wire
|
|
end-to-end against the authored "fenn" NPC: persona + knowledge digest →
|
|
model → non-empty in-voice prose. Not asserted: tag content
|
|
(nondeterministic). Run with: pytest --run-live
|
|
"""
|
|
log = {
|
|
"player": {
|
|
"name": "Aldric",
|
|
"race_id": "human",
|
|
"calling_id": "sellsword",
|
|
"luck_descriptor": "the dice are kind",
|
|
},
|
|
"location": {"id": "greywater_docks", "name": "Greywater Docks"},
|
|
"party": [],
|
|
"recent_events": [],
|
|
"established_facts": [],
|
|
"active_quests": [],
|
|
"humiliations": [],
|
|
}
|
|
available = [
|
|
"offer_quest(find_the_ledger)",
|
|
"reveal(varrell_twins)",
|
|
"reveal(fenns_debt)",
|
|
"adjust_disposition",
|
|
"refuse",
|
|
"end_conversation",
|
|
"become_hostile",
|
|
]
|
|
prose = npc.run(log, "fenn", 0, available, "I heard you lost something. What happened?")
|
|
assert isinstance(prose, str)
|
|
assert len(prose.strip()) > 40 # real prose, not a blip
|
|
# No Luck numbers/mechanics leaked into NPC dialogue (§7).
|
|
assert "luck" not in prose.lower()
|