extends SceneTree const E = preload("res://world/owniverse/math/exact.gd") 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 = E.add(checks, 1) if (not condition): failures = E.add(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.catalog.entities[id] = entity world.state.amount[id] = 0 world.state.stats.total_amount[id] = 0 if E.ne(entity.group_as, ""): world.state.amount[entity.group_as] = 0 world.state.stats.total_amount[entity.group_as] = 0 world.catalog.entities["N"].life_product = {} world.catalog.entities["N"].product_per_year_type = "H" world.catalog.entities["N"].product_per_year_amount = 10 world.catalog.entities["N"].auto = 0 world.state.amount.N = 1 return world func dispose(world) -> void: for entity in world.catalog.entities.values(): entity.free() world.free() func seed_population(world, id: String, count: Variant, time: Variant = 1.0) -> void: world.state.amount.N = 0 world.state.amount[id] = count world.state.batches[id] = [{"amount": count, "start_year": time, "end_year": time}] world.evolution.initialize_life_batches() func sum_ethics(world, id: String) -> Variant: var result: Variant = 0 for n in world.state.ethics.get(id, {}).values(): result = E.add(result, n) return result func invariants(world) -> void: var physical: Dictionary = world.ledger.physical_amounts(world.state) for id in world.state.ethics: check(E.eq(sum_ethics(world, id), physical.get(id, 0)), E.add("Ethics equal physical population for ", id)) for n in world.state.ethics[id].values(): if (E.lt(n, 0) or E.ne(n, E.floor_value(n))): check(false, "Invalid ethic amount") check(E.eq(root.get_node("SaveManager").validate_data({"format_version": 3, "app_version": "", "ui": {"current_page": 0}, "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) world.evolution.evolve_batches(world.state, 1) check(E.gt(world.state.amount["C0.3"], 0), "C0.2 actually progresses") check(E.gt(world.state.amount["C0.1"], 0), "C0.2 actually degrades") check(E.eq(world.state.civilization_stats.get("wars", 0), 0), "Table disables early wars") check(E.eq(E.add(E.add(world.state.amount["C0.2"], world.state.amount["C0.3"]), world.state.amount["C0.1"]), 60000), "Ordinary evolution conserves population") check(E.eq(world.state.stats.total_amount["C0.2"], 0), "Survivors do not count as new civilizations") check(E.gt(world.state.civilization_stats.worship, 0), "Worship derived from surviving ethics") invariants(world) var restored = fixture() restored.state = E.decode(bytes_to_var(var_to_bytes(E.encode(world.state)))) world.evolution.evolve_batches(world.state, 2100) restored.evolution.evolve_batches(restored.state, 2100) check(E.eq(world.state, restored.state), "Save resumes deterministic ethical evolution") invariants(world) dispose(restored) dispose(world) world = fixture() world.state.amount.RD = 100000000 seed_population(world, "C2.0", 60000) world.state.amount["C1.9"] = 10000 world.evolution.initialize_life_batches() world.evolution.evolve_batches(world.state, 1) check(E.gt(world.state.civilization_stats.wars, 0), "Wars execute when enabled") check(E.gt(world.state.civilization_stats.collateral, 0), "Wars affect previous-stage populations") check(E.gt(world.state.amount["C2.1"], 0), "Winners and peaceful progress reach next stage") invariants(world) dispose(world) world = fixture() world.state.amount.RD = 1 world.state.binds.RD = {"HP": 5, "GG": 5, "AB": 5, "RP": 5} seed_population(world, "C1.6", 60000) for row : Dictionary in global.CivilizationRules["C1.6"].ethics.values(): row.degradation = "0" world.evolution.evolve_batches(world.state, 1) var after: Dictionary = world.ledger.physical_amounts(world.state) check(E.eq(world.state.civilization_stats.colonies, 20), "Colonization limited to real targets") check(E.eq(E.add(E.add(E.add(after.HP, after.GG), after.AB), after.RP), 0), "Colonized objects consumed once") check(E.eq(world.state.binds.RD.get("C1.4", 0), 20), "Colonies retain target host and use two-stages-earlier level") var totals: Variant = 0 for id in after: if E.eq(world.catalog.entities[id].group_as, "Planet"): totals = E.add(totals, after[id]) check(E.eq(totals, 60020), "Colonization replaces planets, does not create extra worlds") invariants(world) dispose(world) global.CivilizationRules = baseline.duplicate(true) world = fixture() world.state.amount.RD = E.of("1e25") world.state.amount.BG = E.of("1e25") world.state.amount.TWD = E.of("1e25") world.state.amount.SNS = E.of("1e25") world.state.amount.MGT = E.of("1e25") world.state.amount.BH25 = E.of("1e25") world.state.amount.SMBH = E.of("1e26") for id in world.catalog.entities: if E.ne(world.catalog.entities[id].claim, ""): world.state.amount[id] = E.of("1e12") # Accelerated deterministic fixture traverses every enabled stage, including Ct. for id in global.CivilizationRules: if E.eq(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.catalog.entities[id].lifespan = 1 world.catalog.entities[id].lifespan_delta = 1 global.CivilizationRules[id].span = 1 seed_population(world, "C0.2", 6) world.evolution.evolve_batches(world.state, 1000) check(E.eq(world.state.amount.Ct, 6), "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 world.state.amount.SCS = 1 world.state.amount["C2.4"] = 1 world.evolution.initialize_life_batches() var span: Variant = world.state.batches["C2.4"][0].start_year check(E.near(span, E.divide(20000, E.of("3.6"))), "Structure bonus shortens newly registered lifespan") dispose(world) var evolution = load("res://world/owniverse/civilization_evolution.gd") var balanced: Dictionary = {"m": 10000, "l": 10000, "w": 10000, "d": 10000, "c": 10000, "b": 10000} 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(E.eq(neutral, balanced), "Ordinary drift preserves balanced ethics") check(E.gt(diplomatic.d, diplomatic.w), "Diplomacy uses per-ethic influence") check(E.eq(evolution.total(diplomatic), 60000), "Diplomacy conserves population") world = fixture() world.state.amount.RD = E.of("1e15") world.state.amount.BG = E.of("1e15") world.state.amount["C3.0"] = 6 world.evolution.initialize_life_batches() check((E.eq(world.state.amount["C3.0"], 0) and E.eq(world.state.amount["C2.9"], 6)), "Missing mandatory structures degrade existing civilizations") invariants(world) dispose(world) print(("Civilization evolution: %d checks, %d failures" % [checks, failures])) quit((0 if E.eq(failures, 0) else 1))