extends SceneTree const E = preload("res://world/owniverse/math/exact.gd") const Civ = preload("res://world/owniverse/civilization_evolution.gd") const Q = preload("res://world/owniverse/quantity_math.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 run(): for count in [0, 1, 7, 999999999, E.of("1e40"), E.of("1e400")]: var carry: Dictionary = {} for round_index in 20: var weights: Dictionary = {"m": round_index + 1.0, "l": 0.0, "w": 9.0, "d": 4.0, "c": 137.0, "b": 2.0} var profile: Dictionary = Civ.State.normalize_profile(weights) var shares: Array = [] for ethic in Civ.ETHICS: shares.append(profile[ethic]) var result: Dictionary = Q.split_profile(carry, "profile", count, Civ.ETHICS, shares) check(E.eq(Q.sum(result), count), "Profile quotas conserve arbitrary-size whole totals") for value in result.values(): check(E.ge(value, 0) and E.eq(value, E.floor_value(value)), "Profile quotas stay nonnegative whole") for value in carry.values(): check(is_finite(value) and value >= -1.0 and value <= 1.0, "Signed profile carries stay bounded") check(absf(E.ratio_approx(E.of("1e400"), E.of("3e400")) - 1.0 / 3.0) < 1e-15, "Float profile ratio handles values beyond float range") for count in [1, 101, E.of("1e40"), E.of("1e400")]: var stocks: Dictionary = {"a": count, "b": E.add(count, 1), "c": 0} var total: Variant = Q.sum(stocks) for limit in [0, count, E.subtract(total, 1), total]: var allocated: Dictionary = Q.allocate_profiled(stocks, limit) check(E.eq(Q.sum(allocated), limit), "Profiled stock allocation retains the exact granted total") for id in stocks: check(E.ge(allocated[id], 0) and E.le(allocated[id], stocks[id]), "Profiled stock allocation respects every owner ceiling") for probability in [0.1, 1.0 / 3.0, 1.23456789]: var carries: Dictionary = {} var old_carry: Variant = 0 for iteration in 20: var expected: Variant = E.add(E.multiply(count, probability), old_carry) var whole: Variant = E.floor_value(expected) old_carry = E.maximum(0, E.subtract(expected, whole)) var actual: Variant = Q.accumulate_rate(carries, "rate", count, probability) check(E.eq(actual, whole), "Fast float carries preserve the whole result of rate accumulation") check(E.near(carries.rate, old_carry), "Fast float carries preserve the fractional remainder") 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.near(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.near(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) # Civilizations no longer trigger batch generations. Seed an actual # stellar completion so the selective-rounding path is exercised. world.state.batches.BG = [{"amount": world.state.amount.BG, "start_year": 1, "end_year": 1}] 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)