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
48 lines
1.6 KiB
GDScript
48 lines
1.6 KiB
GDScript
extends "res://addons/gut/test.gd"
|
|
|
|
const NewGame = preload("res://scripts/newgame/new_game.gd")
|
|
const ContentDB = preload("res://scripts/content/content_db.gd")
|
|
const CanonLog = preload("res://scripts/canon_log/canon_log.gd")
|
|
|
|
|
|
func _built_log():
|
|
var world = ContentDB.new()
|
|
world.load_from(ContentDB.default_content_root())
|
|
var origin: Dictionary = ContentDB.load_json(ContentDB.origin_path("deserter"))
|
|
var creation := {
|
|
"name": "Aldric",
|
|
"race_id": "human",
|
|
"calling_id": "bonesetter",
|
|
"seed": 3,
|
|
"spend": {},
|
|
"skills": ["faith_lore", "perception"],
|
|
"bonus_skill": "endurance",
|
|
}
|
|
var res := NewGame.construct(origin, world, creation)
|
|
assert_true(res["ok"], str(res["errors"]))
|
|
return res["log"]
|
|
|
|
|
|
func test_to_from_dict_is_identity():
|
|
var log = _built_log()
|
|
var d1: Dictionary = log.to_dict()
|
|
var log2 = CanonLog.from_dict(d1)
|
|
assert_not_null(log2)
|
|
assert_eq(log2.to_dict(), d1)
|
|
|
|
|
|
func test_constructed_log_satisfies_contract_invariants():
|
|
var d: Dictionary = _built_log().to_dict()
|
|
assert_eq(d["schema_version"], 1)
|
|
assert_true(d["recent_events"].size() <= 5)
|
|
assert_eq(d["player"].keys().size(), 4) # exactly name/race_id/calling_id/luck_descriptor
|
|
assert_false("luck" in d["player"]) # §7: no numeric luck
|
|
assert_true(d["player"]["calling_id"] in Callings.IDS)
|
|
assert_true(d["player"]["race_id"] in Races.IDS)
|
|
for m in d["party"]:
|
|
assert_true(m["disposition"] >= -100 and m["disposition"] <= 100)
|
|
for q in d["active_quests"]:
|
|
assert_true(q["status"] in ["active", "complete", "failed"])
|
|
for h in d["humiliations"]:
|
|
assert_true(h["weight"] >= 1 and h["weight"] <= 10)
|