Files
TheTree_mock/tests/s5/test_runtime_event_defense.gd

323 lines
27 KiB
GDScript

extends "res://tests/s5/test_runtime_defense_cycle.gd"
## Acceptance tests for the configured Event -> Defense bridge. Source causes, facilities and every
## combat value below are disclosed fixtures. This does not prove normal thief,
## male bankruptcy or bank delinquency generation or an approved balance.
func _run() -> void:
print("Configured Event -> Defense: identity, rollback and save")
_test_failure_policy_and_expiry()
_test_event_rollback_preserves_failed_round_guard()
_test_serialized_action_enum_rejection()
_test_source_families_from_ordinary_event_phase()
_test_replay_conflict_and_manual_provenance_rejection()
_test_rollback_of_later_failed_action()
_test_choice_replay_and_real_cost_commit_failure()
_test_save_branch_provenance_corruption_and_legacy()
_cleanup()
if _failures == 0: print("PASS: %d checks" % _checks)
else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _test_source_families_from_ordinary_event_phase() -> void:
_reset_runtime(92_001)
for kind: String in ["THIEF", "BANKRUPTCY_RIOT", "BANK_DELINQUENCY", "AUTHORITY_ESCALATION"]:
var objective := StringName("objective.event." + kind)
_create_staff_and_facility(StringName("guard.event." + kind), objective)
var action := _event_raid_action(objective, kind)
var instance := StringName("event.instance.fixture." + kind)
_expect(_register_threat_event(StringName("event.fixture." + kind), [action]).is_success(), "explicit configured event definition registers for " + kind)
_expect(_enqueue_threat_event(StringName("event.fixture." + kind), instance, StringName("cause.fixture." + kind)).is_success(), "disclosed cause enters the actual Event queue for " + kind)
var before := int(_runtime.defense_service.get_raid_ids().size())
var commands_before := int(_runtime.command_queue.processed_count())
_advance(1)
var raid_id := _event_raid_id(instance, action.action_id)
var raid: Dictionary = _runtime.defense_service.get_raid(raid_id)
_expect(_runtime.defense_service.get_raid_ids().size() == before + 1 and raid[&"state"] == DefenseService.RaidState.PREPARING, "ordinary event phase creates an existing preparing threat for " + kind)
_expect(raid[&"created_at"] == _clock.absolute_minute and raid[&"source_id"] == StringName("cause.fixture." + kind) and raid[&"event_source"]["source_kind"] == kind, "creation time and source families are preserved independently")
_expect(_runtime.event_service.get_instance(instance).state == EventInstance.State.COMPLETED and _count_domain_events(&"DEFENSE_RAID_CREATED", raid_id) == 1, "automatic Event action completes once and emits one real incident event")
_expect(_runtime.command_queue.processed_count() == commands_before, "ordinary event sink creates no synthetic player command")
# Incomplete templates fail closed rather than using Defense defaults.
var incomplete := _event_raid_action(&"objective.event.THIEF", "THIEF")
(incomplete.payload[&"definition"] as Dictionary).erase(&"round_minutes")
_expect(_register_threat_event(&"event.fixture.incomplete", [incomplete]).is_success(), "schema accepts an action whose runtime configuration must still be checked")
_expect(_enqueue_threat_event(&"event.fixture.incomplete", &"event.instance.incomplete", &"cause.incomplete").is_success(), "incomplete disclosed template enters the event queue")
var raids_before: Dictionary = _runtime.defense_service.create_snapshot()
_advance(1)
raids_before[&"current_minute"] = _clock.absolute_minute
_expect(_runtime.defense_service.create_snapshot() == raids_before and _runtime.event_service.get_instance(&"event.instance.incomplete").state == EventInstance.State.CANCELLED, "missing interval creates no threat and preserves every existing raid")
func _test_replay_conflict_and_manual_provenance_rejection() -> void:
_reset_runtime(92_002)
_create_staff_and_facility(&"guard.event.replay", &"objective.event.replay")
var action := _event_raid_action(&"objective.event.replay", "BANKRUPTCY_RIOT")
_expect(_register_threat_event(&"event.fixture.replay", [action]).is_success(), "replay fixture definition registers")
_expect(_enqueue_threat_event(&"event.fixture.replay", &"event.instance.replay", &"male.guest.cause.fixture").is_success(), "disclosed cause is queued")
_advance(1)
var raid_id := _event_raid_id(&"event.instance.replay", action.action_id)
_expect(_register_defender(raid_id, &"guard.event.replay", 50).is_success() and _register_objective(raid_id, &"objective.event.replay", 20).is_success(), "ordinary defense assignment connects real employee and facility")
_expect(_submit(&"start_defense_raid", {&"raid_id": raid_id}).is_success(), "ordinary start command begins the configured incident")
_advance(5)
var context := {&"event_instance_id": &"event.instance.replay", &"threat_source_id": &"male.guest.cause.fixture"}
var before: Dictionary = _runtime.create_snapshot()
var replay: Dictionary = _runtime._execute_event_action_boundary(action, &"apply", context, null)
_expect(replay.get(&"ok", false) and replay.get(&"is_replay", false) and _runtime.create_snapshot() == before, "event-action replay preserves active combat, staff locks, resources, RNG, clock and event log")
var conflicting := EventAction.from_variant(action.create_snapshot())
(conflicting.payload[&"definition"][&"waves"] as Array)[0][&"threat_strength"] += 1
var rejected: Dictionary = _runtime._execute_event_action_boundary(conflicting, &"apply", context, null)
_expect(not rejected.get(&"ok", true) and _runtime.create_snapshot() == before, "same cause/action with a conflicting template never replaces its existing battle")
var forged_definition := _threat_definition(&"objective.event.replay")
forged_definition[&"event_source"] = _runtime.defense_service.get_raid(raid_id)[&"event_source"]
var defense_before: Dictionary = _runtime.defense_service.create_snapshot()
_expect(not _submit(&"create_defense_raid", {&"raid_id": &"forged.manual.provenance", &"definition": forged_definition}).is_success() and _runtime.defense_service.create_snapshot() == defense_before, "manual raid API cannot forge event provenance")
func _test_rollback_of_later_failed_action() -> void:
_reset_runtime(92_003)
_create_staff_and_facility(&"guard.event.rollback", &"objective.event.rollback")
_expect(_runtime.staff_service.create_task(&"fixture.work", 20, _clock.absolute_minute, {}, &"task.event.duplicate").is_success(), "disclosed duplicate task is prepared for apply-time failure")
var threat := _event_raid_action(&"objective.event.rollback", "BANK_DELINQUENCY")
var duplicate := EventAction.new().configure(&"fail.after.threat", EventAction.Type.CREATE_TASK, {&"task_id": &"task.event.duplicate", &"task_type": &"fixture.work"})
_expect(_register_threat_event(&"event.fixture.rollback", [threat, duplicate]).is_success(), "rollback-all event includes threat followed by an apply-time failure")
_expect(_enqueue_threat_event(&"event.fixture.rollback", &"event.instance.rollback", &"loan.overdue.cause.fixture").is_success(), "disclosed overdue cause enters the queue without claiming a real loan")
var before: Dictionary = _runtime.defense_service.create_snapshot()
var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot()
_advance(1)
before[&"current_minute"] = _clock.absolute_minute
var raid_id := _event_raid_id(&"event.instance.rollback", threat.action_id)
_expect(_runtime.defense_service.create_snapshot() == before and _runtime.get_rng_stream(&"defense").snapshot() == rng_before, "later action failure rolls the new incident and its provenance back")
_expect(_runtime.defense_service.get_raid(raid_id).is_empty() and _count_domain_events(&"DEFENSE_RAID_CREATED", raid_id) == 0, "rolled-back incident leaves no committed creation event")
_expect(_runtime.event_service.get_instance(&"event.instance.rollback").state == EventInstance.State.CANCELLED, "failed event follows the existing cancellation policy")
func _test_save_branch_provenance_corruption_and_legacy() -> void:
_reset_runtime(92_004)
_create_staff_and_facility(&"guard.event.save", &"objective.event.save")
var action := _event_raid_action(&"objective.event.save", "THIEF")
_expect(_register_threat_event(&"event.fixture.save", [action]).is_success(), "save fixture definition registers")
_expect(_enqueue_threat_event(&"event.fixture.save", &"event.instance.save", &"thief.cause.fixture").is_success(), "save fixture source enters queue")
var queued: Dictionary = _runtime.create_snapshot()
_advance(1)
var expected: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(queued), "queued configured threat restores before execution")
_advance(1)
_expect(_runtime.create_snapshot() == expected, "save/restart branch produces the exact same event-derived raid ID, provenance, event and command state")
var raid_id := _event_raid_id(&"event.instance.save", action.action_id)
for kind: String in ["digest", "type", "event", "source", "interval", "wave_limit", "wave_strength"]:
var corrupt: Dictionary = expected.duplicate(true)
var raids: Array = corrupt[&"systems"][&"runtime"][&"defense"][&"raids"]
var raid: Dictionary = raids[0]
if kind == "digest": raid[&"event_source"]["template_digest"] = "a".repeat(64)
elif kind == "type": raid[&"event_source"] = []
elif kind == "event": raid[&"event_source"]["event_instance_id"] = "missing.event"
elif kind == "interval": raid[&"round_minutes"] = 7
elif kind == "wave_limit": (raid[&"waves"] as Array)[0][&"max_rounds"] += 1
elif kind == "wave_strength":
(raid[&"waves"] as Array)[0][&"threat_strength"] += 1
(raid[&"waves"] as Array)[0][&"remaining_threat"] += 1
else: raid[&"source_id"] = "unrelated.cause"
_expect(not _runtime.restore_snapshot(corrupt) and _runtime.create_snapshot() == expected, "corrupt event provenance is rejected atomically: " + kind)
_expect(_runtime.defense_service.get_raid(raid_id)[&"event_source"]["template_digest"].length() == 64, "valid event provenance remains after all rejected restores")
_expect(_submit(&"set_defense_retreat_policy", {&"raid_id": raid_id, &"policy": &"PROTECT_AND_RETREAT", &"threshold": 35}).is_success(), "normal player policy remains mutable after event creation")
var changed_policy: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(changed_policy) and _runtime.create_snapshot() == changed_policy, "immutable template validation preserves lawful policy changes")
_reset_runtime(92_005)
_create_staff_and_facility(&"guard.event.legacy", &"objective.event.legacy")
_expect(_submit(&"create_defense_raid", {&"raid_id": &"raid.legacy.manual", &"definition": _threat_definition(&"objective.event.legacy")}).is_success(), "real manual raid exists before legacy provenance migration")
var legacy: Dictionary = _runtime.create_snapshot()
legacy[&"systems"][&"runtime"][&"runtime_schema_version"] = 12
preload("res://tests/fixtures/pre_auto_purchase_runtime.gd").strip_empty(legacy[&"systems"][&"runtime"])
(legacy[&"systems"][&"runtime"][&"defense"][&"raids"] as Array)[0].erase(&"event_source")
var clean: Dictionary = legacy[&"systems"][&"runtime"][&"facility_cleanliness"].duplicate(true)
_expect(_runtime.restore_snapshot(legacy) and _runtime.facility_cleanliness.create_snapshot() == clean, "version-12 save keeps its required cleaning state when migrating to event schema 13")
_expect(not clean["facilities"].is_empty() and _runtime.defense_service.get_raid(&"raid.legacy.manual")[&"event_source"].is_empty(), "legacy migration preserves a nonempty cleanliness participant and old manual raid")
var migrated: Dictionary = _runtime.create_snapshot()
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 14, "legacy12 re-emits current14 without losing required12 data")
var missing_clean: Dictionary = legacy.duplicate(true)
missing_clean[&"systems"][&"runtime"].erase(&"facility_cleanliness")
_expect(not _runtime.restore_snapshot(missing_clean) and _runtime.create_snapshot() == migrated, "legacy12 missing mandatory cleanliness is rejected atomically")
var malformed_clean: Dictionary = legacy.duplicate(true)
malformed_clean[&"systems"][&"runtime"][&"facility_cleanliness"]["schema_version"] = 99
_expect(not _runtime.restore_snapshot(malformed_clean) and _runtime.create_snapshot() == migrated, "legacy12 malformed mandatory cleanliness is rejected atomically")
var future: Dictionary = _runtime.create_snapshot()
var current: Dictionary = future.duplicate(true)
future[&"systems"][&"runtime"][&"runtime_schema_version"] = 15
_expect(not _runtime.restore_snapshot(future) and _runtime.create_snapshot() == current, "unknown future runtime schema remains rejected")
func _test_choice_replay_and_real_cost_commit_failure() -> void:
for commit_failure: bool in [false, true]:
_reset_runtime(92_006 if not commit_failure else 92_007)
_create_staff_and_facility(&"guard.event.choice", &"objective.event.choice")
var instance_id := &"event.instance.choice.fixture"
var threat := _event_raid_action(&"objective.event.choice", "THIEF")
var actions: Array[Dictionary] = [threat.create_snapshot()]
if commit_failure:
# This disclosed action spends the actual reserved escrow before the
# real commit. There is no mocked executor, cost callback or clock.
var consume := EventAction.new().configure(&"fixture.consume.escrow", EventAction.Type.ADD_LEDGER_ENTRY, {
&"from_account": StringName("EVENT_ESCROW:%s" % instance_id),
&"to_account": &"OPERATING_EXPENSE", &"amount": 7,
})
actions.append(consume.create_snapshot())
var registered: CommandResult = _runtime.submit_command(&"register_event_definition", {&"definition": {
&"event_id": &"event.fixture.choice", &"category": &"DEFENSE", &"foreground": false,
&"transaction_policy": &"ROLLBACK_ALL", &"choices": [{&"choice_id": &"fixture.choice",
&"text_key": &"fixture.choice.text", &"cost": {&"gold": 7}, &"actions": actions}],
}})
_expect(registered.is_success() and _enqueue_threat_event(&"event.fixture.choice", instance_id, &"choice.cause.fixture").is_success(), "disclosed choice threat registers and enters the actual event queue")
_advance(1)
var instance: EventInstance = _runtime.event_service.get_instance(instance_id)
_expect(instance.state == EventInstance.State.WAITING_CHOICE, "ordinary event phase waits for an actual choice before creating its raid")
var before: Dictionary = _runtime.defense_service.create_snapshot()
var cash_before: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
var expense_before: int = _runtime.economy_service.get_balance(&"OPERATING_EXPENSE")
var payload := {&"event_instance_id": instance_id, &"choice_instance_id": instance.choice_instance_id, &"choice_id": &"fixture.choice"}
var first := _submit(&"submit_event_choice", payload)
var raid_id := _event_raid_id(instance_id, threat.action_id)
if commit_failure:
_expect(not first.is_success() and _runtime.defense_service.create_snapshot() == before, "real cost commit failure rolls back the newly-created raid and provenance")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == cash_before and _runtime.economy_service.get_balance(&"OPERATING_EXPENSE") == expense_before, "failed commit rolls back its drain action and releases the actual escrow once")
_expect(_count_domain_events(&"DEFENSE_RAID_CREATED", raid_id) == 0 and _runtime.economy_service.get_balance(StringName("EVENT_ESCROW:%s" % instance_id)) == 0, "failed choice leaves no committed threat notification or stranded escrow")
else:
_expect(first.is_success() and _runtime.defense_service.get_raid(raid_id)[&"state"] == DefenseService.RaidState.PREPARING, "actual successful choice creates one preparing incident")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == cash_before - 7 and _runtime.economy_service.get_balance(&"OPERATING_EXPENSE") == expense_before + 7, "successful choice commits its disclosed fixture cost once")
var defense_after: Dictionary = _runtime.defense_service.create_snapshot()
var economy_after: Dictionary = _runtime.economy_service.create_snapshot()
var events_after: Dictionary = _runtime.event_service.create_snapshot()
# Successful choice commands legitimately resume the real clock. Pause
# through its public API to submit and observe the retry immediately.
_clock.set_paused(true)
var replay := _submit(&"submit_event_choice", payload)
_expect(replay.is_replay and replay.is_success() == first.is_success() and _runtime.defense_service.create_snapshot() == defense_after and _runtime.economy_service.create_snapshot() == economy_after and _runtime.event_service.create_snapshot() == events_after, "different command key replays the same real choice result without threat creation or payment retry")
_expect(_runtime.economy_service.validate_ledger()[&"ok"] and _runtime.event_service.validate_invariants()[&"ok"], "configured threat choice preserves actual Event and double-entry invariants")
func _threat_definition(objective: StringName) -> Dictionary:
return {&"entry_points": [&"fixture.event.entrance"], &"objective_ids": [objective],
&"round_minutes": 5, &"max_duration_minutes": 120,
&"retreat_policy": &"HOLD", &"defender_retreat_threshold": 0,
&"terrain_bonus": 0, &"command_bonus": 0, &"surprise_penalty": 0, &"multiple_entry_penalty": 0,
&"waves": [{&"threat_strength": 600, &"max_rounds": 3, &"retreat_threshold": 0}]}
func _event_raid_action(objective: StringName, kind: String) -> EventAction:
return EventAction.new().configure(&"configured.threat", EventAction.Type.CREATE_DEFENSE_RAID, {&"template_id": &"fixture.template.explicit", &"source_kind": kind, &"definition": _threat_definition(objective)})
func _register_threat_event(event_id: StringName, actions: Array) -> CommandResult:
var snapshots: Array[Dictionary] = []
for action: EventAction in actions: snapshots.append(action.create_snapshot())
return _runtime.submit_command(&"register_event_definition", {&"definition": {&"event_id": event_id, &"category": &"DEFENSE", &"foreground": false, &"transaction_policy": &"ROLLBACK_ALL", &"automatic_actions": snapshots}})
func _enqueue_threat_event(event_id: StringName, instance_id: StringName, source_id: StringName) -> CommandResult:
return _runtime.submit_command(&"enqueue_event", {&"event_id": event_id, &"actor_ids": [], &"options": {&"event_instance_id": instance_id, &"context": {&"threat_source_id": source_id}}})
func _event_raid_id(instance_id: StringName, action_id: StringName) -> StringName:
return StringName("event_defense_raid." + JSON.stringify([String(instance_id), String(action_id)], "", true, true).sha256_text())
func _test_serialized_action_enum_rejection() -> void:
_reset_runtime(92_008)
_create_staff_and_facility(&"guard.event.enum", &"objective.event.enum")
var action := _event_raid_action(&"objective.event.enum", "THIEF")
var bad_types: Array = [999, -1, "FUTURE_UNKNOWN_ACTION", 1.5, true, null]
for index: int in range(bad_types.size()):
var raw: Dictionary = action.create_snapshot()
raw[&"type"] = bad_types[index]
var event_id := StringName("event.enum.invalid.%d" % index)
var defense_before: Dictionary = _runtime.defense_service.create_snapshot()
var result: CommandResult = _runtime.submit_command(&"register_event_definition", {&"definition": {&"event_id": event_id, &"category": &"DEFENSE", &"automatic_actions": [raw]}})
_expect(not result.is_success() and _runtime.event_service.get_definition(event_id) == null and _runtime.defense_service.create_snapshot() == defense_before, "actual event registration rejects an unknown serialized action type without dropping or coercing it: %d" % index)
_expect(_register_threat_event(&"event.fixture.enum", [action]).is_success() and _enqueue_threat_event(&"event.fixture.enum", &"event.instance.enum", &"cause.fixture.enum").is_success(), "valid queued event exists for corrupt enum restore tests")
var queued: Dictionary = _runtime.create_snapshot()
for raw_type: Variant in bad_types:
var corrupt: Dictionary = queued.duplicate(true)
for raw_definition: Dictionary in corrupt[&"systems"][&"runtime"][&"event_definitions"]:
if String(raw_definition[&"event_id"]) == "event.fixture.enum":
(raw_definition[&"automatic_actions"] as Array)[0][&"type"] = raw_type
_expect(not _runtime.restore_snapshot(corrupt) and _runtime.create_snapshot() == queued, "queued complete threat template rejects a corrupt action enum atomically")
var integer_enum: Dictionary = queued.duplicate(true)
for raw_definition: Dictionary in integer_enum[&"systems"][&"runtime"][&"event_definitions"]:
if String(raw_definition[&"event_id"]) == "event.fixture.enum":
(raw_definition[&"automatic_actions"] as Array)[0][&"type"] = EventAction.Type.CREATE_DEFENSE_RAID
_expect(_runtime.restore_snapshot(integer_enum) and _runtime.create_snapshot() == queued, "known serialized integer enum remains compatible and canonicalizes to its name")
func _test_failure_policy_and_expiry() -> void:
for policy: String in ["SKIP_FAILED", "BRANCH_FAILURE", "EXPIRED"]:
_reset_runtime(92_020)
_create_staff_and_facility(&"guard.event.policy", &"objective.event.policy")
_expect(_runtime.staff_service.create_task(&"fixture.work", 20, _clock.absolute_minute, {}, &"task.event.policy.duplicate").is_success(), "actual duplicate task exists for policy failure")
var primary := _event_raid_action(&"objective.event.policy", "THIEF")
var duplicate := EventAction.new().configure(&"fail.policy", EventAction.Type.CREATE_TASK, {&"task_id": &"task.event.policy.duplicate", &"task_type": &"fixture.work"})
var fallback := _event_raid_action(&"objective.event.policy", "AUTHORITY_ESCALATION")
fallback.action_id = &"fallback.threat"
var definition := {&"event_id": &"event.fixture.policy", &"category": &"DEFENSE", &"foreground": false, &"automatic_actions": [primary.create_snapshot(), duplicate.create_snapshot()]}
if policy == "EXPIRED":
definition[&"transaction_policy"] = &"ROLLBACK_ALL"
definition[&"fallback_actions"] = [fallback.create_snapshot()]
else:
definition[&"transaction_policy"] = policy
definition[&"failure_actions"] = [fallback.create_snapshot()]
_expect(_runtime.submit_command(&"register_event_definition", {&"definition": definition}).is_success(), "real Event registers policy " + policy)
var options := {&"event_instance_id": &"event.instance.policy", &"context": {&"threat_source_id": &"cause.policy.fixture"}}
if policy == "EXPIRED": options[&"expires_at"] = _clock.absolute_minute
_expect(_runtime.submit_command(&"enqueue_event", {&"event_id": &"event.fixture.policy", &"actor_ids": [], &"options": options}).is_success(), "real Event queue accepts policy cause " + policy)
_advance(1)
var primary_id := _event_raid_id(&"event.instance.policy", primary.action_id)
var fallback_id := _event_raid_id(&"event.instance.policy", fallback.action_id)
var expected_id := primary_id if policy == "SKIP_FAILED" else fallback_id
var missing_id := fallback_id if policy == "SKIP_FAILED" else primary_id
_expect(_runtime.defense_service.get_raid_ids() == [expected_id] and _runtime.defense_service.get_raid(missing_id).is_empty(), "policy commits only the intended threat and discards rolled-back primary: " + policy)
_expect(_count_domain_events(&"DEFENSE_RAID_CREATED", expected_id) == 1 and _count_domain_events(&"DEFENSE_RAID_CREATED", missing_id) == 0, "policy leaves exactly one committed incident notification: " + policy)
var instance: EventInstance = _runtime.event_service.get_instance(&"event.instance.policy")
_expect(instance.state == (EventInstance.State.EXPIRED if policy == "EXPIRED" else EventInstance.State.COMPLETED), "policy preserves its real Event terminal state: " + policy)
var saved: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(saved) and _runtime.create_snapshot() == saved, "non-primary threat provenance restores exactly: " + policy)
func _test_event_rollback_preserves_failed_round_guard() -> void:
_reset_runtime(92_021)
_create_staff_and_facility(&"guard.event.failure", &"objective.event.failure")
_create_staff_and_facility(&"guard.event.uncommitted", &"objective.event.uncommitted")
var prior_definition := _threat_definition(&"objective.event.failure")
prior_definition[&"waves"][0][&"max_rounds"] = 1
_expect(_submit(&"create_defense_raid", {&"raid_id": &"raid.event.prior", &"definition": prior_definition}).is_success(), "real existing incident precedes Event rollback")
_expect(_register_defender(&"raid.event.prior", &"guard.event.failure", 90).is_success() and _register_objective(&"raid.event.prior", &"objective.event.failure", 20).is_success() and _submit(&"start_defense_raid", {&"raid_id": &"raid.event.prior"}).is_success(), "prior real defense has an automatic due round")
var failure := {&"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(report.get(&"ok", false)):
report[&"ok"] = false
report[&"error"] = "Disclosed real post-apply failure before Event rollback."
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(5)
_expect(_clock.paused and failure[&"calls"] == 1 and _runtime.defense_service.get_raid(&"raid.event.prior")[&"current_round"] == 0, "real automatic round fails atomically and pauses once")
_expect(_runtime.staff_service.create_task(&"fixture.work", 20, _clock.absolute_minute, {}, &"task.event.guard.duplicate").is_success(), "Event later apply-time failure uses real duplicate task")
var threat := _event_raid_action(&"objective.event.uncommitted", "THIEF")
var duplicate := EventAction.new().configure(&"fail.guard", EventAction.Type.CREATE_TASK, {&"task_id": &"task.event.guard.duplicate", &"task_type": &"fixture.work"})
_expect(_register_threat_event(&"event.fixture.guard", [threat, duplicate]).is_success() and _enqueue_threat_event(&"event.fixture.guard", &"event.instance.guard", &"cause.guard.fixture").is_success(), "real failed-minute Event registers and queues")
var defense_before: Dictionary = _runtime.defense_service.create_snapshot()
var rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot()
var failed_event: CommandResult = _runtime.event_service.start_next_event(_clock.absolute_minute)
_expect(not failed_event.is_success() and _runtime.defense_service.create_snapshot() == defense_before and _runtime.get_rng_stream(&"defense").snapshot() == rng_before, "Event rollback restores existing Defense and RNG after newly-created incident")
_expect(_count_domain_events(&"DEFENSE_AUTOMATIC_ROUND_FAILED", &"raid.event.prior") == 1 and _count_domain_events(&"DEFENSE_RAID_CREATED", _event_raid_id(&"event.instance.guard", threat.action_id)) == 0, "Event rollback keeps prior failure diagnostic while removing uncommitted creation")
_runtime._advance_defense(_clock.absolute_minute)
_expect(failure[&"calls"] == 1 and _runtime.defense_service.get_raid(&"raid.event.prior")[&"current_round"] == 0, "Event rollback cannot clear the real same-minute automatic attempt guard")
_runtime._configure_defense_boundaries()
_clock.set_paused(false)
_clock.advance_minutes(1)
_clock.set_paused(true)
_expect(_runtime.defense_service.get_raid(&"raid.event.prior")[&"current_round"] == 1, "next real minute resumes prior incident after its failed attempt")