extends SceneTree const E = preload("res://world/owniverse/math/exact.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(legacy: bool): var world = load(("res://tests/fixtures/legacy_stellar_events.gd" if legacy else "res://world/owniverse/owniverse.gd")).new() for id in root.get_node("Global").OwniverseEntities: world.catalog.entities[id] = load("res://world/owniverse/owniverse_entity.gd").new(root.get_node("Global").OwniverseEntities[id]) world.state.amount[id] = 0 world.state.stats.total_amount[id] = 0 if E.ne(world.catalog.entities[id].group_as, ""): world.state.amount[world.catalog.entities[id].group_as] = 0 world.state.stats.total_amount[world.catalog.entities[id].group_as] = 0 return world func dispose(world) -> void: for entity in world.catalog.entities.values(): entity.free() world.free() func equivalent(a: Variant, b: Variant) -> bool: if ((a is Dictionary) and (b is Dictionary)): if E.ne(a.size(), b.size()): return false for key in a: if ((not b.has(key)) or (not equivalent(a[key], b[key]))): return false return true if ((a is Array) and (b is Array)): if E.ne(a.size(), b.size()): return false for i in a.size(): if (not equivalent(a[i], b[i])): return false return true if ((((a is float) or (a is int))) and (((b is float) or (b is int)))): return E.lt(E.absolute(E.subtract(E.of(a), E.of(b))), E.of("1e-6")) return E.eq(a, b) func run() -> void: for parent in ["LRG", "HRG", "RSG", "RHG"]: for year in [101, E.of("101.5"), E.of("104.75"), E.of("1767.3"), 3005]: var fast = fixture(false) var legacy = fixture(true) fast.state.amount[parent] = 20000 fast.state.amount.RD = 100000 fast.state.amount.HP = 50 fast.state.amount.Met = 100 fast.state.binds.RD = {"HP": 2000, "C0.1": 100} fast.state.binds[parent] = {"HP": 10} fast.state.batches[parent] = [{"amount": 20000, "start_year": E.of("100.3"), "end_year": E.of("3000.7")}] # Existing short-lived populations test integration with new arrivals. fast.state.amount.SN = 3 fast.batches.append_batch(fast.state, "SN", 3, E.of("100.5"), E.of("100.5")) if E.eq(year, E.of("104.75")): fast.state.energy = 7 legacy.state = fast.state.duplicate(true) fast.evolution.evolve_batches(fast.state, year) legacy.evolution.evolve_batches(legacy.state, year) for field in fast.state: check(equivalent(fast.state[field], legacy.state[field]), ("%s at %s: %s matches materialized events" % [parent, year, field])) for id in ["SN", "DSN", "BSN", "HN", "GRB", "SGRB", "LGRB", "HGRB"]: check(E.le(fast.state.batches.get(id, []).size(), 3), "Only surviving event windows remain") dispose(fast) dispose(legacy) var fast = fixture(false) var pending: Dictionary = {} fast.batches.append_batch(fast.state, "SN", 10000, E.of("0.25"), E.of("1667.25"), pending, E.of("1667.25")) check((E.le(fast.state.batches.SN.size(), 2) and E.eq(pending.SN.size(), 1)), "Completed windows aggregate without transient batch allocation") var total: Variant = pending.SN[0].amount for batch in fast.state.batches.SN: total = E.add(total, batch.amount) check(E.eq(total, 10000), "Direct completions plus survivors conserve event count") dispose(fast) # Sparse explosions: thousands of time windows but very few actual events. # Exact interval edges and partial final windows exercise the jump boundary. for count in [1, 3, 17]: for year in [500, 1001]: fast = fixture(false) var legacy = fixture(true) fast.state.amount.RHG = count fast.state.batches.RHG = [{"amount": count, "start_year": E.of("0.25"), "end_year": E.of("1000.25")}] legacy.state = fast.state.duplicate(true) fast.evolution.evolve_batches(fast.state, year) legacy.evolution.evolve_batches(legacy.state, year) for field in fast.state: check(equivalent(fast.state[field], legacy.state[field]), "Sparse %s at %s: %s" % [count, year, field]) dispose(fast) dispose(legacy) print(("Short-lived events: %d checks, %d failures" % [checks, failures])) quit((0 if E.eq(failures, 0) else 1))