Files
TheTree_mock/tests/s5/test_runtime_defense_cycle.gd
T
Takeshi 5dde0c5ec2 Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
2026-09-30 23:52:24 +09:00

1108 lines
43 KiB
GDScript

extends SceneTree
const PLAYER_CASH := &"PLAYER_CASH"
var _checks := 0
var _failures := 0
var _clock: Variant
var _state: Variant
var _runtime: Variant
var _command_sequence := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S5: integrated Defense runtime cycle")
_test_all_commands_multiwave_and_terminal_replay()
_test_scheduled_command_time_alignment()
_test_manual_retreat_cancel_and_long_horizon()
_test_rng_skill_and_outcome_atomicity()
_test_cross_raid_locks()
_test_snapshot_replay_corruption_and_legacy_migration()
_cleanup()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _reset_runtime(seed: int, hour := 6, minute := 0) -> void:
_cleanup()
_command_sequence = 0
_clock = load("res://autoload/game_clock.gd").new()
_state = load("res://autoload/game_state.gd").new()
_runtime = load("res://autoload/simulation_runtime.gd").new()
_clock.reset(1, hour, minute)
_state.reset()
var report: Dictionary = _runtime.initialize(_clock, _state, seed)
_clock.set_paused(true)
_expect(report[&"valid"], "runtime initializes with the Defense aggregate")
_expect(
_runtime.defense_service.current_minute == _clock.absolute_minute,
"Defense time begins aligned with SimulationKernel"
)
_expect(
_runtime.get_rng_stream(&"defense") != null
and _runtime.get_rng_stream(&"defense").get_stream_id() == &"defense",
"runtime owns a dedicated Defense RNG stream"
)
func _cleanup() -> void:
if _runtime != null:
_runtime.free()
if _clock != null:
_clock.free()
if _state != null:
_state.free()
_runtime = null
_clock = null
_state = null
func _test_all_commands_multiwave_and_terminal_replay() -> void:
_reset_runtime(7_101)
_create_staff_and_facility(&"guard.multi", &"objective.multi", 94, 12, 9_750)
var created := _submit(&"create_defense_raid", {
&"raid_id": &"raid.multi",
&"definition": _raid_definition(&"objective.multi", [
_wave(&"wave.alpha", 15, 1, [&"gate.north"]),
_wave(&"wave.beta", 20, 1, [&"gate.south"]),
]),
})
var defender := _submit(&"register_defense_combatant", {
&"raid_id": &"raid.multi",
&"actor_id": &"guard.multi",
&"side": &"defender",
&"combat": 90,
&"max_health": 100,
&"options": {&"armor": 3},
})
var attacker_alpha := _submit(&"register_defense_combatant", {
&"raid_id": &"raid.multi",
&"actor_id": &"raider.alpha",
&"side": &"ATTACKER",
&"combat": 15,
&"max_health": 20,
&"options": {
&"wave_id": &"wave.alpha",
&"entry_point_id": &"gate.north",
},
})
var attacker_beta := _submit(&"register_defense_combatant", {
&"raid_id": &"raid.multi",
&"actor_id": &"raider.beta",
&"side": &"ATTACKER",
&"combat": 20,
&"max_health": 20,
&"options": {
&"wave_id": &"wave.beta",
&"entry_point_id": &"gate.south",
},
})
var asset := _submit(&"register_defense_asset", {
&"raid_id": &"raid.multi",
&"asset_id": &"objective.multi",
&"kind": &"objective",
&"max_integrity": 10_000,
&"defense": 40,
&"options": {&"target_order": 1, &"armor": 2},
})
var policy := _submit(&"set_defense_retreat_policy", {
&"raid_id": &"raid.multi",
&"policy": &"threshold",
&"threshold": 5,
})
var started := _submit(&"start_defense_raid", {&"raid_id": &"raid.multi"})
var skill := _submit(&"queue_defense_skill", {
&"raid_id": &"raid.multi",
&"action_id": &"skill.multi.rally",
&"actor_id": &"guard.multi",
&"kind": &"rally",
&"options": {&"potency": 7, &"metadata": {&"label": "free"}},
})
_expect(
created.is_success() and defender.is_success() and attacker_alpha.is_success()
and attacker_beta.is_success() and asset.is_success() and policy.is_success()
and started.is_success() and skill.is_success(),
"create, register, policy, start and skill Commands complete through Runtime"
)
_expect(
created.command_id > 0 and created.target_id == &"raid.multi"
and defender.command_id > created.command_id,
"Defense Command results preserve Runtime command_id and target conventions"
)
var early := _submit(&"resolve_defense_round", {
&"raid_id": &"raid.multi", &"resolution_id": &"round.multi.early",
})
_expect(not early.is_success(), "a round before its configured interval is rejected")
var first_round_attempt := _submit_round_at_due({
&"raid_id": &"raid.multi", &"resolution_id": &"round.multi.1",
})
var first_round: CommandResult = first_round_attempt[&"result"]
var after_first: Dictionary = _runtime.defense_service.get_raid(&"raid.multi")
_expect(
first_round.is_success()
and int(after_first[&"state"]) == DefenseService.RaidState.BETWEEN_WAVES
and int(after_first[&"current_round"]) == 1,
"first max-round Wave resolves and waits for an explicit transition"
)
_expect(
_runtime.facility_service.get_instance(&"objective.multi").durability == 9_750,
"non-terminal round damage stays inside Defense until terminal reconciliation"
)
var next_wave := _submit(&"begin_next_defense_wave", {&"raid_id": &"raid.multi"})
_expect(
next_wave.is_success()
and int(_runtime.defense_service.get_raid(&"raid.multi")[&"state"]) == DefenseService.RaidState.ACTIVE,
"begin_next_defense_wave is the required explicit Wave transition"
)
var same_minute := _submit(&"resolve_defense_round", {
&"raid_id": &"raid.multi", &"resolution_id": &"round.multi.same-minute",
})
_expect(not same_minute.is_success(), "a different round ID cannot resolve again in the same minute")
var terminal_attempt := _submit_round_at_due({
&"raid_id": &"raid.multi", &"resolution_id": &"round.multi.2",
})
var terminal: CommandResult = terminal_attempt[&"result"]
var terminal_raid: Dictionary = _runtime.defense_service.get_raid(&"raid.multi")
var linked_staff: StaffRuntime = _runtime.staff_service.get_staff(&"guard.multi")
var linked_facility: FacilityInstance = _runtime.facility_service.get_instance(&"objective.multi")
_expect(
terminal.is_success()
and int(terminal_raid[&"state"]) == DefenseService.RaidState.DEFENDED,
"second Wave reaches a terminal Raid result through resolve_defense_round"
)
_expect(
linked_staff.health == int(terminal_raid[&"combatants"][&"guard.multi"][&"health"])
and linked_staff.fatigue == int(terminal_raid[&"combatants"][&"guard.multi"][&"fatigue"])
and linked_staff.state == StaffRuntime.State.AVAILABLE,
"terminal combatant outcome reconciles Staff health, fatigue and state"
)
_expect(
linked_facility.durability == int(terminal_raid[&"assets"][&"objective.multi"][&"integrity"]),
"terminal asset outcome reconciles Facility durability"
)
var rng_after: Dictionary = _runtime.get_rng_stream(&"defense").snapshot()
var staff_after: Dictionary = _runtime.staff_service.create_snapshot()
var facility_after: Dictionary = _runtime.facility_service.create_snapshot()
var resolved_events := _count_domain_events(&"DEFENSE_RAID_RESOLVED", &"raid.multi")
var replay := _submit(&"resolve_defense_round", {
&"raid_id": &"raid.multi", &"resolution_id": &"round.multi.2",
})
_expect(replay.is_success() and replay.is_replay, "duplicate round resolution replays its canonical result")
_expect(
_runtime.get_rng_stream(&"defense").snapshot() == rng_after
and _runtime.staff_service.create_snapshot() == staff_after
and _runtime.facility_service.create_snapshot() == facility_after,
"resolution replay does not consume RNG or reapply external outcomes"
)
_expect(
_count_domain_events(&"DEFENSE_RAID_RESOLVED", &"raid.multi") == resolved_events,
"resolution replay emits no duplicate Defense resolved event"
)
var wrong_operation := _submit(&"retreat_defenders", {
&"raid_id": &"raid.multi", &"resolution_id": &"round.multi.2",
})
_expect(not wrong_operation.is_success(), "an explicit resolution ID cannot cross operation namespaces")
_expect(
_defense_terminal_event_order(&"raid.multi", &"round.multi.2"),
"terminal Defense DomainEvents are structured and emitted in causal order"
)
func _test_manual_retreat_cancel_and_long_horizon() -> void:
_reset_runtime(7_202)
_create_staff_and_facility(&"guard.retreat", &"objective.retreat")
_prepare_started_raid(
&"raid.retreat", &"guard.retreat", &"objective.retreat",
[_wave(&"wave.retreat", 150, 8, [&"gate.north"])]
)
var retreat := _submit(&"retreat_defenders", {&"raid_id": &"raid.retreat"})
_expect(
retreat.is_success()
and String(retreat.details.get(&"resolution_id", "")).begins_with("defense_retreat_resolution.")
and int(_runtime.defense_service.get_raid(&"raid.retreat")[&"state"])
== DefenseService.RaidState.DEFENDERS_RETREATED,
"manual retreat uses its stable operation-specific default resolution ID"
)
_expect(
_runtime.staff_service.get_staff(&"guard.retreat").state == StaffRuntime.State.FLEEING,
"manual retreat projects the Defender state to Staff"
)
_create_staff_and_facility(&"guard.cancel", &"objective.cancel", 100, 3, 9_900)
_prepare_started_raid(
&"raid.cancel", &"guard.cancel", &"objective.cancel",
[_wave(&"wave.cancel", 600, 10, [&"gate.north"])],
25,
0,
{&"target_order": 1}
)
var partial_attempt := _submit_round_at_due({
&"raid_id": &"raid.cancel", &"resolution_id": &"round.cancel.partial",
})
var partial: CommandResult = partial_attempt[&"result"]
var partial_raid: Dictionary = _runtime.defense_service.get_raid(&"raid.cancel")
_expect(
partial.is_success()
and int(partial_raid[&"state"]) == DefenseService.RaidState.ACTIVE,
"a long Wave can retain accumulated round state before cancellation"
)
_expect(
_runtime.facility_service.get_instance(&"objective.cancel").durability == 9_900,
"accumulated active-Raid damage is not prematurely projected"
)
var cancel := _submit(&"cancel_defense_raid", {&"raid_id": &"raid.cancel"})
var cancelled_raid: Dictionary = _runtime.defense_service.get_raid(&"raid.cancel")
_expect(
cancel.is_success()
and String(cancel.details.get(&"resolution_id", "")).begins_with("defense_cancel_resolution.")
and int(cancelled_raid[&"state"]) == DefenseService.RaidState.CANCELLED,
"active Raid cancellation uses the terminal transaction boundary"
)
_expect(
_runtime.staff_service.get_staff(&"guard.cancel").health
== int(cancelled_raid[&"combatants"][&"guard.cancel"][&"health"])
and _runtime.facility_service.get_instance(&"objective.cancel").durability
== int(cancelled_raid[&"assets"][&"objective.cancel"][&"integrity"]),
"cancel reconciles all damage accumulated in earlier rounds"
)
_create_staff_and_facility(&"guard.long", &"objective.long")
var long_definition := _raid_definition(
&"objective.long",
[_wave(&"wave.long", 20_000, 400, [&"gate.north"])],
5,
5
)
_prepare_started_raid(
&"raid.long", &"guard.long", &"objective.long",
long_definition[&"waves"], 1, 0, {}, 100, long_definition
)
var long_rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot()
_advance(1_440)
var long_raid: Dictionary = _runtime.defense_service.get_raid(&"raid.long")
_expect(
_runtime.defense_service.current_minute == _clock.absolute_minute
and int(long_raid[&"current_round"]) == 1
and int(long_raid[&"state"]) == DefenseService.RaidState.DEFENDED
and int(long_raid[&"elapsed_minutes"]) == 5,
"ordinary ticking automatically reaches the existing max-duration boundary once"
)
_expect(
_runtime.get_rng_stream(&"defense").snapshot() != long_rng_before
and int(long_raid[&"finished_at"]) == int(long_raid[&"started_at"]) + 5,
"long horizon consumes combat RNG only for the configured due round"
)
var duration_payload: Dictionary = (long_raid[&"resolved_rounds"] as Dictionary).values()[0]
_expect(
duration_payload.get(&"reason", &"") == &"RAID_DURATION_EXHAUSTED",
"the automatic round applies the same max-duration outcome as the command API"
)
_create_staff_and_facility(&"guard.breach", &"objective.breach", 100, 0, 1)
_prepare_started_raid(
&"raid.breach", &"guard.breach", &"objective.breach",
[_wave(&"wave.breach", 1_000, 1, [&"gate.north"])],
0,
0,
{&"target_order": 1},
1_000
)
var breached_attempt := _submit_round_at_due({
&"raid_id": &"raid.breach", &"resolution_id": &"round.breach.terminal",
})
var breached: CommandResult = breached_attempt[&"result"]
_expect(
breached.is_success()
and int(_runtime.defense_service.get_raid(&"raid.breach")[&"state"])
== DefenseService.RaidState.BREACHED
and _runtime.facility_service.get_instance(&"objective.breach").durability == 0
and _runtime.facility_service.get_instance(&"objective.breach").state
== FacilityInstance.State.BROKEN,
"destroyed Defense objective atomically projects Facility durability and state"
)
func _test_scheduled_command_time_alignment() -> void:
_reset_runtime(7_202)
_create_staff_and_facility(&"guard.scheduled", &"objective.scheduled")
_prepare_started_raid(
&"raid.scheduled", &"guard.scheduled", &"objective.scheduled",
[_wave(&"wave.scheduled", 1, 1, [&"gate.north"])],
100,
100,
{&"target_order": 1},
0
)
var execute_at: int = int(_clock.absolute_minute) + 5
var enqueued: CommandResult = _runtime.submit_command(
&"resolve_defense_round",
{&"raid_id": &"raid.scheduled", &"resolution_id": &"round.scheduled.1"},
&"defense_controller",
&"raid.scheduled",
80,
execute_at,
&"runtime.defense.scheduled.round"
)
_clock.set_paused(false)
_clock.advance_minutes(4)
_expect(
enqueued.is_success()
and _runtime.command_queue.get_processed_result(enqueued.command_id) == null
and _runtime.defense_service.current_minute == execute_at - 1,
"scheduled Defense round remains pending until its exact configured minute"
)
_clock.advance_minutes(1)
_clock.set_paused(true)
var completed: CommandResult = _runtime.command_queue.get_processed_result(enqueued.command_id)
_expect(
completed != null and completed.is_success()
and int(completed.details.get(&"absolute_minute", -1)) == execute_at
and _clock.absolute_minute == execute_at
and _runtime.defense_service.current_minute == execute_at,
"scheduled Defense command resolves against the same absolute minute as the completed tick"
)
var event_minutes: Dictionary = {}
for event: SimulationDomainEvent in _runtime.domain_events.recent_events():
if &"raid.scheduled" not in event.entity_ids:
continue
if event.event_type in [
&"DEFENSE_ROUND_RESOLVED",
&"DEFENSE_RAID_STATE_CHANGED",
&"DEFENSE_RAID_RESOLVED",
&"COMMAND_COMPLETED",
]:
event_minutes[event.event_type] = event.absolute_minute
_expect(
event_minutes.size() == 4
and event_minutes.values().all(func(value: Variant) -> bool: return int(value) == execute_at),
"scheduled Defense outcome and Command DomainEvents share the canonical execution minute"
)
func _test_rng_skill_and_outcome_atomicity() -> void:
_reset_runtime(7_303)
_create_staff_and_facility(&"guard.atomic", &"objective.atomic", 100, 4, 9_800)
_expect(
bool(_runtime.inventory_service.add_stack(
&"defense.inventory", &"mana_bomb", 5, 100, -1, _clock.absolute_minute, 1, &"stack.atomic"
).get(&"ok", false)),
"skill-cost Inventory fixture is available"
)
_prepare_started_raid(
&"raid.atomic", &"guard.atomic", &"objective.atomic",
[_wave(&"wave.atomic", 500, 1, [&"gate.north"])],
10,
0,
{&"target_order": 1}
)
var queued := _submit(&"queue_defense_skill", {
&"raid_id": &"raid.atomic",
&"actor_id": &"guard.atomic",
&"kind": &"POWER_STRIKE",
&"options": {
&"potency": 10,
&"metadata": {&"cost": {
&"gold": 125,
&"mana": 9,
&"items": [{
&"inventory_id": &"defense.inventory",
&"item_id": &"mana_bomb",
&"quantity": 2,
}],
}},
},
})
_expect(
queued.is_success()
and String(queued.details.get(&"action_id", "")).begins_with("defense_skill_action."),
"queued skill receives a stable generated action namespace"
)
var defense_before: Dictionary = _runtime.defense_service.create_snapshot()
var defense_rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot()
var authority_rng_before: Dictionary = _runtime.get_rng_stream(&"authority").snapshot()
var economy_before: Dictionary = _runtime.economy_service.create_snapshot()
var state_before: Dictionary = _state.create_snapshot()
var inventory_before: Dictionary = _runtime.inventory_service.create_snapshot()
var staff_before: Dictionary = _runtime.staff_service.create_snapshot()
var facilities_before: Dictionary = _runtime.facility_service.create_snapshot()
var expected_rng := DeterministicRng.from_root_seed(0, &"defense") as DeterministicRng
expected_rng.restore(defense_rng_before)
var expected_defense_roll := expected_rng.randi_range(0, 100)
var expected_attack_roll := expected_rng.randi_range(0, 100)
var failure_switch := {&"armed": true}
var runtime_outcome := Callable(_runtime, "_apply_defense_outcome_boundary")
var failing_outcome := func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
var report: Dictionary = runtime_outcome.call(payload, phase, token)
if phase == &"apply" and bool(report.get(&"ok", false)) and bool(failure_switch[&"armed"]):
failure_switch[&"armed"] = false
report = report.duplicate(true)
report[&"ok"] = false
report[&"error"] = "Injected post-apply outcome failure."
return report
_runtime.defense_service.configure_callbacks(
Callable(_runtime, "_roll_defense_round"),
Callable(_runtime, "_apply_defense_skill_boundary"),
failing_outcome,
Callable(_runtime, "_apply_defense_rng_boundary")
)
var failed_attempt := _submit_round_at_due({
&"raid_id": &"raid.atomic", &"resolution_id": &"round.atomic.retry",
})
var failed: CommandResult = failed_attempt[&"result"]
var atomic_before: Dictionary = failed_attempt[&"before"]
var atomic_after: Dictionary = failed_attempt[&"after"]
defense_before = atomic_before[&"defense"]
defense_rng_before = atomic_before[&"defense_rng"]
authority_rng_before = atomic_before[&"authority_rng"]
economy_before = atomic_before[&"economy"]
state_before = atomic_before[&"state"]
inventory_before = atomic_before[&"inventory"]
staff_before = atomic_before[&"staff"]
facilities_before = atomic_before[&"facilities"]
_expect(not failed.is_success(), "an injected post-apply terminal failure rejects the round")
_expect(
atomic_after[&"defense"] == defense_before
and atomic_after[&"defense_rng"] == defense_rng_before
and atomic_after[&"authority_rng"] == authority_rng_before,
"failed outcome restores Defense state and its RNG without touching other streams"
)
_expect(
atomic_after[&"economy"] == economy_before
and atomic_after[&"state"] == state_before
and atomic_after[&"inventory"] == inventory_before,
"failed outcome restores Gold, Mana, item reservations and consumption"
)
_expect(
atomic_after[&"staff"] == staff_before
and atomic_after[&"facilities"] == facilities_before,
"failed outcome restores Staff and Facility changes atomically"
)
_expect(
_count_domain_events(&"LEDGER_TRANSACTION_POSTED") == 0
and _count_domain_events(&"DEFENSE_RAID_RESOLVED", &"raid.atomic") == 0,
"rolled-back skill and outcome effects leave no committed success events"
)
_runtime._configure_defense_boundaries()
var retried := _submit(&"resolve_defense_round", {
&"raid_id": &"raid.atomic", &"resolution_id": &"round.atomic.retry",
})
_expect(
retried.is_success()
and int(retried.details[&"defense_roll"]) == expected_defense_roll
and int(retried.details[&"attack_roll"]) == expected_attack_roll,
"retry consumes the exact rolls predicted before the failed transaction"
)
_expect(
_runtime.economy_service.get_balance(PLAYER_CASH) == 5_000 - 125
and _state.get_resource(&"gold") == 5_000 - 125
and _state.get_resource(&"mana") == 100 - 9
and _runtime.inventory_service.available_quantity(
&"defense.inventory", &"mana_bomb", _clock.absolute_minute
) == 3,
"successful retry commits explicit Gold, Mana and item costs once"
)
_expect(
_runtime.get_rng_stream(&"authority").snapshot() == authority_rng_before
and _runtime.get_rng_stream(&"defense").snapshot() == expected_rng.snapshot(),
"successful round advances only the Defense RNG to its exact predicted state"
)
_reset_runtime(7_304)
_create_staff_and_facility(&"guard.reserve", &"objective.reserve")
_expect(
bool(_runtime.inventory_service.add_stack(
&"reserve.inventory", &"a_available", 1, 100, -1,
_clock.absolute_minute, 1, &"stack.reserve.available"
).get(&"ok", false)),
"partial-reservation rollback fixture is available"
)
_prepare_started_raid(
&"raid.reserve", &"guard.reserve", &"objective.reserve",
[_wave(&"wave.reserve", 100, 1, [&"gate.north"])]
)
var reservation_skill := _submit(&"queue_defense_skill", {
&"raid_id": &"raid.reserve",
&"action_id": &"skill.reserve.partial",
&"actor_id": &"guard.reserve",
&"kind": &"RALLY",
&"options": {&"potency": 1, &"metadata": {&"cost": {&"items": [
{&"inventory_id": &"reserve.inventory", &"item_id": &"a_available", &"quantity": 1},
{&"inventory_id": &"reserve.inventory", &"item_id": &"z_missing", &"quantity": 1},
]}}},
})
_expect(reservation_skill.is_success(), "multi-item skill cost queues before boundary validation")
var reserve_defense_before: Dictionary = _runtime.defense_service.create_snapshot()
var reserve_rng_before: Dictionary = _runtime.get_rng_stream(&"defense").snapshot()
var reserve_inventory_before: Dictionary = _runtime.inventory_service.create_snapshot()
var reserve_economy_before: Dictionary = _runtime.economy_service.create_snapshot()
var reserve_state_before: Dictionary = _state.create_snapshot()
var reservation_attempt := _submit_round_at_due({
&"raid_id": &"raid.reserve", &"resolution_id": &"round.reserve.failed",
})
var reservation_failed: CommandResult = reservation_attempt[&"result"]
var reserve_before: Dictionary = reservation_attempt[&"before"]
var reserve_after: Dictionary = reservation_attempt[&"after"]
reserve_defense_before = reserve_before[&"defense"]
reserve_rng_before = reserve_before[&"defense_rng"]
reserve_inventory_before = reserve_before[&"inventory"]
reserve_economy_before = reserve_before[&"economy"]
reserve_state_before = reserve_before[&"state"]
_expect(not reservation_failed.is_success(), "a later unavailable item rejects the whole skill-cost batch")
_expect(
reserve_after[&"inventory"] == reserve_inventory_before
and reserve_after[&"economy"] == reserve_economy_before
and reserve_after[&"state"] == reserve_state_before,
"partial item reservation failure rolls every external resource back"
)
_expect(
reserve_after[&"defense"] == reserve_defense_before
and reserve_after[&"defense_rng"] == reserve_rng_before,
"skill validation failure leaves Defense and round RNG untouched"
)
func _test_cross_raid_locks() -> void:
_reset_runtime(7_404)
_create_staff_and_facility(&"guard.lock", &"objective.lock")
for raid_id: StringName in [&"raid.lock.a", &"raid.lock.b"]:
var created := _submit(&"create_defense_raid", {
&"raid_id": raid_id,
&"definition": _raid_definition(
&"objective.lock", [_wave(StringName("wave.%s" % raid_id), 50, 4, [&"gate.north"])]
),
})
_expect(created.is_success(), "lock fixture Raid is created: %s" % raid_id)
var mismatched_defender := _submit(&"register_defense_combatant", {
&"raid_id": &"raid.lock.a",
&"actor_id": &"guard.lock",
&"side": &"DEFENDER",
&"combat": 50,
&"max_health": 100,
&"options": {&"health": 99},
})
var mismatched_asset := _submit(&"register_defense_asset", {
&"raid_id": &"raid.lock.a",
&"asset_id": &"objective.lock",
&"kind": &"OBJECTIVE",
&"max_integrity": 9_999,
&"defense": 20,
&"options": {},
})
_expect(
not mismatched_defender.is_success() and not mismatched_asset.is_success(),
"registration rejects values that differ from Staff and Facility baselines"
)
var first_defender := _register_defender(&"raid.lock.a", &"guard.lock", 50)
var first_asset := _register_objective(&"raid.lock.a", &"objective.lock", 20)
var second_defender := _register_defender(&"raid.lock.b", &"guard.lock", 50)
var second_asset := _register_objective(&"raid.lock.b", &"objective.lock", 20)
_expect(first_defender.is_success() and first_asset.is_success(), "first non-terminal Raid acquires Staff and Facility locks")
_expect(
not second_defender.is_success() and not second_asset.is_success(),
"a second non-terminal Raid cannot reuse locked Defender or Facility IDs"
)
var demolish := _submit(&"demolish_facility", {&"instance_id": &"objective.lock"})
_expect(not demolish.is_success(), "a Defense-locked Facility cannot be demolished")
var cancelled := _submit(&"cancel_defense_raid", {
&"raid_id": &"raid.lock.a", &"resolution_id": &"cancel.lock.release",
})
_expect(cancelled.is_success(), "terminal cancellation releases derived Raid locks")
second_defender = _register_defender(&"raid.lock.b", &"guard.lock", 50)
second_asset = _register_objective(&"raid.lock.b", &"objective.lock", 20)
_expect(
second_defender.is_success() and second_asset.is_success(),
"released locks are deterministically acquired by the remaining Raid"
)
var snapshot: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(snapshot), "snapshot restore rebuilds Defense locks without a mutable lock table")
var health_corruption: Dictionary = snapshot.duplicate(true)
var health_raids: Array = health_corruption[&"systems"][&"runtime"][&"defense"][&"raids"]
for raw_raid: Variant in health_raids:
if String(raw_raid.get(&"raid_id", "")) != "raid.lock.b":
continue
for raw_combatant: Variant in raw_raid[&"combatants"]:
if String(raw_combatant.get(&"actor_id", "")) == "guard.lock":
raw_combatant[&"health"] = 99
_expect(
not _runtime.restore_snapshot(health_corruption)
and _runtime.create_snapshot() == snapshot,
"restore rejects a PREPARING Defender health baseline mismatch atomically"
)
var integrity_corruption: Dictionary = snapshot.duplicate(true)
var integrity_raids: Array = integrity_corruption[&"systems"][&"runtime"][&"defense"][&"raids"]
for raw_raid: Variant in integrity_raids:
if String(raw_raid.get(&"raid_id", "")) != "raid.lock.b":
continue
for raw_asset: Variant in raw_raid[&"assets"]:
if String(raw_asset.get(&"asset_id", "")) == "objective.lock":
raw_asset[&"integrity"] = 9_999
_expect(
not _runtime.restore_snapshot(integrity_corruption)
and _runtime.create_snapshot() == snapshot,
"restore rejects a PREPARING Facility integrity baseline mismatch atomically"
)
var duplicate_after_restore := _submit(&"create_defense_raid", {
&"raid_id": &"raid.lock.c",
&"definition": _raid_definition(
&"objective.lock", [_wave(&"wave.lock.c", 30, 3, [&"gate.north"])]
),
})
var locked_after_restore := _register_defender(&"raid.lock.c", &"guard.lock", 20)
_expect(
duplicate_after_restore.is_success() and not locked_after_restore.is_success(),
"restored non-terminal ownership continues to enforce Staff exclusivity"
)
var second_cancel := _submit(&"cancel_defense_raid", {
&"raid_id": &"raid.lock.b", &"resolution_id": &"cancel.lock.second",
})
_runtime.staff_service.get_staff(&"guard.lock").fatigue = 17
var repaired: CommandResult = _runtime.facility_service.set_operational_status(
&"objective.lock", {&"durability": 9_850}, _clock.absolute_minute
)
var reused_defender := _register_defender(&"raid.lock.c", &"guard.lock", 30)
var reused_asset := _register_objective(&"raid.lock.c", &"objective.lock", 10)
var evolved_snapshot: Dictionary = _runtime.create_snapshot()
_expect(
second_cancel.is_success() and repaired.is_success()
and reused_defender.is_success() and reused_asset.is_success(),
"terminal Raid history does not block later Staff reuse or Facility repair"
)
_expect(
_runtime.restore_snapshot(evolved_snapshot),
"terminal outcomes remain historical while the newer live ownership restores"
)
func _test_snapshot_replay_corruption_and_legacy_migration() -> void:
_reset_runtime(7_505)
_create_staff_and_facility(&"guard.save", &"objective.save", 97, 6, 9_600)
_prepare_started_raid(
&"raid.save", &"guard.save", &"objective.save",
[_wave(&"wave.save", 2_000, 5, [&"gate.north"])],
80,
0,
{&"target_order": 1}
)
var first_attempt := _submit_round_at_due({
&"raid_id": &"raid.save", &"resolution_id": &"round.save.1",
})
var first: CommandResult = first_attempt[&"result"]
_expect(
first.is_success()
and int(_runtime.defense_service.get_raid(&"raid.save")[&"state"])
== DefenseService.RaidState.ACTIVE,
"mid-Raid fixture preserves an active Wave after its first round"
)
var queued := _submit(&"queue_defense_skill", {
&"raid_id": &"raid.save",
&"action_id": &"skill.save.second",
&"actor_id": &"guard.save",
&"kind": &"FORTIFY",
&"options": {
&"potency": 8,
&"target_id": &"objective.save",
&"metadata": {&"cost": {&"gold": 0, &"mana": 0}},
},
})
_expect(queued.is_success(), "queued next-round skill is included in the mid-Raid snapshot")
var origin: Dictionary = _runtime.create_snapshot()
var first_branch_attempt := _submit_round_at_due({
&"raid_id": &"raid.save", &"resolution_id": &"round.save.2",
}, &"runtime.defense.save.resolve")
var first_branch: CommandResult = first_branch_attempt[&"result"]
var branch_snapshot: Dictionary = _runtime.create_snapshot()
_expect(first_branch.is_success(), "first post-save Defense branch resolves")
_expect(_runtime.restore_snapshot(origin), "mid-round Runtime snapshot restores atomically")
var replay_branch_attempt := _submit_round_at_due({
&"raid_id": &"raid.save", &"resolution_id": &"round.save.2",
}, &"runtime.defense.save.resolve")
var replay_branch: CommandResult = replay_branch_attempt[&"result"]
_expect(
replay_branch.is_success() and replay_branch.details == first_branch.details,
"restored mid-round branch reproduces the exact Defense resolution"
)
_expect(
_runtime.create_snapshot() == branch_snapshot,
"save/load reproduces Defense, RNG, costs, events and command state exactly"
)
var retiring_service: DefenseService = _runtime.defense_service
var detach_origin: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(detach_origin), "same-state restore replaces the public Defense aggregate")
var runtime_before_stale_mutation: Dictionary = _runtime.create_snapshot()
var ghost_created := retiring_service.create_raid(
&"raid.stale.ghost",
retiring_service.current_minute,
_raid_definition(&"objective.ghost", [_wave(&"wave.ghost", 10, 1, [&"gate.north"])])
)
retiring_service.advance_time(retiring_service.current_minute + 5)
var stale_round := retiring_service.resolve_next_round(
&"raid.save", &"round.save.stale", retiring_service.current_minute
)
_expect(
ghost_created.is_success() and stale_round.is_success()
and _runtime.create_snapshot() == runtime_before_stale_mutation,
"retired Defense references cannot consume Runtime boundaries or emit ghost events"
)
var live_before_corruption: Dictionary = _runtime.create_snapshot()
var missing_current_defense: Dictionary = live_before_corruption.duplicate(true)
missing_current_defense[&"systems"][&"runtime"].erase(&"defense")
missing_current_defense[&"systems"][&"runtime"][&"rng_streams"].erase("defense")
_expect(
not _runtime.restore_snapshot(missing_current_defense)
and _runtime.create_snapshot() == live_before_corruption,
"current Runtime format requires Defense state and rejects silent data loss"
)
var corrupt_minute: Dictionary = live_before_corruption.duplicate(true)
corrupt_minute[&"systems"][&"runtime"][&"defense"][&"current_minute"] = _clock.absolute_minute - 1
_expect(
not _runtime.restore_snapshot(corrupt_minute)
and _runtime.create_snapshot() == live_before_corruption,
"corrupt Defense clock is rejected without changing any live Domain"
)
var corrupt_rng_stream: Dictionary = live_before_corruption.duplicate(true)
corrupt_rng_stream[&"systems"][&"runtime"][&"rng_streams"]["defense"]["stream_id"] = "authority"
_expect(
not _runtime.restore_snapshot(corrupt_rng_stream)
and _runtime.create_snapshot() == live_before_corruption,
"Defense RNG stream identity mismatch is rejected atomically"
)
var corrupt_rng_seed: Dictionary = live_before_corruption.duplicate(true)
corrupt_rng_seed[&"systems"][&"runtime"][&"rng_streams"]["defense"]["root_seed"] = 999_999
_expect(
not _runtime.restore_snapshot(corrupt_rng_seed)
and _runtime.create_snapshot() == live_before_corruption,
"Defense RNG root-seed mismatch is rejected atomically"
)
var corrupt_staff: Dictionary = live_before_corruption.duplicate(true)
var saved_staff: Array = corrupt_staff[&"systems"][&"runtime"][&"staff"][&"staff"]
for index: int in range(saved_staff.size()):
if String(saved_staff[index].get(&"staff_id", "")) == "guard.save":
saved_staff.remove_at(index)
break
_expect(
not _runtime.restore_snapshot(corrupt_staff)
and _runtime.create_snapshot() == live_before_corruption,
"missing external Staff crosslink rejects the snapshot atomically"
)
var corrupt_facility: Dictionary = live_before_corruption.duplicate(true)
var saved_facilities: Array = corrupt_facility[&"systems"][&"runtime"][&"facilities"][&"instances"]
for index: int in range(saved_facilities.size()):
if String(saved_facilities[index].get(&"instance_id", "")) == "objective.save":
saved_facilities.remove_at(index)
break
_expect(
not _runtime.restore_snapshot(corrupt_facility)
and _runtime.create_snapshot() == live_before_corruption,
"missing external Facility crosslink rejects the snapshot atomically"
)
_reset_runtime(7_606, 9, 17)
var legacy: Dictionary = _runtime.create_snapshot()
legacy[&"systems"][&"runtime"].erase(&"runtime_schema_version")
legacy[&"systems"][&"runtime"].erase(&"kitchen_production")
legacy[&"systems"][&"runtime"].erase(&"elevators")
legacy[&"systems"][&"runtime"].erase(&"legacy_stairs")
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy[&"systems"][&"runtime"])
legacy[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
legacy[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
legacy[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_requires_connector")
legacy[&"systems"][&"runtime"][&"path"].erase(&"transit_actor_ids")
legacy[&"systems"][&"runtime"].erase(&"operations")
legacy[&"systems"][&"runtime"].erase(&"defense")
legacy[&"systems"][&"runtime"][&"rng_streams"].erase("defense")
for infrastructure_key: StringName in [
&"excavation", &"spatial_subslots", &"route_projection",
&"facility_infrastructure",
]:
legacy[&"systems"][&"runtime"].erase(infrastructure_key)
_expect(_runtime.restore_snapshot(legacy), "pre-Defense Runtime snapshot migrates without synthetic Raid state")
_expect(
_runtime.defense_service.get_raid_ids().is_empty()
and _runtime.defense_service.current_minute == _clock.absolute_minute,
"legacy migration installs an empty clock-aligned Defense aggregate"
)
var migrated_rng: DeterministicRng = _runtime.get_rng_stream(&"defense")
var expected_rng := DeterministicRng.from_root_seed(7_606, &"defense") as DeterministicRng
_expect(
migrated_rng.snapshot() == expected_rng.snapshot(),
"legacy migration derives the missing Defense RNG from the saved world seed"
)
var after_legacy: Dictionary = _runtime.create_snapshot()
_expect(
after_legacy[&"systems"][&"runtime"].has(&"defense")
and after_legacy[&"systems"][&"runtime"][&"rng_streams"].has("defense"),
"the next current-format snapshot persists migrated Defense state"
)
func _create_staff_and_facility(
staff_id: StringName,
facility_id: StringName,
health := 100,
fatigue := 0,
durability := 10_000
) -> void:
var staff := _submit(&"register_staff", {
&"staff_id": staff_id,
&"role_id": &"security",
&"options": {
&"state": StaffRuntime.State.AVAILABLE,
&"health": health,
&"fatigue": fatigue,
&"skills": {&"combat": 100},
},
})
var facility: CommandResult = _runtime.facility_service.create_instance({
&"instance_id": facility_id,
&"definition_id": &"defense_fixture",
&"durability": durability,
&"created_at": _clock.absolute_minute,
})
_expect(staff.is_success(), "external Staff fixture is registered: %s" % staff_id)
_expect(facility.is_success(), "external Facility fixture is registered: %s" % facility_id)
func _prepare_started_raid(
raid_id: StringName,
staff_id: StringName,
facility_id: StringName,
waves: Array,
defender_combat := 50,
asset_defense := 20,
asset_options: Dictionary = {},
attacker_combat := 0,
definition_override: Dictionary = {}
) -> void:
var definition := definition_override.duplicate(true) if not definition_override.is_empty() \
else _raid_definition(facility_id, waves)
var created := _submit(&"create_defense_raid", {
&"raid_id": raid_id,
&"definition": definition,
})
var defender := _register_defender(raid_id, staff_id, defender_combat)
var asset := _register_objective(raid_id, facility_id, asset_defense, asset_options)
var wave_index := 0
for wave_value: Variant in waves:
var wave: Dictionary = wave_value
var wave_entries: Array = wave.get(&"entry_points", [&"gate.north"])
var attacker := _submit(&"register_defense_combatant", {
&"raid_id": raid_id,
&"actor_id": StringName("attacker.%s.%d" % [raid_id, wave_index]),
&"side": &"ATTACKER",
&"combat": attacker_combat,
&"max_health": 100,
&"options": {
&"wave_id": StringName(wave[&"wave_id"]),
&"entry_point_id": StringName(wave_entries[0]),
},
})
_expect(attacker.is_success(), "synthetic attacker is registered for %s" % String(wave[&"wave_id"]))
wave_index += 1
var started := _submit(&"start_defense_raid", {&"raid_id": raid_id})
_expect(
created.is_success() and defender.is_success() and asset.is_success() and started.is_success(),
"Raid fixture starts with validated external links: %s" % raid_id
)
func _register_defender(raid_id: StringName, staff_id: StringName, combat: int) -> CommandResult:
return _submit(&"register_defense_combatant", {
&"raid_id": raid_id,
&"actor_id": staff_id,
&"side": &"DEFENDER",
&"combat": combat,
&"max_health": 100,
&"options": {&"armor": 2},
})
func _register_objective(
raid_id: StringName,
facility_id: StringName,
defense: int,
options: Dictionary = {}
) -> CommandResult:
return _submit(&"register_defense_asset", {
&"raid_id": raid_id,
&"asset_id": facility_id,
&"kind": &"OBJECTIVE",
&"max_integrity": 10_000,
&"defense": defense,
&"options": options.duplicate(true),
})
func _raid_definition(
objective_id: StringName,
waves: Array,
round_minutes := 5,
max_duration_minutes := 180
) -> Dictionary:
return {
&"source_id": &"runtime_test",
&"entry_points": [&"gate.north", &"gate.south"],
&"objective_ids": [objective_id],
&"waves": waves.duplicate(true),
&"round_minutes": round_minutes,
&"max_duration_minutes": max_duration_minutes,
&"retreat_policy": &"HOLD",
&"defender_retreat_threshold": 0,
}
func _wave(
wave_id: StringName,
threat_strength: int,
max_rounds: int,
entry_points: Array
) -> Dictionary:
return {
&"wave_id": wave_id,
&"threat_strength": threat_strength,
&"retreat_threshold": 0,
&"max_rounds": max_rounds,
&"entry_points": entry_points.duplicate(),
}
func _submit(command_type: StringName, payload: Dictionary) -> CommandResult:
_command_sequence += 1
return _submit_with_key(
command_type,
payload,
StringName("runtime.defense.%04d" % _command_sequence)
)
func _submit_with_key(
command_type: StringName,
payload: Dictionary,
idempotency_key: StringName
) -> CommandResult:
return _runtime.submit_command(
command_type,
payload,
&"defense_controller",
StringName(payload.get(&"raid_id", payload.get("raid_id", ""))),
80,
-1,
idempotency_key
)
func _advance(minutes: int) -> void:
_clock.advance_minutes(minutes)
_clock.set_paused(true)
func _count_domain_events(event_type: StringName, entity_id: StringName = &"") -> int:
var count := 0
for event: SimulationDomainEvent in _runtime.domain_events.recent_events():
if event.event_type != event_type:
continue
if entity_id.is_empty() or entity_id in event.entity_ids:
count += 1
return count
func _defense_terminal_event_order(raid_id: StringName, resolution_id: StringName) -> bool:
var ordered_types: Array[StringName] = []
for event: SimulationDomainEvent in _runtime.domain_events.recent_events():
if raid_id not in event.entity_ids:
continue
if event.event_type in [
&"DEFENSE_SKILL_APPLIED",
&"DEFENSE_ROUND_RESOLVED",
&"DEFENSE_RAID_STATE_CHANGED",
&"DEFENSE_RAID_RESOLVED",
&"COMMAND_COMPLETED",
]:
if event.event_type == &"DEFENSE_ROUND_RESOLVED" \
and event.payload.get(&"resolution_id", &"") != resolution_id:
continue
ordered_types.append(event.event_type)
var round_index := ordered_types.rfind(&"DEFENSE_ROUND_RESOLVED")
var state_index := ordered_types.rfind(&"DEFENSE_RAID_STATE_CHANGED")
var resolved_index := ordered_types.rfind(&"DEFENSE_RAID_RESOLVED")
var command_index := ordered_types.rfind(&"COMMAND_COMPLETED")
return round_index >= 0 and state_index > round_index \
and resolved_index > state_index and command_index > resolved_index
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)
## Explicit API coverage schedules the command at its actual deadline. The
## normal kernel executes APPLY_COMMANDS before automatic defense progression.
## Captures bracket that actual handler, excluding later unrelated tick phases.
func _submit_round_at_due(
payload: Dictionary,
idempotency_key: StringName = &""
) -> Dictionary:
var raid_id := StringName(payload[&"raid_id"])
var raid: Dictionary = _runtime.defense_service.get_raid(raid_id)
var due := int(raid[&"state_changed_at"]) + int(raid[&"round_minutes"])
_expect(due > _clock.absolute_minute, "manual deadline helper receives a future configured deadline")
if due <= _clock.absolute_minute:
return {&"result": _submit(&"resolve_defense_round", payload), &"before": {}, &"after": {}}
if idempotency_key.is_empty():
_command_sequence += 1
idempotency_key = StringName("runtime.defense.%04d" % _command_sequence)
var captures := {&"before": {}, &"after": {}, &"signals": []}
var executors: Dictionary = _runtime.get("_executors")
var original: Callable = executors[&"resolve_defense_round"]
executors[&"resolve_defense_round"] = func(command: SimulationCommand) -> CommandResult:
captures[&"before"] = _capture_defense_views()
var result: CommandResult = original.call(command)
captures[&"after"] = _capture_defense_views()
return result
var accepted: CommandResult = _runtime.submit_command(
&"resolve_defense_round", payload, &"defense_controller", raid_id,
80, due, idempotency_key
)
var capture_completed := func(result: CommandResult) -> void:
if result.command_id == accepted.command_id:
(captures[&"signals"] as Array).append(result.copy())
_runtime.command_completed.connect(capture_completed)
_clock.advance_minutes(due - _clock.absolute_minute)
_clock.set_paused(true)
_runtime.command_completed.disconnect(capture_completed)
executors[&"resolve_defense_round"] = original
var completed: CommandResult = _runtime.command_queue.get_processed_result(accepted.command_id)
_expect(accepted.is_success() and completed != null, "manual defense command is genuinely processed at its scheduled tick")
_expect(
(captures[&"signals"] as Array).size() == 1
and completed != null
and (captures[&"signals"][0] as CommandResult).to_dictionary() == completed.to_dictionary(),
"command_completed emits the actual persisted result, including failures"
)
captures[&"result"] = completed
return captures
func _capture_defense_views() -> Dictionary:
return {
&"defense": _runtime.defense_service.create_snapshot(),
&"defense_rng": _runtime.get_rng_stream(&"defense").snapshot(),
&"authority_rng": _runtime.get_rng_stream(&"authority").snapshot(),
&"economy": _runtime.economy_service.create_snapshot(),
&"state": _state.create_snapshot(),
&"inventory": _runtime.inventory_service.create_snapshot(),
&"staff": _runtime.staff_service.create_snapshot(),
&"facilities": _runtime.facility_service.create_snapshot(),
}