class_name P93CoreLoopState extends RefCounted enum Phase { TITLE, HALL, ENCOUNTER, BATTLE, RESULT } var revision := 0 var phase := Phase.TITLE var coin := 24 var company: Array[String] = ["Hearthguard"] var available_offers: Array[String] = ["Wayrunner", "Farstring", "Kindhand"] var focused_offer := "" var hired_member := "" var selected_encounter := "" var reward_committed := false var last_reward := 0 var last_outcome := "" var hire_receipts: Array[Dictionary] = [] var reward_receipts: Array[Dictionary] = [] var focused_equipment := "" var equipped_items: Dictionary = {"Hearthguard": ""} var equipment_receipts: Array[Dictionary] = [] var member_levels: Dictionary = {"Hearthguard": 1} var member_xp: Dictionary = {"Hearthguard": 0} var xp_receipts: Array[Dictionary] = [] func reset() -> void: revision = 0 phase = Phase.TITLE coin = 24 company.assign(["Hearthguard"]) available_offers.assign(["Wayrunner", "Farstring", "Kindhand"]) focused_offer = "" hired_member = "" selected_encounter = "" reward_committed = false last_reward = 0 last_outcome = "" hire_receipts.clear() reward_receipts.clear() focused_equipment = "" equipped_items = {"Hearthguard": ""} equipment_receipts.clear() member_levels = {"Hearthguard": 1} member_xp = {"Hearthguard": 0} xp_receipts.clear() func snapshot() -> Dictionary: return { "revision": revision, "phase": phase, "coin": coin, "company": company.duplicate(), "capacity": 10, "available_offers": available_offers.duplicate(), "focused_offer": focused_offer, "hired_member": hired_member, "selected_encounter": selected_encounter, "reward_committed": reward_committed, "last_reward": last_reward, "last_outcome": last_outcome, "hire_receipts": hire_receipts.duplicate(true), "reward_receipts": reward_receipts.duplicate(true), "focused_equipment": focused_equipment, "equipped_items": equipped_items.duplicate(true), "equipment_receipts": equipment_receipts.duplicate(true), "member_levels": member_levels.duplicate(true), "member_xp": member_xp.duplicate(true), "xp_receipts": xp_receipts.duplicate(true), }.duplicate(true) func invariants_hold() -> bool: if revision < 0 or coin < 0: return false if phase < Phase.TITLE or phase > Phase.RESULT: return false if company.is_empty() or company.size() > 10: return false var seen := {} for member_id in company: if member_id not in ["Hearthguard", "Wayrunner", "Farstring", "Kindhand"] or seen.has(member_id): return false seen[member_id] = true if not equipped_items.has(member_id) or String(equipped_items[member_id]) not in ["", "EQ-002", "EQ-012"]: return false if not member_levels.has(member_id) or int(member_levels[member_id]) < 1 or int(member_levels[member_id]) > 10: return false if not member_xp.has(member_id) or int(member_xp[member_id]) < 0: return false var level := int(member_levels[member_id]) if level < 10 and int(member_xp[member_id]) >= 20 + 10 * level: return false for member_id in equipped_items: if member_id not in company: return false for member_id in member_levels: if member_id not in company: return false for member_id in member_xp: if member_id not in company: return false if not focused_equipment.is_empty() and focused_equipment not in ["EQ-002", "EQ-012"]: return false var owned_items := {} for item_id in equipped_items.values(): if String(item_id).is_empty(): continue if owned_items.has(item_id): return false owned_items[item_id] = true for offer_id in available_offers: if seen.has(offer_id): return false seen[offer_id] = true if not focused_offer.is_empty() and focused_offer not in available_offers: return false if not selected_encounter.is_empty() and selected_encounter not in ["open_road", "crosswind", "bell_breach"]: return false if reward_committed and last_outcome != "victory": return false return true