extends "res://addons/gut/test.gd" const ContentDB = preload("res://scripts/content/content_db.gd") const NewGame = preload("res://scripts/newgame/new_game.gd") var world func before_each(): world = ContentDB.new() world.load_from(ContentDB.default_content_root()) func _deserter() -> Dictionary: return ContentDB.load_json(ContentDB.origin_path("deserter")) func _draft() -> CreationDraft: # A fixed seed, so every assertion below is reproducible. var d := CreationDraft.new() d.character_seed = 8675309 d.set_race("human") d.set_calling("sellsword") d.name = "Aldric" return d func test_a_fresh_draft_has_a_seed_and_no_choices(): var d := CreationDraft.fresh() assert_ne(d.character_seed, 0, "randomize() gave us the one bit of non-determinism there is") assert_eq(d.spend, {}) assert_eq(d.skills, []) assert_eq(d.bonus_skill, "") func test_rolled_is_exactly_what_new_game_will_roll(): var d := _draft() assert_eq(d.rolled(), NewGame.roll_attributes(d.character_seed), "the screen and construct must reach the same five numbers") func test_final_is_rolled_plus_spend(): var d := _draft() d.increment("str") d.increment("str") assert_eq(int(d.final()["str"]), int(d.rolled()["str"]) + 2) assert_eq(int(d.final()["dex"]), int(d.rolled()["dex"]), "an unspent stat is untouched") func test_the_pool_is_three_and_the_fourth_point_does_not_exist(): var d := _draft() assert_eq(d.points_left(), 3) d.increment("str"); d.increment("dex"); d.increment("con") assert_eq(d.points_left(), 0) assert_false(d.can_increment("fth")) d.increment("fth") assert_eq(int(d.final()["fth"]), int(d.rolled()["fth"]), "the fourth point bought nothing") assert_eq(d.spent_total(), 3) func test_minus_removes_only_what_you_spent_and_floors_at_the_roll(): var d := _draft() d.increment("str") assert_true(d.can_decrement("str")) d.decrement("str") assert_eq(d.points_left(), 3) assert_false(d.can_decrement("str"), "you cannot go below the roll — additive only") d.decrement("str") assert_eq(int(d.final()["str"]), int(d.rolled()["str"])) func test_reroll_changes_the_numbers_and_clears_the_spend_and_nothing_else(): var d := _draft() d.set_calling("cutpurse") d.toggle_skill("stealth") d.increment("dex") var before := d.character_seed d.reroll() assert_ne(d.character_seed, before, "a re-roll IS a new seed — nothing else") assert_eq(d.spend, {}, "points spent against a roll that no longer exists are meaningless") assert_eq(d.calling_id, "cutpurse", "the calling survives") assert_eq(d.skills, ["stealth"], "the picks survive") assert_eq(d.name, "Aldric", "the name survives") func test_changing_calling_clears_the_picks(): var d := _draft() d.toggle_skill("athletics") assert_eq(d.skills, ["athletics"]) d.set_calling("hedge_mage") assert_eq(d.skills, [], "the pool changed underneath them — stale picks are an invalid draft the player did not cause") func test_changing_race_drops_a_pick_the_new_race_now_grants(): # The elf/cutpurse case. An Elf is GRANTED perception; a Cutpurse who had # picked it now holds a pick that "buys nothing" and construct rejects by name. var d := _draft() d.set_calling("cutpurse") d.toggle_skill("perception") assert_eq(d.skills, ["perception"]) d.set_race("elf") assert_eq(d.skills, [], "the elf already has perception — the pick is dropped, not left to 422") func test_changing_race_off_human_clears_the_bonus_skill(): var d := _draft() d.set_bonus_skill("stealth") assert_eq(d.bonus_skill, "stealth") d.set_race("dwarf") assert_eq(d.bonus_skill, "", "only a human gets a bonus skill") func test_a_granted_skill_cannot_be_picked(): var d := _draft() d.set_race("elf") d.set_calling("cutpurse") assert_true(d.is_granted("perception")) assert_false(d.can_pick("perception")) d.toggle_skill("perception") assert_eq(d.skills, [], "the chip is inert, not merely styled to look inert") func test_picks_stop_at_the_calling_s_count(): var d := _draft() d.set_calling("sellsword") # picks 2 d.toggle_skill("athletics") d.toggle_skill("endurance") assert_eq(d.picks_left(), 0) d.toggle_skill("perception") assert_eq(d.skills, ["athletics", "endurance"], "the third pick does not exist") func test_toggling_an_already_picked_skill_removes_it(): var d := _draft() d.toggle_skill("athletics") d.toggle_skill("athletics") assert_eq(d.skills, []) func test_to_creation_round_trips_through_construct(): var d := _draft() d.toggle_skill("athletics") d.toggle_skill("endurance") d.set_bonus_skill("perception") d.increment("str") var res := NewGame.construct(_deserter(), world, d.to_creation()) assert_true(res["ok"], str(res["errors"])) assert_eq(res["state"].sheet.attributes, d.final(), "TRAP 1: what the screen SHOWED is what the player GOT — asserted across the pipeline") func test_errors_are_construct_s_errors_not_a_second_copy_of_the_rules(): var d := _draft() # no skills picked yet assert_gt(d.errors(_deserter(), world).size(), 0) assert_false(d.is_complete(_deserter(), world)) d.toggle_skill("athletics") d.toggle_skill("endurance") d.set_bonus_skill("perception") assert_eq(d.errors(_deserter(), world), [], "a legal draft has nothing wrong with it") assert_true(d.is_complete(_deserter(), world)) func test_an_unnamed_draft_is_not_complete(): var d := _draft() d.toggle_skill("athletics") d.toggle_skill("endurance") d.set_bonus_skill("perception") d.name = " " assert_false(d.is_complete(_deserter(), world), "a nameless character cannot enter the world") func test_the_draft_never_exposes_luck(): # §7 — no accessor, no field, no leak. The screen cannot show what it cannot reach. var d := _draft() assert_false("lck" in d.to_creation().get("spend", {})) assert_false(d.to_creation().has("luck")) assert_false(d.final().has("lck")) assert_false(d.rolled().has("lck"))