extends SceneTree const E = preload("res://world/owniverse/math/exact.gd") const Civ = preload("res://world/owniverse/civilization_evolution.gd") const Fixture = preload("res://tests/fixtures/civilization_benchmark.gd") var checks: int = 0 var failures: int = 0 func _initialize(): call_deferred("run") func check(value: bool, message: String): checks += 1 if not value: failures += 1 push_error(message) func reference_drift(target: Dictionary, pop: Dictionary, rows: Dictionary, diplomacy: bool, colony: bool) -> Dictionary: var weights: Dictionary = {} for other in Civ.ETHICS: weights[other] = 0 for ethic in pop: var contribution: Variant = E.multiply(pop[ethic], E.of(rows[ethic].diplomacy_rate) if diplomacy else 1) if E.eq(contribution, 0): continue for other in Civ.ETHICS: var weight: int = 9 if colony else 1 if other == ethic: weight = 960 if colony else 196 elif other == Civ.OPPOSITE[ethic]: weight = 4 if colony else 0 weights[other] = E.add(weights[other], E.multiply(contribution, weight)) if E.le(Civ.total(weights), 0): return pop.duplicate() return Civ.split(target, "test", Civ.total(pop), weights) func run(): var rows: Dictionary = {} for ethic in Civ.ETHICS: rows[ethic] = {"diplomacy_rate": "0.125" if ethic == "m" else "1.75"} for count in [0, 1, 7, 999999999, E.of("1e40")]: for diplomacy in [false, true]: for colony in [false, true]: var actual: Dictionary = {} var reference: Dictionary = {} for round_index in 5: var pop: Dictionary = {"m": count, "l": round_index, "w": 9, "d": 0, "c": 137, "b": 2} var result: Dictionary = Civ.drift(actual, "test", pop, rows, diplomacy, colony) if diplomacy: check(E.eq(result, reference_drift(reference, pop, rows, diplomacy, colony)), "Diplomatic drift preserves allocation") var carries_valid: bool = true for value in actual.get("civilization_remainders", {}).values(): carries_valid = carries_valid and E.ge(value, -1) and E.le(value, 1) check(carries_valid, "Diplomatic drift carries stay bounded") else: check(E.eq(Civ.total(result), Civ.total(pop)), "Integer flow drift conserves population") var valid_whole: bool = true for ethic in Civ.ETHICS: valid_whole = valid_whole and E.ge(result[ethic], 0) and E.eq(result[ethic], E.floor_value(result[ethic])) check(valid_whole, "Integer flow drift has nonnegative whole counts") var decimal_rows: Dictionary = {} for ethic in Civ.ETHICS: decimal_rows[ethic] = {"diplomacy_rate": "0.333333333333" if ethic == "m" else "0.017"} var decimal_target: Dictionary = {} for round_index in 20: var pop: Dictionary = {"m": E.of("1e40"), "l": round_index, "w": 17, "d": 4, "c": 91, "b": 2} var result: Dictionary = Civ.drift(decimal_target, "decimal", pop, decimal_rows, true) check(E.eq(Civ.total(result), Civ.total(pop)), "Decimal diplomatic drift preserves whole population") for ethic in result: check(E.ge(result[ethic], 0) and E.eq(result[ethic], E.floor_value(result[ethic])), "Decimal drift returns nonnegative whole counts") for speed in [1, 4, 6]: var world = load("res://world/owniverse/owniverse.gd").new() world.catalog.load_definitions(root.get_node("Global").OwniverseEntities) Fixture.seed(world, root.get_node("Global"), true, true) var rules: Dictionary = root.get_node("Global").CivilizationRules var id: String = rules.keys()[0] var modifiers: Dictionary = {"progress": 1, "degradation": 1, "war": 1, "colonization": 1} var original: Dictionary = Civ.coefficients(world, rules, id, modifiers).duplicate(true) modifiers.progress = 2 var changed: Dictionary = Civ.coefficients(world, rules, id, modifiers) var row: Dictionary = rules[id].ethics.m var p: Variant = E.maximum(0, E.multiply(E.of(row.progress), 2)) var d: Variant = E.maximum(0, E.of(row.degradation)) check(E.eq(changed.m.pro, E.divide(p, E.maximum(1, E.add(p, d)))), "Coefficient cache follows modifiers") modifiers.progress = 1 check(E.eq(Civ.coefficients(world, rules, id, modifiers), original), "Coefficient cache restores original rates") var local_rules: Dictionary = rules.duplicate(true) local_rules[id].ethics.m.progress = "0.375" changed = Civ.coefficients(world, local_rules, id, modifiers) check(E.eq(changed.m.pro, E.divide(E.of("0.375"), E.maximum(1, E.add(E.of("0.375"), d)))), "Coefficient cache follows rule edits") var target: Dictionary = {"ethics": {}} target.amount = {} target.binds = {} target.stats = {"total_amount": {}} target.amount[id] = 60 target.stats.total_amount[id] = 60 Civ.State.reconcile(world, target, rules, {id: 60}) target.ethics[id] = {"m": 1.0, "l": 0.0, "w": 0.0, "d": 0.0, "c": 0.0, "b": 0.0} Civ.State.reconcile(world, target, rules, {id: 60}) check(E.eq(target.ethics[id].m, 1.0), "Reconcile cache detects changed ethics") Civ.State.reconcile(world, target, rules, {id: 120}) check(E.eq(target.ethics[id].m, 1.0), "Reconcile cache detects changed population") target.amount[id] = 120 Civ.worship(world, target, rules) target.ethics[id].m = 0.5 target.ethics[id].l = 0.5 var expected: float = 0.0 for stage in rules: if rules[stage].worships: for ethic in target.ethics[stage]: var n: float = (0.0 + target.ethics[stage][ethic]) * E.approx(E.of(rules[stage].ethics[ethic].worship)) n *= E.approx(target.amount.get(stage, 0)) expected += n check(E.near(Civ.worship(world, target, rules), expected), "Worship cache detects changed ethics") var worship_rules: Dictionary = rules.duplicate(true) for stage in worship_rules: if worship_rules[stage].worships: worship_rules[stage].ethics.m.worship = "0.375" expected = 0.0 for stage in worship_rules: if worship_rules[stage].worships: for ethic in target.ethics[stage]: var n: float = (0.0 + target.ethics[stage][ethic]) * E.approx(E.of(worship_rules[stage].ethics[ethic].worship)) n *= E.approx(target.amount.get(stage, 0)) expected += n check(E.near(Civ.worship(world, target, worship_rules), expected), "Worship cache detects changed rules") world.ledger = load("res://tests/fixtures/audit_rounding_ledger.gd").new(world) world.speed = speed for i in 3: world.evolute(1.0 if speed == 1 else 0.1) check(world.ledger.checks > 0 and world.ledger.failures == 0, "Selective and full rounding agree at speed %d" % speed) for entity in world.catalog.entities.values(): entity.free() world.free() print("Simulation optimizations: %d checks, %d failures" % [checks, failures]) quit(1 if failures else 0)