From a7e153884f7e8b35d3310613e71ba9af38343a9e Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Sun, 12 Jul 2026 20:18:19 -0500 Subject: [PATCH] feat(content): calling + race blurbs, loaded by ContentDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hand-written like items/ — no build-tool namespace needed. Blurbs are content; mechanics are code, and a test asserts the blurb files carry no hit dice. The prose is placeholder; swapping it later touches no code, which is what stops the content BOM from blocking the engine. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8 --- client/scripts/content/content_db.gd | 8 ++++++ client/tests/unit/test_content_db.gd | 34 ++++++++++++++++++++++++++ content/world/callings/bloodsworn.json | 1 + content/world/callings/bonesetter.json | 1 + content/world/callings/cutpurse.json | 1 + content/world/callings/hedge_mage.json | 1 + content/world/callings/reaver.json | 1 + content/world/callings/sellsword.json | 1 + content/world/callings/trapper.json | 1 + content/world/races/beastfolk.json | 1 + content/world/races/dwarf.json | 1 + content/world/races/elf.json | 1 + content/world/races/human.json | 1 + 13 files changed, 53 insertions(+) create mode 100644 content/world/callings/bloodsworn.json create mode 100644 content/world/callings/bonesetter.json create mode 100644 content/world/callings/cutpurse.json create mode 100644 content/world/callings/hedge_mage.json create mode 100644 content/world/callings/reaver.json create mode 100644 content/world/callings/sellsword.json create mode 100644 content/world/callings/trapper.json create mode 100644 content/world/races/beastfolk.json create mode 100644 content/world/races/dwarf.json create mode 100644 content/world/races/elf.json create mode 100644 content/world/races/human.json diff --git a/client/scripts/content/content_db.gd b/client/scripts/content/content_db.gd index 6752e4c..732281c 100644 --- a/client/scripts/content/content_db.gd +++ b/client/scripts/content/content_db.gd @@ -8,6 +8,8 @@ var locations: Dictionary = {} var npcs: Dictionary = {} var quests: Dictionary = {} var items: Dictionary = {} +var callings: Dictionary = {} +var races: Dictionary = {} var canon_entities: Dictionary = {} var topics: Dictionary = {} @@ -31,6 +33,8 @@ func load_from(content_root: String) -> void: npcs = _load_dir(world.path_join("npcs")) quests = _load_dir(world.path_join("quests")) items = _load_dir(world.path_join("items")) + callings = _load_dir(world.path_join("callings")) + races = _load_dir(world.path_join("races")) canon_entities = _load_dir(world.path_join("canon")) topics = _load_dir(world.path_join("topics")) @@ -61,6 +65,10 @@ func has_location(id: String) -> bool: return locations.has(id) func has_npc(id: String) -> bool: return npcs.has(id) func has_quest(id: String) -> bool: return quests.has(id) func has_item(id: String) -> bool: return items.has(id) +func calling(id: String) -> Dictionary: return callings.get(id, {}) +func race(id: String) -> Dictionary: return races.get(id, {}) +func has_calling(id: String) -> bool: return callings.has(id) +func has_race(id: String) -> bool: return races.has(id) func canon(id: String) -> Dictionary: return canon_entities.get(id, {}) func topic(id: String) -> Dictionary: return topics.get(id, {}) func has_canon(id: String) -> bool: return canon_entities.has(id) diff --git a/client/tests/unit/test_content_db.gd b/client/tests/unit/test_content_db.gd index a7955b3..c6a1963 100644 --- a/client/tests/unit/test_content_db.gd +++ b/client/tests/unit/test_content_db.gd @@ -91,3 +91,37 @@ func test_no_loaded_topic_has_a_body(): func test_legacy_npcs_still_load(): assert_true(db.has_npc("fenn")) + + +func test_loads_calling_and_race_content(): + assert_true(db.has_calling("reaver")) + assert_true(db.has_calling("bloodsworn")) + assert_true(db.has_race("beastfolk")) + assert_eq(db.calling("reaver")["name"], "Reaver") + assert_eq(db.race("beastfolk")["name"], "Beastfolk") + + +func test_calling_content_matches_the_code_table(): + # The roster exists in code (Callings.IDS — mechanics are rules) and in content + # (the blurbs). Nothing at runtime reconciles them, so guard against drift. + var content_ids: Array = db.callings.keys() + content_ids.sort() + var code_ids: Array = Callings.IDS.duplicate() + code_ids.sort() + assert_eq(content_ids, code_ids) + + +func test_race_content_matches_the_code_table(): + var content_ids: Array = db.races.keys() + content_ids.sort() + var code_ids: Array = Races.IDS.duplicate() + code_ids.sort() + assert_eq(content_ids, code_ids) + + +func test_blurb_content_carries_no_mechanics(): + # Mechanics are code. A hit_die in a content file is a second source of truth. + for id in db.callings: + for banned in ["hit_die", "saves", "skill_pool", "skill_count", "armor", "talent"]: + assert_false(db.callings[id].has(banned), + "%s.json leaks the mechanic '%s' — that lives in Callings" % [id, banned]) diff --git a/content/world/callings/bloodsworn.json b/content/world/callings/bloodsworn.json new file mode 100644 index 0000000..ff30160 --- /dev/null +++ b/content/world/callings/bloodsworn.json @@ -0,0 +1 @@ +{ "id": "bloodsworn", "name": "Bloodsworn", "blurb": "You made a bargain with one of the Seven and it was accepted. The power is real. So is the ledger, and it is not settled." } diff --git a/content/world/callings/bonesetter.json b/content/world/callings/bonesetter.json new file mode 100644 index 0000000..0ad877f --- /dev/null +++ b/content/world/callings/bonesetter.json @@ -0,0 +1 @@ +{ "id": "bonesetter", "name": "Bonesetter", "blurb": "The Warden is distant and does not explain himself. Still — you set the bone, you say the words, and often enough the rot does not take. Often enough." } diff --git a/content/world/callings/cutpurse.json b/content/world/callings/cutpurse.json new file mode 100644 index 0000000..697a3f6 --- /dev/null +++ b/content/world/callings/cutpurse.json @@ -0,0 +1 @@ +{ "id": "cutpurse", "name": "Cutpurse", "blurb": "Purses, locks, confidences — you have taken all three. The trick was never the hands. It was knowing which pocket was worth it." } diff --git a/content/world/callings/hedge_mage.json b/content/world/callings/hedge_mage.json new file mode 100644 index 0000000..26ff24b --- /dev/null +++ b/content/world/callings/hedge_mage.json @@ -0,0 +1 @@ +{ "id": "hedge_mage", "name": "Hedge-Mage", "blurb": "No tower took you. What you know, you got from a book you should not have had and a teacher who is not alive to confirm it." } diff --git a/content/world/callings/reaver.json b/content/world/callings/reaver.json new file mode 100644 index 0000000..24b3fc2 --- /dev/null +++ b/content/world/callings/reaver.json @@ -0,0 +1 @@ +{ "id": "reaver", "name": "Reaver", "blurb": "They hire you when they want a door opened and do not care about the door. You do not wear plate. You have never needed it." } diff --git a/content/world/callings/sellsword.json b/content/world/callings/sellsword.json new file mode 100644 index 0000000..2aff2e5 --- /dev/null +++ b/content/world/callings/sellsword.json @@ -0,0 +1 @@ +{ "id": "sellsword", "name": "Sellsword", "blurb": "You fight for money. You are good at it, and the work has never once run out." } diff --git a/content/world/callings/trapper.json b/content/world/callings/trapper.json new file mode 100644 index 0000000..3031d0a --- /dev/null +++ b/content/world/callings/trapper.json @@ -0,0 +1 @@ +{ "id": "trapper", "name": "Trapper", "blurb": "You worked the treelines until the treelines ran out of anything worth taking. A snare does the waiting for you, and you have learned to wait." } diff --git a/content/world/races/beastfolk.json b/content/world/races/beastfolk.json new file mode 100644 index 0000000..4dde59a --- /dev/null +++ b/content/world/races/beastfolk.json @@ -0,0 +1 @@ +{ "id": "beastfolk", "name": "Beastfolk", "blurb": "Claws you did not ask for and a nose you cannot switch off. Every gate you pass, someone decides what you are before you speak." } diff --git a/content/world/races/dwarf.json b/content/world/races/dwarf.json new file mode 100644 index 0000000..1a5bc4b --- /dev/null +++ b/content/world/races/dwarf.json @@ -0,0 +1 @@ +{ "id": "dwarf", "name": "Dwarf", "blurb": "You have drunk worse and survived worse. The rot that takes other men tends to think better of it." } diff --git a/content/world/races/elf.json b/content/world/races/elf.json new file mode 100644 index 0000000..6755cb9 --- /dev/null +++ b/content/world/races/elf.json @@ -0,0 +1 @@ +{ "id": "elf", "name": "Elf", "blurb": "You see in the dark and you notice things. Neither has made you popular in a town that would rather not be noticed." } diff --git a/content/world/races/human.json b/content/world/races/human.json new file mode 100644 index 0000000..373054e --- /dev/null +++ b/content/world/races/human.json @@ -0,0 +1 @@ +{ "id": "human", "name": "Human", "blurb": "Short-lived, adaptable, everywhere. The Margreave is mostly yours, for whatever that has been worth." }