extends GutTest func test_explicit_job_order_is_due_phase_entity_sequence() -> void: var scheduler := RegionalJobScheduler.new() assert_true(scheduler.schedule(ScheduledJobRecord.create(&"z", &"work", 8, 0, &"b", 2))) assert_true(scheduler.schedule(ScheduledJobRecord.create(&"d", &"work", 7, 2, &"a", 3))) assert_true(scheduler.schedule(ScheduledJobRecord.create(&"c", &"work", 7, 1, &"z", 1))) assert_true(scheduler.schedule(ScheduledJobRecord.create(&"b", &"work", 7, 1, &"a", 9))) assert_true(scheduler.schedule(ScheduledJobRecord.create(&"a", &"work", 7, 1, &"a", 4))) assert_eq(_job_ids(scheduler.get_all_sorted()), [&"a", &"b", &"c", &"d", &"z"]) assert_eq(scheduler.get_due_count(6), 0) assert_eq(scheduler.get_due_count(7), 4) assert_eq(_job_ids(scheduler.take_due(7, 2)), [&"a", &"b"]) assert_eq(_job_ids(scheduler.take_due(7)), [&"c", &"d"]) assert_eq(_job_ids(scheduler.take_due(8)), [&"z"]) assert_true(scheduler.is_empty()) func test_budgets_one_eight_and_unlimited_preserve_order_and_checksum() -> void: var budget_one := _run_fixture(1) var budget_eight := _run_fixture(8) var unlimited := _run_fixture(RegionalJobScheduler.UNLIMITED_BUDGET) assert_eq(budget_one["order"], budget_eight["order"]) assert_eq(budget_one["order"], unlimited["order"]) assert_eq(budget_one["checksum"], budget_eight["checksum"]) assert_eq(budget_one["checksum"], unlimited["checksum"]) assert_eq(budget_one["order"].size(), 37) assert_eq(budget_one["remaining"], 0) assert_eq(budget_eight["remaining"], 0) assert_eq(unlimited["remaining"], 0) func test_dynamic_jobs_interleave_identically_across_execution_budgets() -> void: var budget_one := _run_dynamic_fixture(1) var budget_eight := _run_dynamic_fixture(8) var unlimited := _run_dynamic_fixture(RegionalJobScheduler.UNLIMITED_BUDGET) assert_eq(budget_one, [&"parent", &"child", &"tail"]) assert_eq(budget_eight, budget_one) assert_eq(unlimited, budget_one) func test_restore_mid_due_queue_continues_without_drop_or_reorder() -> void: var uninterrupted := _fixture_scheduler() var expected: Array[StringName] = [] while not uninterrupted.is_empty(): expected.append_array(_job_ids(uninterrupted.take_due(100, 3))) var interrupted := _fixture_scheduler() var actual := _job_ids(interrupted.take_due(100, 5)) var saved := interrupted.to_dictionary() var restored := RegionalJobScheduler.from_dictionary(saved) assert_not_null(restored) assert_eq(restored.to_dictionary(), saved) while not restored.is_empty(): actual.append_array(_job_ids(restored.take_due(100, 2))) assert_eq(actual, expected) assert_eq(actual.size(), 37) assert_eq(actual.duplicate().reduce(_unique_id_count, {} as Dictionary).size(), 37) assert_eq(restored.get_cursor()["executed_count"], 37) func test_schedule_cancel_dedupe_and_repeating_jobs_preserve_queue_contract() -> void: var scheduler := RegionalJobScheduler.new() var repeating := scheduler.schedule_new( &"weather#0", &"weather", 2, 0, &"jajce", {}, &"weather:jajce", 3 ) assert_not_null(repeating) assert_null( scheduler.schedule_new(&"duplicate", &"weather", 2, 0, &"jajce", {}, &"weather:jajce", 3) ) assert_eq(_job_ids(scheduler.take_due(2, 1)), [&"weather#0"]) assert_true(scheduler.has_job(&"weather#1")) assert_eq(scheduler.get_by_id(&"weather#1").get_due_tick(), 5) assert_eq(scheduler.get_by_id(&"weather#1").get_occurrence(), 1) assert_eq(_job_ids(scheduler.take_due(100, 2)), [&"weather#1", &"weather#2"]) assert_true(scheduler.has_job(&"weather#3")) assert_not_null(scheduler.cancel(&"weather#3")) assert_true(scheduler.is_empty()) assert_null(scheduler.cancel(&"missing")) var duplicate_sequence := ScheduledJobRecord.create(&"one", &"work", 9, 0, &"a", 40) assert_true(scheduler.schedule(duplicate_sequence)) assert_false(scheduler.schedule(ScheduledJobRecord.create(&"two", &"work", 10, 0, &"b", 40))) func test_keyed_random_is_order_independent_and_key_sensitive() -> void: var first := KeyedRandom.value_u32(1337, &"caravan", &"caravan_a", 4) var unrelated := KeyedRandom.value_u32(1337, &"weather", &"jajce", 99) var repeated := KeyedRandom.value_u32(1337, &"caravan", &"caravan_a", 4) assert_eq(first, repeated) assert_ne(first, unrelated) assert_ne(first, KeyedRandom.value_u32(1338, &"caravan", &"caravan_a", 4)) assert_ne(first, KeyedRandom.value_u32(1337, &"caravan", &"caravan_b", 4)) assert_ne(first, KeyedRandom.value_u32(1337, &"caravan", &"caravan_a", 5)) assert_true(KeyedRandom.value_float(1337, &"caravan", &"a", 1) >= 0.0) assert_true(KeyedRandom.value_float(1337, &"caravan", &"a", 1) < 1.0) assert_true(KeyedRandom.range_int(1337, &"caravan", &"a", 1, 3, 7) in range(3, 8)) assert_true(KeyedRandom.chance(1337, &"caravan", &"a", 1, 1.0)) assert_false(KeyedRandom.chance(1337, &"caravan", &"a", 1, 0.0)) func test_analytical_elapsed_tick_updates_match_stepwise_results() -> void: var stepwise_linear := 4.0 for _tick in range(17): stepwise_linear = clampf(stepwise_linear + 0.75, 0.0, 20.0) assert_almost_eq( AnalyticalRegionalUpdates.linear_value(4.0, 0.75, 17, 0.0, 20.0), stepwise_linear, 0.000001 ) var stepwise_exponential := 100.0 for _tick in range(24): stepwise_exponential *= 0.98 assert_almost_eq( AnalyticalRegionalUpdates.exponential_value(100.0, 0.98, 24), stepwise_exponential, 0.000001 ) assert_eq(AnalyticalRegionalUpdates.periodic_occurrences(5, 3, 4), 0) assert_eq(AnalyticalRegionalUpdates.periodic_occurrences(5, 3, 14), 4) var transfer := AnalyticalRegionalUpdates.transfer_amount(12.0, 9.0, 2.0, 3, 0.25) assert_eq(transfer["sent"], 6.0) assert_eq(transfer["received"], 4.5) assert_eq(transfer["remaining_available"], 6.0) assert_eq(transfer["remaining_demand"], 4.5) func test_record_and_scheduler_parsers_reject_invalid_state_transactionally() -> void: var valid := ScheduledJobRecord.create( &"job", &"work", 3, 0, &"entity", 1, {&"z": &"value", &"a": {&"b": 2, &"a": 1}} ) assert_eq( ScheduledJobRecord.from_dictionary(valid.to_dictionary()).to_dictionary(), valid.to_dictionary() ) assert_eq(valid.get_payload(), {"a": {"a": 1, "b": 2}, "z": "value"}) var invalid := valid.to_dictionary() invalid["due_tick"] = -1 assert_null(ScheduledJobRecord.from_dictionary(invalid)) var scheduler := RegionalJobScheduler.new() assert_true(scheduler.schedule(valid)) var before := scheduler.to_dictionary() var malformed := before.duplicate(true) malformed["jobs"].append(before["jobs"][0].duplicate(true)) assert_false(scheduler.restore_from_dictionary(malformed)) assert_eq(scheduler.to_dictionary(), before) assert_eq(_job_ids(scheduler.take_due(3, 1)), [&"job"]) assert_false(scheduler.schedule(ScheduledJobRecord.create(&"past", &"work", 2, 0, &"a", 2))) func _run_fixture(budget: int) -> Dictionary: var scheduler := _fixture_scheduler() var order: Array[StringName] = [] var checksum := 146959810 while not scheduler.is_empty(): var batch := scheduler.take_due(100, budget) assert_false(batch.is_empty()) for job in batch: order.append(job.get_job_id()) checksum = int((checksum * 16777619 + job.get_stable_sequence()) & 0x7FFFFFFF) return {"order": order, "checksum": checksum, "remaining": scheduler.size()} func _run_dynamic_fixture(budget: int) -> Array[StringName]: var scheduler := RegionalJobScheduler.new() assert_not_null(scheduler.schedule_new(&"parent", &"work", 5, 0, &"a")) assert_not_null(scheduler.schedule_new(&"tail", &"work", 5, 2, &"z")) var order: Array[StringName] = [] while not scheduler.is_empty(): var executed := scheduler.execute_due( 5, budget, func(job: ScheduledJobRecord) -> void: order.append(job.get_job_id()) if job.get_job_id() == &"parent": assert_not_null(scheduler.schedule_new(&"child", &"work", 5, 1, &"b")) ) assert_false(executed.is_empty()) return order func _fixture_scheduler() -> RegionalJobScheduler: var scheduler := RegionalJobScheduler.new() for index in range(37): var due_tick := 5 + (index * 7) % 11 var phase := (index * 5) % 3 var entity_id := StringName("entity_%02d" % ((index * 13) % 9)) var job := ScheduledJobRecord.create( StringName("job_%02d" % index), &"fixture", due_tick, phase, entity_id, index, {"delta": index + 1} ) assert_true(scheduler.schedule(job)) return scheduler func _job_ids(jobs: Array[ScheduledJobRecord]) -> Array[StringName]: var result: Array[StringName] = [] for job in jobs: result.append(job.get_job_id()) return result func _unique_id_count(accumulator: Dictionary, job_id: StringName) -> Dictionary: accumulator[job_id] = true return accumulator