extends SceneTree const Clock = preload("res://world/owniverse/simulation_time.gd") var checks: int = 0 var failures: int = 0 func _initialize() -> void: call_deferred("run") func check(condition: bool, message: String) -> void: checks += 1 if not condition: failures += 1 push_error(message) func fixture(): var world = load("res://world/owniverse/owniverse.gd").new() var global: Node = root.get_node("Global") for id in global.OwniverseEntities: var entity = load("res://world/owniverse/owniverse_entity.gd").new(global.OwniverseEntities[id]) world.entities[id] = entity world.state.amount[id] = 0.0 world.state.stats.total_amount[id] = 0.0 if entity.group_as != "": world.state.amount[entity.group_as] = 0.0 world.state.stats.total_amount[entity.group_as] = 0.0 world.entities["N"].life_product = {} world.entities["N"].product_per_year_type = "H" world.entities["N"].product_per_year_amount = 10.0 world.entities["N"].auto = 0.0 world.state.amount.N = 1.0 return world func dispose(world) -> void: for entity in world.entities.values(): entity.free() world.free() func seed_population(world, id: String, count: float, time: float = 1.0) -> void: world.state.amount.N = 0.0 world.state.amount[id] = count world.state.batches[id] = [{"amount": count, "start_year": time, "end_year": time}] world.initialize_life_batches() func sum_ethics(world, id: String) -> float: var result: float = 0.0 for n in world.state.ethics.get(id, {}).values(): result += n return result func invariants(world) -> void: var physical: Dictionary = world._physical_amounts(world.state) for id in world.state.ethics: check(sum_ethics(world, id) == physical.get(id, 0.0), "Ethics equal physical population for " + id) for n in world.state.ethics[id].values(): if n < 0.0 or n != floorf(n): check(false, "Invalid ethic amount") check(root.get_node("SaveManager").validate_data({"state": world.state}) == "", "Civilization state validates") func run() -> void: var global: Node = root.get_node("Global") var baseline: Dictionary = global.CivilizationRules.duplicate(true) var world = fixture() seed_population(world, "C0.2", 60000.0) world._evolve_batches(world.state, 1.0) check(world.state.amount["C0.3"] > 0.0, "C0.2 actually progresses") check(world.state.amount["C0.1"] > 0.0, "C0.2 actually degrades") check(world.state.civilization_stats.get("wars", 0.0) == 0.0, "Table disables early wars") check(world.state.amount["C0.2"] + world.state.amount["C0.3"] + world.state.amount["C0.1"] == 60000.0, "Ordinary evolution conserves population") check(world.state.stats.total_amount["C0.2"] == 0.0, "Survivors do not count as new civilizations") check(world.state.civilization_stats.worship > 0.0, "Worship derived from surviving ethics") invariants(world) var restored = fixture() restored.state = bytes_to_var(var_to_bytes(world.state)) world._evolve_batches(world.state, 2100.0) restored._evolve_batches(restored.state, 2100.0) check(world.state == restored.state, "Save resumes deterministic ethical evolution") invariants(world) dispose(restored) dispose(world) world = fixture() world.state.amount.RD = 1e8 seed_population(world, "C2.0", 60000.0) world.state.amount["C1.9"] = 10000.0 world.initialize_life_batches() world._evolve_batches(world.state, 1.0) check(world.state.civilization_stats.wars > 0.0, "Wars execute when enabled") check(world.state.civilization_stats.collateral > 0.0, "Wars affect previous-stage populations") check(world.state.amount["C2.1"] > 0.0, "Winners and peaceful progress reach next stage") invariants(world) dispose(world) world = fixture() world.state.amount.RD = 1.0 world.state.binds.RD = {"HP": 5.0, "GG": 5.0, "AB": 5.0, "RP": 5.0} seed_population(world, "C1.6", 60000.0) for row: Dictionary in global.CivilizationRules["C1.6"].ethics.values(): row.degradation = "0" world._evolve_batches(world.state, 1.0) var after: Dictionary = world._physical_amounts(world.state) check(world.state.civilization_stats.colonies == 20.0, "Colonization limited to real targets") check(after.HP + after.GG + after.AB + after.RP == 0.0, "Colonized objects consumed once") check(world.state.binds.RD.get("C1.4", 0.0) == 20.0, "Colonies retain target host and use two-stages-earlier level") var totals: float = 0.0 for id in after: if world.entities[id].group_as == "Planet": totals += after[id] check(totals == 60020.0, "Colonization replaces planets, does not create extra worlds") invariants(world) dispose(world) global.CivilizationRules = baseline.duplicate(true) world = fixture() world.state.amount.RD = 1e25 world.state.amount.BG = 1e25 world.state.amount.TWD = 1e25 world.state.amount.SNS = 1e25 world.state.amount.MGT = 1e25 world.state.amount.BH25 = 1e25 world.state.amount.SMBH = 1e26 for id in world.entities: if world.entities[id].claim != "": world.state.amount[id] = 1e12 # Accelerated deterministic fixture traverses every enabled stage, including Ct. for id in global.CivilizationRules: if id == "Ct": continue global.CivilizationRules[id].fights = false global.CivilizationRules[id].colonizes = false global.CivilizationRules[id].diplomacy = false for row: Dictionary in global.CivilizationRules[id].ethics.values(): row.progress = "1" row.degradation = "0" world.entities[id].lifespan = 1.0 world.entities[id].lifespan_delta = 1.0 global.CivilizationRules[id].span = 1.0 seed_population(world, "C0.2", 6.0) world._evolve_batches(world.state, 1000.0) check(world.state.amount.Ct == 6.0, "Complete C0.2 -> C4.4 -> Ct chain runs") check(not world.state.batches.has("Ct"), "Transcendence is terminal") invariants(world) dispose(world) global.CivilizationRules = baseline world = fixture() world.state.amount.RD = 10000.0 world.state.amount.SCS = 1.0 world.state.amount["C2.4"] = 1.0 world.initialize_life_batches() var span: float = world.state.batches["C2.4"][0].start_year check(is_equal_approx(span, 20000.0 / 3.6), "Structure bonus shortens newly registered lifespan") dispose(world) var evolution = load("res://world/owniverse/civilization_evolution.gd") var balanced: Dictionary = {"m": 10000.0, "l": 10000.0, "w": 10000.0, "d": 10000.0, "c": 10000.0, "b": 10000.0} var neutral: Dictionary = evolution.drift({}, "test", balanced, global.CivilizationRules["C2.0"].ethics) var diplomatic: Dictionary = evolution.drift({}, "test", balanced, global.CivilizationRules["C2.0"].ethics, true) check(neutral == balanced, "Ordinary drift preserves balanced ethics") check(diplomatic.d > diplomatic.w, "Diplomacy uses per-ethic influence") check(evolution.total(diplomatic) == 60000.0, "Diplomacy conserves population") world = fixture() world.state.amount.RD = 1e15 world.state.amount.BG = 1e15 world.state.amount["C3.0"] = 6.0 world.initialize_life_batches() check(world.state.amount["C3.0"] == 0.0 and world.state.amount["C2.9"] == 6.0, "Missing mandatory structures degrade existing civilizations") invariants(world) dispose(world) print("Civilization evolution: %d checks, %d failures" % [checks, failures]) quit(0 if failures == 0 else 1)