Files
code_of_conquest_dnd/client/tests/unit/test_entities.gd
Phillip Tarrant a668cf98eb refactor(contract)!: class_id -> calling_id, and the log gains race_id
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
2026-07-12 20:27:56 -05:00

60 lines
2.1 KiB
GDScript

extends "res://addons/gut/test.gd"
const Ids = preload("res://scripts/ids.gd")
const LogPlayer = preload("res://scripts/canon_log/log_player.gd")
const LogLocation = preload("res://scripts/canon_log/log_location.gd")
const PartyMember = preload("res://scripts/canon_log/party_member.gd")
const Quest = preload("res://scripts/canon_log/quest.gd")
const Humiliation = preload("res://scripts/canon_log/humiliation.gd")
func test_ids_regex():
assert_true(Ids.is_valid("greywater_docks"))
assert_false(Ids.is_valid("Bad Id"))
assert_false(Ids.is_valid("has-hyphen"))
func test_log_player_carries_race_and_calling_and_no_luck_number():
var p := LogPlayer.new("Aldric", "beastfolk", "cutpurse", "Fortune spits on you")
var d := p.to_dict()
assert_eq(d["race_id"], "beastfolk")
assert_eq(d["calling_id"], "cutpurse")
assert_false("luck" in d, "numeric Luck never reaches the log (§7)")
assert_false("class_id" in d)
assert_eq(d.keys().size(), 4)
func test_log_player_rejects_a_dead_calling():
var p := LogPlayer.new()
assert_false(p.set_calling_id("priest"), "priest is not a calling")
assert_true(p.set_calling_id("bonesetter"))
assert_false(p.set_race_id("orc"))
assert_true(p.set_race_id("dwarf"))
func test_party_member_clamps_disposition():
var m = PartyMember.new("brannoc_thane", "Brannoc Thane", 200)
assert_eq(m.disposition, 100)
m.set_disposition(-200)
assert_eq(m.disposition, -100)
func test_quest_status_enum():
var q = Quest.new("find_the_ledger", "The Missing Ledger", "active", "Find it")
assert_false(q.set_status("abandoned"))
assert_eq(q.status, "active")
assert_true(q.set_status("complete"))
func test_humiliation_clamps_weight_and_turn():
var h = Humiliation.new("h_1", "vomited on a shrine", 11, -3)
assert_eq(h.weight, 10)
assert_eq(h.turn, 0)
func test_round_trip_each_entity():
var loc = LogLocation.new("greywater_docks", "the Greywater docks")
assert_eq(LogLocation.from_dict(loc.to_dict()).to_dict(), loc.to_dict())
var m = PartyMember.new("cadwyn_vell", "Cadwyn Vell", 15)
assert_eq(PartyMember.from_dict(m.to_dict()).to_dict(), m.to_dict())