From a9d9eedb7e71a05a9a3615a493faa01f5360ecf7 Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Sun, 12 Jul 2026 18:36:08 -0500 Subject: [PATCH] feat(currency): copper/silver/gold replace the coin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three denominations are move tokens, not stored buckets — there is one integer, so change-making never arises. The deserter is down to 47c: under a silver, so he cannot cover a bed and the player sees it on turn one. A parity test guards the code-side ratios against the content-side ids. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8 --- client/scripts/newgame/new_game.gd | 5 +++-- client/tests/unit/test_content_db.gd | 19 ++++++++++++++++++- client/tests/unit/test_new_game.gd | 3 ++- content/origins/deserter.json | 2 +- content/world/items/coin.json | 1 - content/world/items/copper.json | 1 + content/world/items/gold.json | 1 + content/world/items/silver.json | 1 + 8 files changed, 27 insertions(+), 6 deletions(-) delete mode 100644 content/world/items/coin.json create mode 100644 content/world/items/copper.json create mode 100644 content/world/items/gold.json create mode 100644 content/world/items/silver.json diff --git a/client/scripts/newgame/new_game.gd b/client/scripts/newgame/new_game.gd index c06b196..3f52e46 100644 --- a/client/scripts/newgame/new_game.gd +++ b/client/scripts/newgame/new_game.gd @@ -61,9 +61,10 @@ static func construct(origin: Dictionary, world: ContentDB, creation: Dictionary var qd: Dictionary = world.quest(quest_id) log.active_quests.append(Quest.new(qd.get("id", ""), qd.get("name", ""), "active", qd.get("objective", ""))) - # inventory grants -> game state (NOT the log). Charter §2. + # inventory grants -> game state (NOT the log). Charter §2. grant() routes + # currency to the purse; everything else to the inventory dict. for grant in origin.get("inventory_grants", []): - state.add_item(grant.get("item_id", ""), int(grant.get("qty", 0))) + state.grant(grant.get("item_id", ""), int(grant.get("qty", 0))) return {"ok": true, "errors": [], "log": log, "state": state} diff --git a/client/tests/unit/test_content_db.gd b/client/tests/unit/test_content_db.gd index a1b3c42..a7955b3 100644 --- a/client/tests/unit/test_content_db.gd +++ b/client/tests/unit/test_content_db.gd @@ -18,7 +18,24 @@ func test_loads_world_ids(): assert_true(db.has_location("greywater_docks")) assert_true(db.has_quest("find_the_ledger")) assert_true(db.has_item("worn_shortsword")) - assert_true(db.has_item("coin")) + assert_true(db.has_item("copper")) + assert_true(db.has_item("silver")) + assert_true(db.has_item("gold")) + assert_false(db.has_item("coin"), "the undifferentiated coin is gone") + + +func test_currency_items_agree_with_the_code(): + # The denomination ids exist twice — in code (Currency.VALUES, because the + # ratios are rules) and in content (slot: "currency"). Guard the two against + # drifting apart, since nothing at runtime reconciles them. + var content_ids: Array = [] + for id in db.items: + if db.items[id].get("slot", "") == "currency": + content_ids.append(id) + content_ids.sort() + var code_ids: Array = Currency.VALUES.keys() + code_ids.sort() + assert_eq(content_ids, code_ids) func test_companions_are_brannoc_and_cadwyn(): diff --git a/client/tests/unit/test_new_game.gd b/client/tests/unit/test_new_game.gd index b529b1f..18049f4 100644 --- a/client/tests/unit/test_new_game.gd +++ b/client/tests/unit/test_new_game.gd @@ -40,8 +40,9 @@ func test_numeric_luck_absent_from_log(): func test_inventory_and_dispo_land_in_state_not_log(): var res := _build({"name": "Aldric", "class_id": "sellsword"}) - assert_eq(res["state"].inventory.get("coin", 0), 3) assert_eq(res["state"].inventory.get("worn_shortsword", 0), 1) + assert_eq(res["state"].purse_copper, 47, "the deserter is down to his last coin") + assert_false("copper" in res["state"].inventory, "money is never an inventory row") assert_false("inventory" in res["log"].to_dict()) diff --git a/content/origins/deserter.json b/content/origins/deserter.json index d7e0b47..f68cc1a 100644 --- a/content/origins/deserter.json +++ b/content/origins/deserter.json @@ -15,7 +15,7 @@ "disposition_overrides": { "brannoc_thane": 40, "cadwyn_vell": 15 }, "inventory_grants": [ { "item_id": "worn_shortsword", "qty": 1 }, - { "item_id": "coin", "qty": 3 } + { "item_id": "copper", "qty": 47 } ], "start_quest_id": "find_the_ledger", "build_constraints": { diff --git a/content/world/items/coin.json b/content/world/items/coin.json deleted file mode 100644 index eeb0ea3..0000000 --- a/content/world/items/coin.json +++ /dev/null @@ -1 +0,0 @@ -{ "id": "coin", "name": "coin", "slot": "currency" } diff --git a/content/world/items/copper.json b/content/world/items/copper.json new file mode 100644 index 0000000..9e4c32e --- /dev/null +++ b/content/world/items/copper.json @@ -0,0 +1 @@ +{ "id": "copper", "name": "copper coin", "slot": "currency" } diff --git a/content/world/items/gold.json b/content/world/items/gold.json new file mode 100644 index 0000000..1caff29 --- /dev/null +++ b/content/world/items/gold.json @@ -0,0 +1 @@ +{ "id": "gold", "name": "gold coin", "slot": "currency" } diff --git a/content/world/items/silver.json b/content/world/items/silver.json new file mode 100644 index 0000000..5f3d119 --- /dev/null +++ b/content/world/items/silver.json @@ -0,0 +1 @@ +{ "id": "silver", "name": "silver coin", "slot": "currency" }