extends "res://tests/s5/test_runtime_defense_cycle.gd" ## All threat strengths, skills, ## costs and wave limits below are disclosed fixtures, not production values. ## Autonomy is enabled in every Runtime, with no test-only opt-out switch. func _run() -> void: print("Defense autonomy: ordinary clock, command precedence and restart") _test_ordinary_due_pause_two_waves_and_terminal() _test_multiple_raids_stable_order() _test_automatic_failure_pause_retry_and_atomicity() _test_manual_failure_is_not_hidden_by_same_tick_auto() _test_history_replay_does_not_skip_due_automatic_round() _test_history_replay_preserves_prior_manual_failure_guard() _test_resolution_history_is_scoped_per_raid() _test_generated_ids_skip_existing_saved_history() _test_exhausted_generated_ids_fail_atomically() _test_restart_mid_interval_and_speed_equivalence() _test_auto_breach_projects_facility_suspension() _cleanup() if _failures == 0: print("PASS: %d checks" % _checks) else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks]) quit(_failures) func _test_ordinary_due_pause_two_waves_and_terminal() -> void: _reset_runtime(91_001) _create_staff_and_facility(&"guard.auto", &"objective.auto", 100, 0, 9_900) _prepare_started_raid(&"raid.auto", &"guard.auto", &"objective.auto", [ _wave(&"wave.auto.1", 1_000, 1, [&"gate.north"]), _wave(&"wave.auto.2", 1_000, 1, [&"gate.south"]), ]) # A second incident stays preparing even while the first battle advances. _create_staff_and_facility(&"guard.prep", &"objective.prep") _expect(_submit(&"create_defense_raid", { &"raid_id": &"raid.prep", &"definition": _raid_definition( &"objective.prep", [_wave(&"wave.prep", 100, 1, [&"gate.north"])] ), }).is_success(), "a separate preparing incident exists without automatic kickoff") var start := int(_clock.absolute_minute) var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() _expect(_clock.process_real_delta(10.0) == 0 and _clock.absolute_minute == start, "real paused play advances no game minute or defense") _advance(4) _expect(_runtime.defense_service.get_raid(&"raid.auto")[&"current_round"] == 0 and _runtime.get_rng_stream(&"defense").snapshot() == rng_before, "deadline-minus-one neither attacks nor consumes combat RNG") var due: Array[Dictionary] = _runtime.defense_service.get_due_rounds(start + 5) _expect(due.size() == 1 and due[0][&"raid_id"] == &"raid.auto", "compact due projection includes only active due battles") due[0][&"round_number"] = 999 _expect(_runtime.defense_service.get_due_rounds(start + 5)[0][&"round_number"] == 1, "due projection cannot mutate its raid") var processed_before := int(_runtime.command_queue.processed_count()) _advance(1) var raid: Dictionary = _runtime.defense_service.get_raid(&"raid.auto") _expect(raid[&"current_round"] == 1 and raid[&"state"] == DefenseService.RaidState.BETWEEN_WAVES, "exact ordinary deadline resolves one first wave") _expect(_runtime.command_queue.processed_count() == processed_before, "automatic progression creates no fake player command") _expect(_runtime.staff_service.get_staff(&"guard.auto").state == StaffRuntime.State.UNAVAILABLE and _runtime.facility_service.get_instance(&"objective.auto").durability == 9_900, "between-wave damage stays in Defense and employee remains reserved") var between_rng: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() _advance(20) _expect(_runtime.defense_service.get_raid(&"raid.auto")[&"current_round"] == 1 and _runtime.get_rng_stream(&"defense").snapshot() == between_rng, "between-wave waiting invents no cooldown or automatic next wave") _expect(_runtime.defense_service.get_raid(&"raid.prep")[&"state"] == DefenseService.RaidState.PREPARING, "ordinary time never starts preparation automatically") _expect(_submit(&"begin_next_defense_wave", {&"raid_id": &"raid.auto"}).is_success(), "existing player command explicitly begins the second wave") _advance(5) raid = _runtime.defense_service.get_raid(&"raid.auto") _expect(raid[&"state"] == DefenseService.RaidState.DEFENDED and raid[&"current_round"] == 2, "second wave reaches terminal state from ordinary clock only") var guard: StaffRuntime = _runtime.staff_service.get_staff(&"guard.auto") _expect(guard.health == raid[&"combatants"][&"guard.auto"][&"health"] and guard.fatigue == raid[&"combatants"][&"guard.auto"][&"fatigue"] and guard.state == StaffRuntime.State.AVAILABLE, "automatic terminal boundary reconciles employee health, fatigue and availability") _expect(_runtime.facility_service.get_instance(&"objective.auto").durability == raid[&"assets"][&"objective.auto"][&"integrity"], "automatic terminal boundary reconciles facility durability") var terminal_rng: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() var terminal_events := _count_domain_events(&"DEFENSE_RAID_RESOLVED", &"raid.auto") _advance(10) _expect(_runtime.get_rng_stream(&"defense").snapshot() == terminal_rng and _count_domain_events(&"DEFENSE_RAID_RESOLVED", &"raid.auto") == terminal_events, "terminal raids stop consuming combat RNG and emit no repeated resolution") func _test_multiple_raids_stable_order() -> void: _reset_runtime(91_002) # Deliberately register in reverse order; tick order must use stable raid IDs. for suffix: String in ["z", "a"]: var guard := StringName("guard.order." + suffix) var objective := StringName("objective.order." + suffix) var raid_id := StringName("raid.order." + suffix) _create_staff_and_facility(guard, objective) _prepare_started_raid(raid_id, guard, objective, [_wave(StringName("wave.order." + suffix), 1_000, 1, [&"gate.north"])]) var expected_rng := DeterministicRng.from_root_seed(0, &"defense") as DeterministicRng expected_rng.restore(_runtime.get_rng_stream(&"defense").snapshot()) var expected_rolls: Array[int] = [] for _draw: int in 4: expected_rolls.append(expected_rng.randi_range(0, 100)) _advance(5) var order: Array[StringName] = [] for event: SimulationDomainEvent in _runtime.domain_events.recent_events(): if event.event_type == &"DEFENSE_ROUND_RESOLVED": order.append(StringName(event.payload[&"raid_id"])) _expect(order == [&"raid.order.a", &"raid.order.z"], "simultaneous raids resolve in deterministic ID order regardless of creation order") var roll_cursor := 0 for raid_id: StringName in [&"raid.order.a", &"raid.order.z"]: var raid: Dictionary = _runtime.defense_service.get_raid(raid_id) var payload: Dictionary = (raid[&"resolved_rounds"] as Dictionary).values()[0] _expect(payload[&"defense_roll"] == expected_rolls[roll_cursor] and payload[&"attack_roll"] == expected_rolls[roll_cursor + 1], "each simultaneous raid receives the exact stable-order RNG pair") _expect(String(payload[&"resolution_id"]).begins_with("defense_round_resolution."), "automatic resolution uses the existing saved ID namespace") roll_cursor += 2 _expect(_runtime.get_rng_stream(&"defense").snapshot() == expected_rng.snapshot(), "multi-raid tick consumes exactly two combat draws per round") func _test_automatic_failure_pause_retry_and_atomicity() -> void: _reset_runtime(91_003) _create_staff_and_facility(&"guard.failure", &"objective.failure", 100, 0, 9_800) _prepare_started_raid(&"raid.failure", &"guard.failure", &"objective.failure", [_wave(&"wave.failure", 500, 1, [&"gate.north"])]) _expect(bool(_runtime.inventory_service.add_stack(&"auto.inventory", &"mana_bomb", 5, 100, -1, _clock.absolute_minute, 1, &"auto.stack").get(&"ok", false)), "disclosed skill-cost inventory fixture exists") _expect(_submit(&"queue_defense_skill", { &"raid_id": &"raid.failure", &"actor_id": &"guard.failure", &"kind": &"POWER_STRIKE", &"options": {&"potency": 10, &"metadata": {&"cost": {&"gold": 125, &"mana": 9, &"items": [{&"inventory_id": &"auto.inventory", &"item_id": &"mana_bomb", &"quantity": 2}]}}}, }).is_success(), "disclosed active skill queues before automatic resolution") var before: Dictionary = _runtime.defense_service.create_snapshot() var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() var id_before := int(_runtime.id_generator.get_counter(&"defense_round_resolution")) var failure := {&"armed": true, &"calls": 0} var normal_outcome := Callable(_runtime, "_apply_defense_outcome_boundary") var injected := func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary: var report: Dictionary = normal_outcome.call(payload, phase, token) if phase == &"apply": failure[&"calls"] += 1 if bool(failure[&"armed"]) and bool(report.get(&"ok", false)): failure[&"armed"] = false report[&"ok"] = false report[&"error"] = "Disclosed automatic terminal post-apply failure." return report _runtime.defense_service.configure_callbacks(Callable(_runtime, "_roll_defense_round"), Callable(_runtime, "_apply_defense_skill_boundary"), injected, Callable(_runtime, "_apply_defense_rng_boundary")) _clock.set_paused(false) _clock.advance_minutes(20) var failed_minute := int(_clock.absolute_minute) before[&"current_minute"] = failed_minute _expect(_clock.paused and failed_minute == 365 and failure[&"calls"] == 1, "automatic failure pauses the long catch-up batch after one due attempt") _expect(_runtime.defense_service.create_snapshot() == before and _runtime.get_rng_stream(&"defense").snapshot() == rng_before, "failed automatic transaction retains round, queued skill and RNG cursor") _expect(_state.get_resource(&"gold") == 5_000 and _state.get_resource(&"mana") == 100 and _runtime.inventory_service.available_quantity(&"auto.inventory", &"mana_bomb", failed_minute) == 5, "failed automatic terminal outcome rolls all skill costs back") _expect(_runtime.staff_service.get_staff(&"guard.failure").health == 100 and _runtime.staff_service.get_staff(&"guard.failure").fatigue == 0 and _runtime.facility_service.get_instance(&"objective.failure").durability == 9_800, "failed automatic terminal outcome rolls staff and facility changes back") _expect(_runtime.id_generator.get_counter(&"defense_round_resolution") == id_before and _count_domain_events(&"DEFENSE_AUTOMATIC_ROUND_FAILED", &"raid.failure") == 1, "failed generated round ID rolls back and one diagnostic remains") _runtime._advance_defense(failed_minute) _expect(failure[&"calls"] == 1 and _clock.process_real_delta(5.0) == 0, "same-minute deferred reentry and paused real frames never retry the failure") _runtime._configure_defense_boundaries() _clock.set_paused(false) _clock.advance_minutes(1) _clock.set_paused(true) var raid: Dictionary = _runtime.defense_service.get_raid(&"raid.failure") _expect(raid[&"state"] == DefenseService.RaidState.DEFENDED and raid[&"current_round"] == 1 and raid[&"elapsed_minutes"] == 5 and raid[&"state_changed_at"] == failed_minute + 1, "resuming retries once at the current minute without fabricating offline combat") _expect(_state.get_resource(&"gold") == 4_875 and _state.get_resource(&"mana") == 91 and _runtime.inventory_service.available_quantity(&"auto.inventory", &"mana_bomb", _clock.absolute_minute) == 3, "automatic retry commits skill costs exactly once") var expected_rng := DeterministicRng.from_root_seed(0, &"defense") as DeterministicRng expected_rng.restore(rng_before) var expected_defense := expected_rng.randi_range(0, 100) var expected_attack := expected_rng.randi_range(0, 100) var round_payload: Dictionary = (raid[&"resolved_rounds"] as Dictionary).values()[0] _expect(round_payload[&"defense_roll"] == expected_defense and round_payload[&"attack_roll"] == expected_attack and _runtime.get_rng_stream(&"defense").snapshot() == expected_rng.snapshot(), "retry consumes exactly the combat pair restored after failure") func _test_manual_failure_is_not_hidden_by_same_tick_auto() -> void: _reset_runtime(91_004) _create_staff_and_facility(&"guard.manual", &"objective.manual") _prepare_started_raid(&"raid.manual", &"guard.manual", &"objective.manual", [_wave(&"wave.manual", 500, 1, [&"gate.north"])]) var failure := {&"armed": true, &"calls": 0} var normal_outcome := Callable(_runtime, "_apply_defense_outcome_boundary") var injected := func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary: var report: Dictionary = normal_outcome.call(payload, phase, token) if phase == &"apply": failure[&"calls"] += 1 if bool(failure[&"armed"]): failure[&"armed"] = false report[&"ok"] = false report[&"error"] = "Disclosed one-shot manual failure." return report _runtime.defense_service.configure_callbacks(Callable(_runtime, "_roll_defense_round"), Callable(_runtime, "_apply_defense_skill_boundary"), injected, Callable(_runtime, "_apply_defense_rng_boundary")) var attempt := _submit_round_at_due({&"raid_id": &"raid.manual"}) var failed: CommandResult = attempt[&"result"] _expect(not failed.is_success() and failure[&"calls"] == 1 and attempt[&"before"] == attempt[&"after"], "manual due failure remains a failed command with all domain boundaries rolled back") _expect(_runtime.defense_service.get_raid(&"raid.manual")[&"current_round"] == 0 and _count_domain_events(&"DEFENSE_ROUND_RESOLVED", &"raid.manual") == 0, "automatic phase never hides that failure by immediately retrying the same raid") # A successful restore at the very same clock minute must discard the old # execution guard, including when this Runtime already attempted that minute. var retryable: Dictionary = _runtime.create_snapshot() _expect(_runtime.restore_snapshot(retryable), "failed but atomic manual round remains a valid save") _runtime._advance_defense(_clock.absolute_minute) _expect(_runtime.defense_service.get_raid(&"raid.manual")[&"current_round"] == 1, "successful same-minute restore clears ephemeral attempt state") func _test_restart_mid_interval_and_speed_equivalence() -> void: _reset_runtime(91_005) _create_staff_and_facility(&"guard.restart", &"objective.restart") _prepare_started_raid(&"raid.restart", &"guard.restart", &"objective.restart", [_wave(&"wave.restart", 10_000, 100, [&"gate.north"])]) _advance(2) _expect(_submit(&"queue_defense_skill", {&"raid_id": &"raid.restart", &"actor_id": &"guard.restart", &"kind": &"FORTIFY", &"options": {&"potency": 8, &"target_id": &"objective.restart"}}).is_success(), "mid-interval queued skill is an explicit disclosed fixture") var origin: Dictionary = _runtime.create_snapshot() _advance(8) var expected: Dictionary = _runtime.create_snapshot() _reset_runtime(91_005) _expect(_runtime.restore_snapshot(origin), "independently initialized Runtime restores the mid-interval save") _advance(8) _expect(_runtime.create_snapshot() == expected, "restart reproduces full combat, RNG, queued skill, ID and event state without extra serialized timers") _expect(_runtime.restore_snapshot(origin), "restore speed comparison origin") _clock.process_real_delta(0.0) _clock.set_speed(1) _clock.set_paused(false) var x1_ticks := 0 for frame: int in 9: x1_ticks += _clock.process_real_delta(SimulationKernel.REAL_SECONDS_PER_GAME_MINUTE * 1.01) var x1: Dictionary = _capture_defense_views() _expect(_runtime.restore_snapshot(origin), "restore identical x3 origin") _clock.process_real_delta(0.0) _clock.set_speed(3) _clock.set_paused(false) var x3_ticks := 0 for frame: int in 3: x3_ticks += _clock.process_real_delta(SimulationKernel.REAL_SECONDS_PER_GAME_MINUTE * 1.01) _expect(x1_ticks == 9 and x3_ticks == 9 and _capture_defense_views() == x1, "x1 and x3 real-frame execution agree after the same nine game minutes") _clock.set_paused(true) func _test_history_replay_does_not_skip_due_automatic_round() -> void: _reset_runtime(91_007) _create_staff_and_facility(&"guard.replay.auto", &"objective.replay.auto") _prepare_started_raid(&"raid.replay.auto", &"guard.replay.auto", &"objective.replay.auto", [_wave(&"wave.replay.auto", 10_000, 100, [&"gate.north"])]) var first_attempt := _submit_round_at_due({&"raid_id": &"raid.replay.auto", &"resolution_id": &"round.replay.first"}) var first: CommandResult = first_attempt[&"result"] _expect(first.is_success() and not first.is_replay, "a genuine first round establishes canonical history") var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() # A new command key deliberately reads that old result at the second round's # actual deadline; its command is processed before the automatic phase. var replay_attempt := _submit_round_at_due({&"raid_id": &"raid.replay.auto", &"resolution_id": &"round.replay.first"}, &"autonomy.replay.new.command.key") var replay: CommandResult = replay_attempt[&"result"] _expect(replay.is_success() and replay.is_replay and replay.command_id > first.command_id and replay.details == first.details, "new command key returns the unchanged canonical historical result") _expect(replay_attempt[&"before"] == replay_attempt[&"after"], "history replay alone consumes no RNG, costs or combat state") var raid: Dictionary = _runtime.defense_service.get_raid(&"raid.replay.auto") _expect(raid[&"current_round"] == 2 and _count_domain_events(&"DEFENSE_ROUND_RESOLVED", &"raid.replay.auto") == 2, "historical replay never skips the legitimate automatic round due in the same tick") var expected_rng := DeterministicRng.from_root_seed(0, &"defense") as DeterministicRng expected_rng.restore(rng_before) var expected_defense := expected_rng.randi_range(0, 100) var expected_attack := expected_rng.randi_range(0, 100) var latest: Dictionary = {} for payload: Dictionary in (raid[&"resolved_rounds"] as Dictionary).values(): if payload[&"round_number"] == 2: latest = payload _expect(not latest.is_empty() and latest[&"defense_roll"] == expected_defense and latest[&"attack_roll"] == expected_attack and _runtime.get_rng_stream(&"defense").snapshot() == expected_rng.snapshot(), "replay plus due automatic work consumes exactly one new round RNG pair") func _test_history_replay_preserves_prior_manual_failure_guard() -> void: _reset_runtime(91_008) _create_staff_and_facility(&"guard.replay.failure", &"objective.replay.failure") _prepare_started_raid(&"raid.replay.failure", &"guard.replay.failure", &"objective.replay.failure", [_wave(&"wave.replay.failure", 10_000, 2, [&"gate.north"])]) var first_attempt := _submit_round_at_due({&"raid_id": &"raid.replay.failure", &"resolution_id": &"round.replay.failure.first"}) _expect((first_attempt[&"result"] as CommandResult).is_success(), "first round precedes the disclosed next-round failure") var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() var failure := {&"armed": true, &"calls": 0} var normal_outcome := Callable(_runtime, "_apply_defense_outcome_boundary") var injected := func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary: var report: Dictionary = normal_outcome.call(payload, phase, token) if phase == &"apply": failure[&"calls"] += 1 if bool(failure[&"armed"]): failure[&"armed"] = false report[&"ok"] = false report[&"error"] = "Disclosed failure before historical replay." return report _runtime.defense_service.configure_callbacks(Callable(_runtime, "_roll_defense_round"), Callable(_runtime, "_apply_defense_skill_boundary"), injected, Callable(_runtime, "_apply_defense_rng_boundary")) var due := int(_clock.absolute_minute) + int(_runtime.defense_service.get_raid(&"raid.replay.failure")[&"round_minutes"]) # Stable command-ID order executes this real failing transaction before the # helper's subsequent historical replay, at the very same canonical minute. var accepted: CommandResult = _runtime.submit_command(&"resolve_defense_round", {&"raid_id": &"raid.replay.failure", &"resolution_id": &"round.replay.failure.next"}, &"defense_controller", &"raid.replay.failure", 80, due, &"autonomy.real.failure.first") var replay_attempt := _submit_round_at_due({&"raid_id": &"raid.replay.failure", &"resolution_id": &"round.replay.failure.first"}, &"autonomy.old.replay.after.failure") var failed: CommandResult = _runtime.command_queue.get_processed_result(accepted.command_id) var replay: CommandResult = replay_attempt[&"result"] _expect(accepted.is_success() and failed != null and not failed.is_success() and replay.is_success() and replay.is_replay and replay.command_id > accepted.command_id, "real failure and following old replay preserve their distinct actual command results") _expect(failure[&"calls"] == 1 and _runtime.defense_service.get_raid(&"raid.replay.failure")[&"current_round"] == 1 and _runtime.get_rng_stream(&"defense").snapshot() == rng_before, "old replay cannot erase a prior failed real-round guard or hide it with same-tick automatic success") _runtime._configure_defense_boundaries() _advance(1) _expect(_runtime.defense_service.get_raid(&"raid.replay.failure")[&"current_round"] == 2, "the genuine failed round can progress automatically on a later game minute") func _test_resolution_history_is_scoped_per_raid() -> void: _reset_runtime(91_009) var accepted: Array[CommandResult] = [] for suffix: String in ["a", "b"]: var guard := StringName("guard.shared.id." + suffix) var objective := StringName("objective.shared.id." + suffix) var raid_id := StringName("raid.shared.id." + suffix) _create_staff_and_facility(guard, objective) _prepare_started_raid(raid_id, guard, objective, [_wave(StringName("wave.shared.id." + suffix), 10_000, 100, [&"gate.north"])]) accepted.append(_runtime.submit_command(&"resolve_defense_round", {&"raid_id": raid_id, &"resolution_id": &"round.shared.history.id"}, &"defense_controller", raid_id, 80, int(_clock.absolute_minute) + 5, StringName("autonomy.shared.history." + suffix))) _advance(5) for pending: CommandResult in accepted: var completed: CommandResult = _runtime.command_queue.get_processed_result(pending.command_id) _expect(pending.is_success() and completed != null and completed.is_success() and not completed.is_replay and completed.details[&"resolution_id"] == &"round.shared.history.id", "same explicit resolution ID belongs independently to each raid in the same minute") _expect(_runtime.defense_service.get_raid(&"raid.shared.id.a")[&"current_round"] == 1 and _runtime.defense_service.get_raid(&"raid.shared.id.b")[&"current_round"] == 1, "per-raid manual guards prevent double resolution without suppressing the other raid") func _test_generated_ids_skip_existing_saved_history() -> void: _reset_runtime(91_010) _create_staff_and_facility(&"guard.id.collision", &"objective.id.collision") _prepare_started_raid(&"raid.id.collision", &"guard.id.collision", &"objective.id.collision", [_wave(&"wave.id.collision", 10_000, 100, [&"gate.north"])]) for number: int in [1, 2]: var explicit_id := StringName("defense_round_resolution.%012d" % number) var attempt := _submit_round_at_due({&"raid_id": &"raid.id.collision", &"resolution_id": explicit_id}) _expect((attempt[&"result"] as CommandResult).is_success(), "valid explicit resolution establishes saved namespace collision") _expect(_runtime.id_generator.get_counter(&"defense_round_resolution") == 0, "existing explicit IDs legitimately leave the generated counter unchanged") var origin: Dictionary = _runtime.create_snapshot() _reset_runtime(91_010) _expect(_runtime.restore_snapshot(origin), "valid mid-battle history with explicit namespace IDs remains loadable") var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() var due := int(_clock.absolute_minute) + 5 _advance(5) var raid: Dictionary = _runtime.defense_service.get_raid(&"raid.id.collision") _expect(raid[&"current_round"] == 3 and raid[&"state_changed_at"] == due, "namespace collision never delays the attack beyond its saved deadline") _expect(_runtime.defense_service.has_resolution(&"raid.id.collision", &"defense_round_resolution.000000000003") and _runtime.id_generator.get_counter(&"defense_round_resolution") == 3, "generated resolution skips both existing IDs and commits one unused ID") var expected_rng := DeterministicRng.from_root_seed(0, &"defense") as DeterministicRng expected_rng.restore(rng_before) var expected_defense := expected_rng.randi_range(0, 100) var expected_attack := expected_rng.randi_range(0, 100) var resolved: Dictionary = raid[&"resolved_rounds"][&"defense_round_resolution.000000000003"] _expect(resolved[&"defense_roll"] == expected_defense and resolved[&"attack_roll"] == expected_attack and _runtime.get_rng_stream(&"defense").snapshot() == expected_rng.snapshot(), "skipping history IDs consumes exactly one attack's combat RNG pair") _expect(raid[&"resolved_rounds"][&"defense_round_resolution.000000000001"][&"round_number"] == 1 and raid[&"resolved_rounds"][&"defense_round_resolution.000000000002"][&"round_number"] == 2, "skipping IDs preserves both canonical old results") func _test_exhausted_generated_ids_fail_atomically() -> void: _reset_runtime(91_011) _create_staff_and_facility(&"guard.id.exhausted", &"objective.id.exhausted") _prepare_started_raid(&"raid.id.exhausted", &"guard.id.exhausted", &"objective.id.exhausted", [_wave(&"wave.id.exhausted", 10_000, 100, [&"gate.north"])]) var last_id := StringName("defense_round_resolution.%012d" % StableIdGenerator.MAX_COUNTER) var attempt := _submit_round_at_due({&"raid_id": &"raid.id.exhausted", &"resolution_id": last_id}) _expect((attempt[&"result"] as CommandResult).is_success(), "valid explicit history can occupy the last generated ID") var allocator: Dictionary = _runtime.id_generator.create_snapshot() allocator["counters"]["defense_round_resolution"] = StableIdGenerator.MAX_COUNTER - 1 _expect(_runtime.id_generator.restore_snapshot(allocator), "valid near-exhausted allocator is a disclosed saved-state boundary") var before: Dictionary = _runtime.defense_service.create_snapshot() var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot() var mana_before: int = _state.get_resource(&"mana") var reserve_minute_before: int = _runtime.mana_reserve.get_minute() _advance(5) # A06: crossing a 10-minute boundary settles the finite reserve; that flow is # the only Mana change allowed here. var reserve_mana := mana_before if _runtime.mana_reserve.get_minute() != reserve_minute_before: reserve_mana = int(_runtime.mana_reserve.get_last_flow()[&"after"]) before[&"current_minute"] = _clock.absolute_minute _expect(before == _runtime.defense_service.create_snapshot() and rng_before == _runtime.get_rng_stream(&"defense").snapshot() and _clock.paused, "exhausted automatic allocator fails without advancing combat or RNG") _expect(_state.get_resource(&"gold") == 5_000 and _state.get_resource(&"mana") == reserve_mana, "allocation failure charges no skill or outcome resources (Mana moves only by the A06 reserve flow)") _expect(_count_domain_events(&"DEFENSE_AUTOMATIC_ROUND_FAILED", &"raid.id.exhausted") == 1 and _runtime.id_generator.get_counter(&"defense_round_resolution") == StableIdGenerator.MAX_COUNTER - 1, "exhaustion records one diagnostic and rolls skipped-ID allocation back") func _test_auto_breach_projects_facility_suspension() -> void: _reset_runtime(91_006) _create_staff_and_facility(&"guard.breach.auto", &"objective.breach.auto", 100, 0, 1) _prepare_started_raid(&"raid.breach.auto", &"guard.breach.auto", &"objective.breach.auto", [_wave(&"wave.breach.auto", 1_000, 1, [&"gate.north"])], 0, 0, {&"target_order": 1}, 1_000) _advance(5) var facility: FacilityInstance = _runtime.facility_service.get_instance(&"objective.breach.auto") _expect(_runtime.defense_service.get_raid(&"raid.breach.auto")[&"state"] == DefenseService.RaidState.BREACHED and facility.durability == 0 and facility.state == FacilityInstance.State.BROKEN, "ordinary automatic combat destroys the objective through the actual facility boundary") _expect(facility.suspension_reason == &"DESTROYED", "automatic destruction keeps the user-specified suspension state without inventing recovery") var terminal: Dictionary = _runtime.create_snapshot() _expect(_runtime.restore_snapshot(terminal), "automatically breached terminal state round-trips through ordinary Runtime save restoration")