construct() takes a seed, not a stat block. It rebuilds the RNG and rolls the attributes itself, so it never trusts a number handed to it (§2) and the same seed yields an identical character, hidden Luck included (§10). The whole character is reproducible from four primitives: seed, race_id, calling_id, spend. The rng parameter is dropped — the seed is now the only source of randomness. The roll is 3d6 with a hard floor of 8. Straight 3d6 puts ~9% on a primary the +3 pool cannot rescue, and a character bad at the one thing he is FOR is not grit — it is the fine §7 forbids, paid for twenty hours. LCK is unspendable, not merely hidden: spending on it is a validation error. GameState.stats becomes GameState.sheet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
99 lines
2.5 KiB
GDScript
99 lines
2.5 KiB
GDScript
extends "res://addons/gut/test.gd"
|
|
|
|
const GameState = preload("res://scripts/state/game_state.gd")
|
|
|
|
|
|
func test_state_holds_a_sheet_not_a_stat_bag():
|
|
var gs = GameState.new()
|
|
assert_null(gs.sheet, "the sheet is built by NewGame.construct")
|
|
assert_false("stats" in gs, "the flat stats Dictionary is gone")
|
|
|
|
|
|
func test_add_item_accumulates():
|
|
var s = GameState.new()
|
|
s.add_item("worn_shortsword", 3)
|
|
s.add_item("worn_shortsword", 2)
|
|
assert_eq(s.inventory["worn_shortsword"], 5)
|
|
|
|
|
|
func test_npc_disposition_clamps():
|
|
var s = GameState.new()
|
|
s.set_npc_disposition("oda_fenn", 250)
|
|
assert_eq(s.npc_dispositions["oda_fenn"], 100)
|
|
|
|
|
|
func test_drift_luck_respects_base():
|
|
var s = GameState.new()
|
|
s.luck_base = 10
|
|
s.luck = 10
|
|
s.drift_luck(100)
|
|
assert_eq(s.luck, 13)
|
|
|
|
|
|
func test_luck_descriptor_delegates():
|
|
var s = GameState.new()
|
|
s.luck = 4
|
|
assert_eq(s.luck_descriptor(), "Fortune spits on you")
|
|
|
|
|
|
func test_remove_item_decrements_and_floors_at_zero():
|
|
var gs = GameState.new()
|
|
gs.add_item("worn_shortsword", 3)
|
|
gs.remove_item("worn_shortsword", 2)
|
|
assert_eq(gs.inventory.get("worn_shortsword", 0), 1)
|
|
gs.remove_item("worn_shortsword", 5)
|
|
assert_eq(gs.inventory.get("worn_shortsword", 0), 0)
|
|
|
|
|
|
func test_revealed_topics_tracked():
|
|
var gs = GameState.new()
|
|
assert_false(gs.is_revealed("varrell_twins"))
|
|
gs.mark_revealed("varrell_twins")
|
|
assert_true(gs.is_revealed("varrell_twins"))
|
|
|
|
|
|
func test_gifts_given_tracked():
|
|
var gs = GameState.new()
|
|
assert_false(gs.is_gift_given("amulet"))
|
|
gs.mark_gift_given("amulet")
|
|
assert_true(gs.is_gift_given("amulet"))
|
|
|
|
|
|
func test_purse_starts_empty():
|
|
assert_eq(GameState.new().purse_copper, 0)
|
|
|
|
|
|
func test_grant_currency_lands_in_the_purse_not_the_inventory():
|
|
var gs = GameState.new()
|
|
gs.grant("silver", 2)
|
|
assert_eq(gs.purse_copper, 200)
|
|
assert_eq(gs.inventory.size(), 0, "money is never an inventory row")
|
|
|
|
|
|
func test_grant_non_currency_lands_in_the_inventory():
|
|
var gs = GameState.new()
|
|
gs.grant("worn_shortsword", 1)
|
|
assert_eq(gs.inventory.get("worn_shortsword", 0), 1)
|
|
assert_eq(gs.purse_copper, 0)
|
|
|
|
|
|
func test_take_currency_spends_from_the_purse():
|
|
var gs = GameState.new()
|
|
gs.grant("gold", 1)
|
|
gs.take("copper", 5)
|
|
assert_eq(gs.purse_copper, 9995)
|
|
|
|
|
|
func test_take_currency_clamps_at_zero():
|
|
var gs = GameState.new()
|
|
gs.grant("copper", 47)
|
|
gs.take("gold", 1)
|
|
assert_eq(gs.purse_copper, 0, "the purse never goes negative")
|
|
|
|
|
|
func test_take_non_currency_removes_from_the_inventory():
|
|
var gs = GameState.new()
|
|
gs.grant("worn_shortsword", 2)
|
|
gs.take("worn_shortsword", 1)
|
|
assert_eq(gs.inventory.get("worn_shortsword", 0), 1)
|