Preserve approved pixel tower prototype as development baseline

This commit is contained in:
Codex
2026-09-09 07:51:16 +09:00
commit 423fd6dd2e
915 changed files with 95193 additions and 0 deletions
+362
View File
@@ -0,0 +1,362 @@
extends SceneTree
const HeatStateType = preload("res://scripts/authority/heat_state.gd")
const IncidentType = preload("res://scripts/authority/incident.gd")
const EvidenceType = preload("res://scripts/authority/evidence.gd")
const InspectionType = preload("res://scripts/authority/inspection.gd")
const AuthorityServiceType = preload("res://scripts/authority/authority_service.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S5: Authority, Incident, Evidence and Inspection")
_test_heat_incident_dedup_and_decay_floor()
_test_evidence_visibility_and_incident_scoring()
_test_inspection_selection_and_patrol_order()
_test_path_unreachable_records_departure_reason()
_test_resolution_idempotency()
_test_resolution_failure_is_atomic()
_test_snapshot_round_trip_and_atomicity()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _test_heat_incident_dedup_and_decay_floor() -> void:
var service := AuthorityServiceType.new()
var first = service.create_incident(
&"incident_shared",
&"illegal_service",
10,
&"noise_detector",
{&"severity": 80, &"heat_delta": 30, &"facility_id": &"hidden_room"}
)
_expect(first.is_success(), "first Incident report is accepted")
_expect(service.get_authority_state().heat == 30, "Incident applies its Heat delta once")
var duplicate = service.report_incident(
IncidentType.new().configure(
&"incident_shared",
&"conflicting_duplicate",
11,
{&"severity": 100, &"heat_delta": 100}
),
&"witness_detector"
)
_expect(duplicate.is_success() and duplicate.is_replay, "duplicate incident_id is an idempotent replay")
_expect(duplicate.details[&"heat_applied"] == 0 and service.get_authority_state().heat == 30, "second detector cannot add Heat again")
var canonical := service.get_incident(&"incident_shared")
_expect(canonical.detector_ids == [&"noise_detector", &"witness_detector"], "all unique detector IDs remain auditable")
_expect(canonical.incident_type == &"illegal_service" and canonical.severity == 80, "first Incident payload remains canonical")
service.advance_time(1_440, 15, 20)
_expect(service.get_authority_state().heat == 20, "daily decay stops at the active-case floor")
service.advance_time(2_880, 15, 20)
_expect(service.get_authority_state().heat == 20, "active-case floor remains stable on later days")
service.dismiss_incident(&"incident_shared")
service.advance_time(4_320, 15, 20)
_expect(service.get_authority_state().heat == 5, "Heat decays below the floor after the case closes")
var low_state := HeatStateType.new().configure({&"heat": 10, &"active_case_ids": [&"case_a"]})
low_state.apply_daily_decay(1, 5, 20)
_expect(low_state.heat == 10, "daily decay never raises pre-existing low Heat up to its floor")
_expect(service.validate_invariants()[&"ok"], "Incident dedup and active-case state satisfy invariants")
func _test_evidence_visibility_and_incident_scoring() -> void:
var los_calls := {&"count": 0, &"visible": false}
var service := AuthorityServiceType.new().configure_callbacks(
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool:
los_calls[&"count"] = int(los_calls[&"count"]) + 1
return bool(los_calls[&"visible"])
)
service.create_incident(&"incident_evidence", &"hidden_facility", 0, &"detector_a", {&"heat_delta": 5})
service.create_evidence(&"evidence_wall", &"visual_trace", 25, {
&"incident_id": &"incident_evidence",
&"facility_id": &"illegal_room",
&"location_cell": Vector3i(5, 5, -1),
&"created_at": 0,
&"expires_at": 1_000,
})
var blocked = service.attempt_discover_evidence(
&"evidence_wall", &"inspector_a", Vector3i(4, 5, -1), 10
)
_expect(not blocked.is_success() and blocked.details[&"reason"] == &"LINE_OF_SIGHT_BLOCKED", "SYS-AUT-001: a wall-blocked callback prevents discovery")
_expect(service.get_authority_state().evidence_points == 0, "blocked visual evidence contributes no points")
var other_layer = service.attempt_discover_evidence(
&"evidence_wall", &"inspector_a", Vector3i(5, 5, 0), 11
)
_expect(not other_layer.is_success() and other_layer.details[&"reason"] == &"DIFFERENT_LAYER", "different layers never fall through to line-of-sight")
_expect(los_calls[&"count"] == 1, "cross-layer evidence does not call the visibility boundary")
los_calls[&"visible"] = true
var visible = service.attempt_discover_evidence(
&"evidence_wall", &"inspector_a", Vector3i(4, 5, -1), 12,
{&"illegal_confirmed": true}
)
_expect(visible.is_success() and visible.details[&"discovery_method"] == &"LINE_OF_SIGHT", "same-layer evidence is found only after the injected boundary confirms sight")
_expect(service.get_authority_state().evidence_points == 25, "discovered evidence contributes its severity")
_expect(service.get_authority_state().known_illegal_facility_ids == [&"illegal_room"], "confirmed visual evidence records the known illegal facility")
service.create_evidence(&"evidence_same_incident", &"second_photo", 25, {
&"incident_id": &"incident_evidence",
&"location_cell": Vector3i(8, 8, -1),
&"created_at": 0,
})
service.attempt_discover_evidence(
&"evidence_same_incident", &"inspector_a", Vector3i(8, 8, -1), 13
)
_expect(service.get_authority_state().evidence_points == 25, "SYS-AUT-002: the same Incident is not added to evidence points twice")
service.create_evidence(&"evidence_stronger", &"ledger", 40, {
&"incident_id": &"incident_evidence",
&"location_cell": Vector3i(99, 99, 5),
&"created_at": 0,
})
service.attempt_discover_evidence(
&"evidence_stronger", &"inspector_a", Vector3i.ZERO, 14,
{&"trusted_source": true}
)
_expect(service.get_authority_state().evidence_points == 40, "corroboration upgrades an Incident to its strongest evidence rather than stacking")
var replay = service.attempt_discover_evidence(
&"evidence_stronger", &"inspector_a", Vector3i.ZERO, 15,
{&"trusted_source": true}
)
_expect(replay.is_success() and replay.is_replay and service.get_authority_state().evidence_points == 40, "repeated evidence discovery is idempotent")
_expect(service.validate_invariants()[&"ok"], "visibility discoveries and reverse references satisfy invariants")
func _test_inspection_selection_and_patrol_order() -> void:
var service := AuthorityServiceType.new().configure_callbacks(
Callable(),
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> Dictionary:
return {&"reachable": true, &"travel_minutes": 2}
)
service.apply_heat_delta(30, &"fixture", 0)
service.set_suspicion(20)
var selected = service.evaluate_inspection(360, {
&"base_chance": 5,
&"heat_weight": 1,
&"suspicion_weight": 1,
&"overdue_case_bonus": 0,
&"reputation_protection": 0,
&"roll": 54,
&"inspection_id": &"inspection_order",
&"inspector_id": &"inspector_order",
&"entrance_cell": Vector3i.ZERO,
})
_expect(selected.is_success() and selected.details[&"chance"] == 55 and selected.details[&"selected"], "06:00 inspection chance uses deterministic integer terms")
var replay = service.evaluate_inspection(360, {&"roll": 99})
_expect(replay.is_success() and replay.is_replay and replay.details[&"inspection_id"] == &"inspection_order", "same-day inspection selection replays the stored result")
_expect(service.get_authority_state().next_inspection_window == 1_800, "selection updates the next inspection Window")
service.start_inspection(&"inspection_order", 360)
service.mark_inspector_arrived(&"inspection_order", 361)
var candidates := [
{&"waypoint_id": &"z_tie", &"cell": Vector3i(3, 0, 0), &"suspect_score": 10, &"evidence_potential": 10, &"travel_minutes": 5, &"inspection_minutes": 5},
{&"waypoint_id": &"top", &"cell": Vector3i(1, 0, 0), &"suspect_score": 10, &"evidence_potential": 20, &"travel_minutes": 5, &"inspection_minutes": 5},
{&"waypoint_id": &"a_tie", &"cell": Vector3i(2, 0, 0), &"suspect_score": 10, &"evidence_potential": 10, &"travel_minutes": 5, &"inspection_minutes": 5},
]
var plan = service.plan_patrol(&"inspection_order", candidates, 362, 30)
_expect(plan.is_success(), "patrol plan is accepted after CHECK_IN")
_expect(plan.details[&"waypoint_ids"] == [&"top", &"a_tie", &"z_tie"], "patrol sorts Utility descending then waypoint ID lexically")
_expect(service.get_inspection(&"inspection_order").discovered_evidence_ids.is_empty(), "planning patrol does not perform an instantaneous map scan")
var moved = service.advance_patrol(&"inspection_order", 363)
_expect(moved.is_success() and moved.details[&"waypoint_id"] == &"top", "patrol advances exactly one waypoint per call")
var second := AuthorityServiceType.new()
second.schedule_inspection(360, {&"inspection_id": &"stable", &"inspector_id": &"stable_i"})
second.start_inspection(&"stable", 360)
second.mark_inspector_arrived(&"stable", 361)
var reversed := candidates.duplicate(true)
reversed.reverse()
var second_plan = second.plan_patrol(&"stable", reversed, 362, 30)
_expect(second_plan.details[&"waypoint_ids"] == plan.details[&"waypoint_ids"], "patrol route is independent of candidate input order")
func _test_path_unreachable_records_departure_reason() -> void:
var service := AuthorityServiceType.new().configure_callbacks(
Callable(),
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> Dictionary:
return {&"reachable": false, &"reason": &"SEALED_BY_WALL"}
)
service.schedule_inspection(10, {
&"inspection_id": &"inspection_blocked",
&"inspector_id": &"inspector_blocked",
&"entrance_cell": Vector3i.ZERO,
})
service.start_inspection(&"inspection_blocked", 10)
service.mark_inspector_arrived(&"inspection_blocked", 11)
service.plan_patrol(&"inspection_blocked", [{
&"waypoint_id": &"sealed_room",
&"cell": Vector3i(9, 9, -1),
&"suspect_score": 90,
&"evidence_potential": 90,
&"travel_minutes": 10,
}], 12)
var blocked = service.advance_patrol(&"inspection_blocked", 13)
var inspection := service.get_inspection(&"inspection_blocked")
_expect(blocked.is_success() and not blocked.details[&"reachable"], "unreachable patrol target is a handled inspection outcome")
_expect(inspection.state == InspectionType.State.DEPARTING, "SYS-AUT-004: unreachable inspector enters DEPARTING instead of teleporting")
_expect(inspection.exit_reason == &"PATH_UNREACHABLE" and inspection.result_reason == &"SEALED_BY_WALL", "path failure records both stable exit and detailed result reasons")
_expect(inspection.inaccessible_waypoint_ids == [&"sealed_room"] and inspection.visited_waypoint_ids.is_empty(), "unreachable waypoint is recorded without being marked visited")
var departed = service.depart_inspection(&"inspection_blocked", 14)
_expect(departed.is_success() and inspection.state == InspectionType.State.CLOSED, "inspector safely closes after departure")
_expect(departed.details[&"exit_reason"] == &"PATH_UNREACHABLE", "closed inspection preserves the path-unreachable exit reason")
_expect(service.validate_invariants()[&"ok"], "path-unreachable inspection remains snapshot-safe")
func _test_resolution_idempotency() -> void:
var applications := {&"validate": 0, &"apply": 0, &"fine_total": 0, &"sanctions": []}
var service := AuthorityServiceType.new().configure_callbacks(
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool: return true,
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool: return true,
func(payload: Dictionary, phase: StringName, _token: Variant) -> Dictionary:
applications[phase] = int(applications.get(phase, 0)) + 1
if phase == &"apply":
applications[&"fine_total"] = int(applications[&"fine_total"]) + int(payload[&"fine_amount"])
(applications[&"sanctions"] as Array).append(payload[&"sanction"])
return {&"ok": true, &"token": &"validated"}
)
service.create_incident(&"incident_fine", &"illegal_facility", 0, &"detector", {&"severity": 80, &"heat_delta": 10})
service.create_evidence(&"evidence_fine", &"physical", 60, {
&"incident_id": &"incident_fine",
&"facility_id": &"facility_illegal",
&"location_cell": Vector3i(2, 0, 0),
&"created_at": 0,
})
_prepare_reviewing_inspection(service, &"inspection_fine", &"inspector_fine", &"evidence_fine")
var first = service.resolve_inspection(&"inspection_fine", &"resolution_once", 5, {&"fine_per_point": 10})
_expect(first.is_success() and first.details[&"result"] == &"TEMPORARY_CLOSURE", "evidence thresholds deterministically select the sanction tier")
_expect(first.details[&"fine_amount"] == 600 and first.details[&"sanction"] == &"TEMPORARY_CLOSURE", "resolution projection contains fine and sanction for external domains")
_expect(applications[&"validate"] == 1 and applications[&"apply"] == 1, "resolution boundary validates then applies exactly once")
_expect(service.get_authority_state().fines_due == 600 and applications[&"fine_total"] == 600, "successful resolution records one fine liability and one external application")
_expect(service.get_incident(&"incident_fine").state == IncidentType.State.RESOLVED, "resolution closes linked active Incident cases")
var duplicate = service.resolve_inspection(&"inspection_fine", &"resolution_once", 6, {&"fine_per_point": 999})
_expect(duplicate.is_success() and duplicate.is_replay, "SYS-AUT-003: duplicate resolution_id returns its stored result")
_expect(applications[&"apply"] == 1 and service.get_authority_state().fines_due == 600, "duplicate resolution cannot apply fine or sanction twice")
_expect(duplicate.details[&"fine_amount"] == 600, "replay ignores conflicting later resolution terms")
_expect(service.validate_invariants()[&"ok"], "resolved inspection, resolution and Incident references satisfy invariants")
func _test_resolution_failure_is_atomic() -> void:
var external := {&"fine": 0, &"rollback": 0}
var service := AuthorityServiceType.new().configure_callbacks(
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool: return true,
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool: return true,
func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase == &"validate":
return {&"ok": true, &"token": int(external[&"fine"])}
if phase == &"apply":
external[&"fine"] = int(external[&"fine"]) + int(payload[&"fine_amount"])
return {&"ok": false, &"token": token, &"reason": &"LEDGER_REJECTED"}
if phase == &"rollback":
external[&"fine"] = int(token)
external[&"rollback"] = int(external[&"rollback"]) + 1
return {&"ok": true}
return {&"ok": false}
)
service.create_incident(&"incident_fail", &"illegal_facility", 0, &"detector", {&"heat_delta": 5})
service.create_evidence(&"evidence_fail", &"physical", 30, {
&"incident_id": &"incident_fail",
&"location_cell": Vector3i(2, 0, 0),
&"created_at": 0,
})
_prepare_reviewing_inspection(service, &"inspection_fail", &"inspector_fail", &"evidence_fail")
var before: Dictionary = service.create_snapshot()
var failed = service.resolve_inspection(&"inspection_fail", &"resolution_fail", 5)
_expect(not failed.is_success() and failed.details[&"rollback_ok"], "failed external resolution invokes its rollback boundary")
_expect(external[&"fine"] == 0 and external[&"rollback"] == 1, "failed fine application leaves no external value behind")
_expect(service.create_snapshot() == before, "failed resolution leaves the full Authority snapshot unchanged")
_expect(service.get_resolution(&"resolution_fail").is_empty(), "failed resolution_id is not marked processed")
func _test_snapshot_round_trip_and_atomicity() -> void:
var application_count := {&"apply": 0}
var service := AuthorityServiceType.new().configure_callbacks(
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool: return true,
func(_from: Vector3i, _to: Vector3i, _context: Dictionary) -> bool: return true,
func(_payload: Dictionary, phase: StringName, _token: Variant) -> Dictionary:
if phase == &"apply":
application_count[&"apply"] = int(application_count[&"apply"]) + 1
return {&"ok": true}
)
service.create_incident(&"incident_save", &"contraband", 2, &"detector_b", {&"heat_delta": 12})
service.report_incident(
IncidentType.new().configure(&"incident_save", &"contraband", 2, {&"heat_delta": 12}),
&"detector_a"
)
service.create_evidence(&"evidence_save", &"inventory_record", 30, {
&"incident_id": &"incident_save",
&"location_cell": Vector3i(1, 1, 0),
&"created_at": 2,
})
_prepare_reviewing_inspection(service, &"inspection_save", &"inspector_save", &"evidence_save")
service.resolve_inspection(&"inspection_save", &"resolution_save", 5)
service.depart_inspection(&"inspection_save", 6)
service.advance_time(1_440)
var snapshot: Dictionary = service.create_snapshot()
var restored := AuthorityServiceType.new().configure_callbacks(
Callable(), Callable(),
func(_payload: Dictionary, phase: StringName, _token: Variant) -> Dictionary:
if phase == &"apply":
application_count[&"apply"] = int(application_count[&"apply"]) + 1
return {&"ok": true}
)
_expect(restored.restore_snapshot(snapshot), "valid Authority snapshot restores")
_expect(restored.create_snapshot() == snapshot, "Authority snapshot round trips exactly")
_expect(restored.validate_invariants()[&"ok"], "restored Authority aggregate satisfies invariants")
var replay = restored.resolve_inspection(&"inspection_save", &"resolution_save", 1_441)
_expect(replay.is_success() and replay.is_replay, "resolution idempotency survives save and load")
_expect(application_count[&"apply"] == 1, "loaded resolution replay never calls external application again")
var before: Dictionary = restored.create_snapshot()
var corrupt := snapshot.duplicate(true)
corrupt[&"authority_state"][&"evidence_points"] = 999
_expect(not restored.restore_snapshot(corrupt), "cross-domain cache mismatch rejects a corrupt Authority snapshot")
_expect(restored.create_snapshot() == before, "failed Authority restore leaves every live field unchanged")
var wrong_version := snapshot.duplicate(true)
wrong_version[&"version"] = 999
_expect(not restored.restore_snapshot(wrong_version), "unsupported Authority snapshot version is rejected")
_expect(restored.create_snapshot() == before, "version rejection is also atomic")
var out_of_range := snapshot.duplicate(true)
out_of_range[&"authority_state"][&"heat"] = 101
_expect(not restored.restore_snapshot(out_of_range), "out-of-range snapshot values are rejected instead of silently clamped")
_expect(restored.create_snapshot() == before, "strict value rejection preserves the live aggregate")
func _prepare_reviewing_inspection(
service: AuthorityService,
inspection_id: StringName,
inspector_id: StringName,
evidence_id: StringName
) -> void:
service.schedule_inspection(0, {
&"inspection_id": inspection_id,
&"inspector_id": inspector_id,
&"entrance_cell": Vector3i.ZERO,
})
service.start_inspection(inspection_id, 0)
service.mark_inspector_arrived(inspection_id, 1)
service.plan_patrol(inspection_id, [{
&"waypoint_id": StringName("waypoint_%s" % inspection_id),
&"cell": Vector3i(1, 1, 0) if evidence_id == &"evidence_save" else Vector3i(2, 0, 0),
&"suspect_score": 100,
&"evidence_potential": 100,
&"travel_minutes": 1,
&"inspection_minutes": 1,
}], 2)
service.advance_patrol(inspection_id, 3)
service.inspect_current_waypoint(inspection_id, [evidence_id], 4)
service.finish_patrol(inspection_id, 5)
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)
+1
View File
@@ -0,0 +1 @@
uid://bdrksuqlwvmu4
+970
View File
@@ -0,0 +1,970 @@
extends SceneTree
const DefenseServiceType = preload("res://scripts/defense/defense_service.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S5: deterministic wave-by-wave Raid defense")
_test_definition_and_registration_validation()
_test_scores_penalties_and_stable_order()
_test_layered_assets_and_breach()
_test_explicit_and_offscreen_attackers_share_one_formula()
_test_active_skills_and_successful_boundaries()
_test_skill_boundary_validation_rollback()
_test_skill_boundary_apply_and_outcome_rollback()
_test_rng_transaction_retry_stability()
_test_retreat_policies_and_manual_retreat_replay()
_test_cancel_transaction_and_reconciliation()
_test_resolution_operation_namespace_and_legacy_v1()
_test_multiple_waves_require_explicit_transition()
_test_raid_duration_and_wave_round_limits()
_test_round_resolution_replay()
_test_round_clock_contract()
_test_wave_entry_and_metadata_validation()
_test_armor_pool_accounting()
_test_snapshot_round_trip_and_corrupt_atomicity()
_test_cross_time_snapshot_rejection()
_test_input_order_determinism()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _test_definition_and_registration_validation() -> void:
var service := DefenseServiceType.new()
_expect(not service.create_raid(&"", 0, _definition()).is_success(), "Raid ID is required")
_expect(not service.create_raid(&"bad_minute", -1, _definition()).is_success(), "creation minute cannot be negative")
_expect(not service.create_raid(&"no_entries", 0, _definition({&"entry_points": []})).is_success(), "at least one entry point is required")
_expect(not service.create_raid(&"no_objective", 0, _definition({&"objective_ids": []})).is_success(), "at least one objective is required")
_expect(not service.create_raid(&"no_waves", 0, _definition({&"waves": []})).is_success(), "at least one wave is required")
_expect(not service.create_raid(&"bad_threat", 0, _definition({&"waves": [{&"wave_id": &"w", &"threat_strength": 0}]})).is_success(), "wave strength must be positive")
_expect(not service.create_raid(&"duplicate_wave", 0, _definition({&"waves": [
{&"wave_id": &"same", &"threat_strength": 10},
{&"wave_id": &"same", &"threat_strength": 20},
]})).is_success(), "wave IDs are unique")
_expect(not service.create_raid(&"unknown_entry", 0, _definition({&"waves": [{
&"wave_id": &"w", &"threat_strength": 10, &"entry_points": [&"not_declared"],
}]})).is_success(), "wave entry points must belong to the Raid")
_expect(not service.create_raid(&"unknown_objective", 0, _definition({&"waves": [{
&"wave_id": &"w", &"threat_strength": 10, &"objective_ids": [&"not_declared"],
}]})).is_success(), "wave objectives must belong to the Raid")
_expect(not service.create_raid(&"bad_policy", 0, _definition({&"retreat_policy": &"PANIC"})).is_success(), "retreat policy is enumerated")
_expect(not service.create_raid(&"bad_duration", 0, _definition({&"round_minutes": 0})).is_success(), "round duration must be positive")
_expect(not service.create_raid(&"bad_threshold", 0, _definition({&"defender_retreat_threshold": 101})).is_success(), "defender retreat threshold is bounded")
var created = service.create_raid(&"valid", 10, _definition({
&"entry_points": [&"west", &"east", &"west"],
&"objective_ids": [&"vault", &"core", &"vault"],
&"waves": [{
&"wave_id": &"wave_a",
&"threat_strength": 40,
&"entry_points": [&"west", &"east"],
&"objective_ids": [&"vault", &"core"],
}],
}))
_expect(created.is_success() and created.details[&"wave_count"] == 1, "valid Raid definition is accepted")
var canonical := service.get_raid(&"valid")
_expect(canonical[&"entry_points"] == [&"east", &"west"], "entry point IDs are canonicalized")
_expect(canonical[&"objective_ids"] == [&"core", &"vault"], "objective IDs are canonicalized")
_expect(not service.create_raid(&"valid", 10, _definition()).is_success(), "duplicate Raid ID is rejected")
_expect(not service.register_combatant(&"valid", &"bad_side", &"NEUTRAL", 10, 10).is_success(), "combatant side is validated")
_expect(not service.register_combatant(&"valid", &"bad_health", &"DEFENDER", 10, 0).is_success(), "combatant health is validated")
_expect(not service.register_combatant(&"valid", &"bad_wave", &"ATTACKER", 10, 10, {&"wave_id": &"missing"}).is_success(), "attacker wave reference is validated")
_expect(not service.register_combatant(&"valid", &"bad_defender_wave", &"DEFENDER", 10, 10, {&"wave_id": &"wave_a"}).is_success(), "defenders cannot be wave-bound")
_expect(service.register_combatant(&"valid", &"guard", &"DEFENDER", 20, 100, {&"health": 60, &"entry_point_id": &"east"}).is_success(), "valid wounded defender is registered")
_expect(service.get_raid(&"valid")[&"combatants"][&"guard"][&"initial_health"] == 100, "retreat baseline uses maximum health")
_expect(not service.register_asset(&"valid", &"core", &"DOOR", 100, 10).is_success(), "declared objective cannot be registered as a door")
_expect(not service.register_asset(&"valid", &"ordinary", &"OBJECTIVE", 100, 10).is_success(), "undeclared asset cannot be registered as an objective")
_expect(not service.register_asset(&"valid", &"door_bad_entry", &"DOOR", 100, 10, {&"entry_points": [&"north"]}).is_success(), "asset entry coverage is validated")
_expect(service.register_asset(&"valid", &"core", &"OBJECTIVE", 100, 5, {&"integrity": 40}).is_success(), "first objective asset is registered")
_expect(service.get_raid(&"valid")[&"assets"][&"core"][&"initial_integrity"] == 100, "objective retreat baseline uses maximum integrity")
_expect(not service.start_raid(&"valid", 10).is_success(), "Raid cannot start until every objective is registered")
_expect(service.register_asset(&"valid", &"vault", &"OBJECTIVE", 80, 5).is_success(), "second objective asset is registered")
_expect(service.start_raid(&"valid", 10).is_success(), "complete Raid can start")
_expect(service.get_raid(&"valid")[&"waves"][0][&"state"] == &"ACTIVE", "starting marks the first wave ACTIVE")
_expect(not service.register_asset(&"valid", &"late", &"DOOR", 10, 1).is_success(), "assets cannot be added after start")
_expect(not service.register_combatant(&"valid", &"late", &"DEFENDER", 1, 1).is_success(), "combatants cannot be added after start")
_expect(service.validate_invariants()[&"ok"], "validated registration state satisfies all invariants")
func _test_scores_penalties_and_stable_order() -> void:
var service := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(service.create_raid(&"scores", 0, _definition({
&"entry_points": [&"west", &"east"],
&"terrain_bonus": 3,
&"command_bonus": 4,
&"surprise_penalty": 7,
&"multiple_entry_penalty": 5,
&"waves": [{
&"wave_id": &"wave_1", &"threat_strength": 40,
&"entry_points": [&"west", &"east"], &"objective_ids": [&"core"],
&"retreat_threshold": 0, &"max_rounds": 10,
}],
})), "score fixture Raid is created")
_must(service.register_combatant(&"scores", &"z_guard", &"DEFENDER", 20, 100, {&"fatigue": 25}), "fatigued defender is registered")
_must(service.register_combatant(&"scores", &"a_guard", &"DEFENDER", 10, 100), "fresh defender is registered")
_must(service.register_combatant(&"scores", &"raider", &"ATTACKER", 20, 100, {&"wave_id": &"wave_1"}), "explicit attacker is registered")
_must(service.register_asset(&"scores", &"core", &"OBJECTIVE", 100, 5), "score objective is registered")
_must(service.register_asset(&"scores", &"tower", &"DEFENSE_DEVICE", 100, 12, {&"terrain_bonus": 2}), "score device is registered")
_must(service.register_asset(&"scores", &"gate", &"DOOR", 100, 8), "score door is registered")
_must(service.start_raid(&"scores", 0), "score Raid starts")
var first = service.resolve_next_round(&"scores", &"score_round_1", 5)
_expect(first.is_success(), "first score round resolves")
var components: Dictionary = first.details[&"score_components"]
_expect(components[&"defender_combat"] == 30, "defender score sums effective combat")
_expect(components[&"asset_defense"] == 25, "door, device and objective all contribute defense")
_expect(components[&"terrain_bonus"] == 5 and components[&"command_bonus"] == 4, "terrain and command bonuses are explicit score components")
_expect(components[&"fatigue_penalty"] == 5, "fatigue scales the affected defender's effective combat")
_expect(components[&"multiple_entry_penalty"] == 5, "second entry point applies one deterministic penalty")
_expect(components[&"surprise_penalty"] == 7, "surprise applies on the first wave round")
_expect(components[&"attacker_combat"] == 20 and components[&"aggregate_attacker_combat"] == 40, "explicit and aggregate attackers are visible score components")
_expect(first.details[&"defense_score"] == 47 and first.details[&"attack_score"] == 60, "integer score formula has the expected result")
var raid_after_first := service.get_raid(&"scores")
_expect(raid_after_first[&"combatants"][&"a_guard"][&"fatigue"] == 5, "fresh defender gains five fatigue per round")
_expect(raid_after_first[&"combatants"][&"z_guard"][&"fatigue"] == 30, "existing fatigue increases by the same fixed amount")
var second = service.resolve_next_round(&"scores", &"score_round_2", 10)
_expect(second.is_success() and second.details[&"score_components"][&"surprise_penalty"] == 0, "surprise never repeats after round one")
_expect(second.details[&"defense_roll"] == 50 and second.details[&"attack_roll"] == 50, "injected rolls are deterministic")
_expect(service.validate_invariants()[&"ok"], "multi-entry and fatigue score state remains valid")
func _test_layered_assets_and_breach() -> void:
var outcomes := {&"count": 0, &"payload": {}}
var service := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50), Callable(),
func(_payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase == &"apply":
outcomes[&"count"] = int(outcomes[&"count"]) + 1
outcomes[&"payload"] = _payload.duplicate(true)
return {&"ok": true, &"token": token}
)
_must(service.create_raid(&"layers", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "layered Raid is created")
_must(service.register_asset(&"layers", &"core", &"OBJECTIVE", 3, 0), "fragile objective is registered")
_must(service.register_asset(&"layers", &"z_door", &"DOOR", 3, 0), "door is registered")
_must(service.register_asset(&"layers", &"a_device", &"DEFENSE_DEVICE", 3, 0), "device is registered")
_must(service.start_raid(&"layers", 0), "layered Raid starts")
var door_round = service.resolve_next_round(&"layers", &"layer_1", 5)
_expect(door_round.is_success() and door_round.details[&"defense_damage"][&"target_id"] == &"z_door", "lowest target-order door is damaged first")
_expect(door_round.details[&"defense_damage"][&"destroyed"], "door integrity reaches zero in its round")
var device_round = service.resolve_next_round(&"layers", &"layer_2", 10)
_expect(device_round.is_success() and device_round.details[&"defense_damage"][&"target_id"] == &"a_device", "device is targeted after destroyed doors")
_expect(device_round.details[&"score_components"][&"asset_defense"] == 0, "destroyed door no longer contributes defense")
var objective_round = service.resolve_next_round(&"layers", &"layer_3", 15)
_expect(objective_round.is_success() and objective_round.details[&"defense_damage"][&"target_id"] == &"core", "objective is reached after devices")
_expect(objective_round.details[&"state"] == &"BREACHED" and objective_round.details[&"reason"] == &"OBJECTIVE_DESTROYED", "destroyed objective breaches the Raid")
var raid := service.get_raid(&"layers")
_expect(raid[&"assets"][&"z_door"][&"integrity"] == 0 and raid[&"assets"][&"a_device"][&"integrity"] == 0 and raid[&"assets"][&"core"][&"integrity"] == 0, "all three defense layers retain their per-round integrity updates")
_expect(raid[&"waves"][0][&"state"] == &"BREACHED" and raid[&"result"] == &"BREACHED", "wave and Raid terminal states agree")
_expect(outcomes[&"count"] == 1, "terminal outcome boundary applies exactly once")
var asset_results: Array = outcomes[&"payload"][&"asset_results"]
_expect([asset_results[0][&"asset_id"], asset_results[1][&"asset_id"], asset_results[2][&"asset_id"]] == [&"a_device", &"core", &"z_door"], "terminal asset reconciliation payload uses stable ID order")
_expect(asset_results.all(func(asset: Dictionary) -> bool: return bool(asset[&"destroyed"])), "terminal payload exposes final integrity for every destroyed defense layer")
_expect(service.validate_invariants()[&"ok"], "breached layered Raid remains valid")
func _test_explicit_and_offscreen_attackers_share_one_formula() -> void:
var aggregate_only := _attack_mix_service(&"aggregate_only", false)
var mixed := _attack_mix_service(&"mixed", true)
var aggregate_round = aggregate_only.resolve_next_round(&"aggregate_only", &"round", 5)
var mixed_round = mixed.resolve_next_round(&"mixed", &"round", 5)
_expect(aggregate_round.is_success() and mixed_round.is_success(), "aggregate-only and mixed rounds resolve")
_expect(aggregate_round.details[&"attack_score"] == mixed_round.details[&"attack_score"], "equal explicit/offscreen effective power produces the same attack score")
_expect(aggregate_round.details[&"damage_to_threat"] == mixed_round.details[&"damage_to_threat"], "equal attack power uses the same damage formula")
_expect(mixed_round.details[&"attacker_damage"].size() == 1, "mixed damage is allocated to the stable explicit attacker first")
_expect(mixed_round.details[&"attacker_damage"][0][&"target_id"] == &"visible_raider" and mixed_round.details[&"attacker_damage"][0][&"damage"] == 2, "explicit attacker consumes only its actual remaining durability")
_expect(mixed_round.details[&"aggregate_damage"] == mixed_round.details[&"damage_applied_to_threat"] - 2, "only leftover damage reaches the offscreen aggregate")
_expect(mixed.get_raid(&"mixed")[&"waves"][0][&"remaining_threat"] == 10 - mixed_round.details[&"aggregate_damage"], "aggregate threat is reduced once, never in parallel with explicit damage")
_expect(mixed.get_raid(&"mixed")[&"combatants"][&"visible_raider"][&"state"] == DefenseServiceType.CombatantState.INCAPACITATED, "explicit attacker state follows its health")
_expect(mixed.validate_invariants()[&"ok"], "mixed on/offscreen attacker state remains valid")
func _test_active_skills_and_successful_boundaries() -> void:
var calls: Array[String] = []
var service := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50),
func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
calls.append("%s:%s" % [phase, payload[&"action_id"]])
return {&"ok": true, &"token": token if token != null else String(payload[&"action_id"])}
)
_must(service.create_raid(&"skills", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 50, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "skill Raid is created")
_must(service.register_combatant(&"skills", &"caster", &"DEFENDER", 20, 100, {&"health": 50}), "skill caster is registered")
_must(service.register_asset(&"skills", &"core", &"OBJECTIVE", 100, 5), "skill objective is registered")
_must(service.register_asset(&"skills", &"door", &"DOOR", 100, 5, {&"integrity": 40}), "fortify target is registered")
_must(service.start_raid(&"skills", 0), "skill Raid starts")
_must(service.queue_active_skill(&"skills", &"z_rally", &"caster", &"RALLY", {&"potency": 10}), "RALLY is queued")
_must(service.queue_active_skill(&"skills", &"a_power", &"caster", &"POWER_STRIKE", {&"potency": 5}), "POWER_STRIKE is queued")
_must(service.queue_active_skill(&"skills", &"m_heal", &"caster", &"HEAL", {&"potency": 7, &"target_id": &"caster"}), "HEAL is queued")
_must(service.queue_active_skill(&"skills", &"n_fortify", &"caster", &"FORTIFY", {&"potency": 6, &"target_id": &"door"}), "FORTIFY is queued")
var queued_replay = service.queue_active_skill(&"skills", &"z_rally", &"caster", &"RALLY", {&"potency": 99})
_expect(queued_replay.is_success() and queued_replay.is_replay, "duplicate queued action ID is an idempotent replay")
var result = service.resolve_next_round(&"skills", &"skill_round", 5)
_expect(result.is_success(), "round with all active skills resolves")
_expect(result.details[&"skill_ids"] == [&"a_power", &"m_heal", &"n_fortify", &"z_rally"], "skills resolve in stable action-ID order")
_expect(result.details[&"score_components"][&"skill_bonus"] == 10, "RALLY contributes its score bonus")
_expect(result.details[&"damage_to_threat"] >= 6, "POWER_STRIKE adds direct threat damage")
var raid := service.get_raid(&"skills")
_expect(raid[&"combatants"][&"caster"][&"health"] == 57, "HEAL updates defender health before damage")
var fortify_effect: Dictionary = result.details[&"skill_effects"][2]
_expect(fortify_effect[&"action_id"] == &"n_fortify" and fortify_effect[&"repaired"] == 6, "FORTIFY repairs its target before the attack")
_expect(raid[&"assets"][&"door"][&"integrity"] == 46 - int(result.details[&"defense_damage"][&"damage"]), "round damage is applied after the FORTIFY repair")
_expect(raid[&"queued_skills"].is_empty() and raid[&"applied_skill_ids"].size() == 4, "applied skills move from queue to idempotency history")
_expect(calls.slice(0, 4) == ["validate:a_power", "validate:m_heal", "validate:n_fortify", "validate:z_rally"], "all boundary validations use stable action-ID order")
_expect(calls.slice(4, 8) == ["apply:a_power", "apply:m_heal", "apply:n_fortify", "apply:z_rally"], "all boundary applications follow the validated order")
var applied_replay = service.queue_active_skill(&"skills", &"z_rally", &"caster", &"RALLY")
_expect(applied_replay.is_success() and applied_replay.is_replay, "applied skill action ID also replays without recharging")
_expect(service.validate_invariants()[&"ok"], "active skill state satisfies invariants")
func _test_skill_boundary_validation_rollback() -> void:
var calls: Array[String] = []
var service := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50),
func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
calls.append("%s:%s" % [phase, payload[&"action_id"]])
if phase == &"validate" and payload[&"action_id"] == &"b_reject":
return {&"ok": false, &"reason": &"NO_RESOURCE"}
return {&"ok": true, &"token": token if token != null else String(payload[&"action_id"])}
)
_prepare_basic(service, &"validation_rollback")
_must(service.queue_active_skill(&"validation_rollback", &"a_reserved", &"guard", &"RALLY", {&"potency": 3}), "first reserved skill is queued")
_must(service.queue_active_skill(&"validation_rollback", &"b_reject", &"guard", &"RALLY", {&"potency": 3}), "rejecting skill is queued")
var before := service.create_snapshot()
var failed = service.resolve_next_round(&"validation_rollback", &"rejected_round", 5)
_expect(not failed.is_success(), "skill validation rejection aborts the round")
_expect(service.create_snapshot() == before, "validation rejection leaves all local Raid state unchanged")
_expect(calls == ["validate:a_reserved", "validate:b_reject", "rollback:a_reserved"], "earlier validation reservations roll back in reverse order")
_expect(service.validate_invariants()[&"ok"], "validation rollback preserves invariants")
func _test_skill_boundary_apply_and_outcome_rollback() -> void:
var apply_calls: Array[String] = []
var applying := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50),
func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
apply_calls.append("%s:%s" % [phase, payload[&"action_id"]])
if phase == &"apply" and payload[&"action_id"] == &"b_fail":
return {&"ok": false, &"token": "failed_token"}
return {&"ok": true, &"token": token if token != null else String(payload[&"action_id"])}
)
_prepare_basic(applying, &"apply_rollback")
for action_id: StringName in [&"a_apply", &"b_fail", &"c_validated"]:
_must(applying.queue_active_skill(&"apply_rollback", action_id, &"guard", &"RALLY", {&"potency": 2}), "apply rollback skill queues: %s" % action_id)
var apply_before := applying.create_snapshot()
var apply_failed = applying.resolve_next_round(&"apply_rollback", &"apply_failed", 5)
_expect(not apply_failed.is_success(), "skill application rejection aborts the round")
_expect(applying.create_snapshot() == apply_before, "application rejection leaves local state atomic")
_expect(apply_calls == [
"validate:a_apply", "validate:b_fail", "validate:c_validated",
"apply:a_apply", "apply:b_fail",
"rollback:c_validated", "rollback:b_fail", "rollback:a_apply",
], "applied, failed and not-yet-applied validation tokens all roll back once")
var skill_calls: Array[String] = []
var outcome_calls: Array[String] = []
var skill_boundary := func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
skill_calls.append("%s:%s" % [phase, payload[&"action_id"]])
return {&"ok": true, &"token": token if token != null else &"skill_token"}
var outcome_boundary := func(_payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
outcome_calls.append(String(phase))
if phase == &"apply":
return {&"ok": false, &"token": &"outcome_token"}
return {&"ok": true, &"token": token if token != null else &"outcome_token"}
var terminal := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50), skill_boundary, outcome_boundary
)
_must(terminal.create_raid(&"outcome_rollback", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 1}],
})), "terminal rollback Raid is created")
_must(terminal.register_combatant(&"outcome_rollback", &"guard", &"DEFENDER", 10, 100), "terminal rollback guard is registered")
_must(terminal.register_asset(&"outcome_rollback", &"core", &"OBJECTIVE", 100, 0), "terminal rollback objective is registered")
_must(terminal.start_raid(&"outcome_rollback", 0), "terminal rollback Raid starts")
_must(terminal.queue_active_skill(&"outcome_rollback", &"skill", &"guard", &"RALLY", {&"potency": 10}), "terminal rollback skill is queued")
var terminal_before := terminal.create_snapshot()
var outcome_failed = terminal.resolve_next_round(&"outcome_rollback", &"outcome_failed", 5)
_expect(not outcome_failed.is_success(), "rejected terminal outcome aborts the round")
_expect(terminal.create_snapshot() == terminal_before, "outcome rejection restores round, skill and wave state atomically")
_expect(skill_calls == ["validate:skill", "apply:skill", "rollback:skill"], "successful external skill application is rolled back after outcome failure")
_expect(outcome_calls == ["validate", "apply", "rollback"], "failed outcome apply invokes its rollback boundary")
_expect(terminal.validate_invariants()[&"ok"], "cross-boundary rollback leaves a valid Raid")
func _test_rng_transaction_retry_stability() -> void:
var rng_state := {&"cursor": 0, &"values": [17, 83, 41, 59], &"phases": []}
var outcome_state := {&"apply_attempts": 0}
var roll_callback := func(_key: StringName, _context: Dictionary) -> int:
var cursor := int(rng_state[&"cursor"])
var value := int(rng_state[&"values"][cursor])
rng_state[&"cursor"] = cursor + 1
return value
var rng_boundary := func(_context: Dictionary, phase: StringName, token: Variant) -> Dictionary:
(rng_state[&"phases"] as Array).append(phase)
if phase == &"snapshot":
return {&"ok": true, &"token": int(rng_state[&"cursor"])}
if phase == &"rollback":
rng_state[&"cursor"] = int(token)
return {&"ok": true, &"token": token}
var outcome_boundary := func(_payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase == &"apply":
outcome_state[&"apply_attempts"] = int(outcome_state[&"apply_attempts"]) + 1
if int(outcome_state[&"apply_attempts"]) == 1:
return {&"ok": false, &"token": &"first_attempt"}
return {&"ok": true, &"token": token}
var service := DefenseServiceType.new().configure_callbacks(
roll_callback, Callable(), outcome_boundary, rng_boundary
)
_must(service.create_raid(&"rng_retry", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 1}],
})), "RNG retry Raid is created")
_must(service.register_asset(&"rng_retry", &"core", &"OBJECTIVE", 1_000, 0), "RNG retry objective is registered")
_must(service.start_raid(&"rng_retry", 0), "RNG retry Raid starts")
var before := service.create_snapshot()
var failed = service.resolve_next_round(&"rng_retry", &"rng_resolution", 5)
_expect(not failed.is_success(), "terminal outcome failure aborts a stateful-RNG round")
_expect(service.create_snapshot() == before, "failed stateful-RNG round leaves Defense state atomic")
_expect(rng_state[&"cursor"] == 0, "RNG rollback restores the cursor consumed by both rolls")
_expect(rng_state[&"phases"] == [&"snapshot", &"rollback"], "failed round snapshots then rolls back RNG state")
var retried = service.resolve_next_round(&"rng_retry", &"rng_resolution", 5)
_expect(retried.is_success() and retried.details[&"defense_roll"] == 17 and retried.details[&"attack_roll"] == 83, "retry consumes the exact same RNG values")
_expect(rng_state[&"cursor"] == 2, "successful retry commits exactly two RNG draws")
_expect(rng_state[&"phases"] == [&"snapshot", &"rollback", &"snapshot", &"commit"], "successful retry commits its RNG transaction")
var replay = service.resolve_next_round(&"rng_retry", &"rng_resolution", 999)
_expect(replay.is_success() and replay.is_replay and replay.details == retried.details, "RNG-backed resolution replays its exact committed payload")
_expect(rng_state[&"cursor"] == 2 and (rng_state[&"phases"] as Array).size() == 4, "resolution replay consumes no RNG or boundary phase")
_expect(service.validate_invariants()[&"ok"], "RNG retry result satisfies invariants")
func _test_retreat_policies_and_manual_retreat_replay() -> void:
var threshold := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(threshold.create_raid(&"threshold", 0, _definition({&"retreat_policy": &"THRESHOLD", &"defender_retreat_threshold": 50})), "threshold Raid is created")
_must(threshold.register_combatant(&"threshold", &"guard", &"DEFENDER", 10, 100, {&"health": 40}), "wounded threshold guard is registered")
_must(threshold.register_asset(&"threshold", &"core", &"OBJECTIVE", 100, 5), "threshold objective is registered")
_must(threshold.start_raid(&"threshold", 0), "threshold Raid starts")
var threshold_round = threshold.resolve_next_round(&"threshold", &"threshold_round", 5)
_expect(threshold_round.is_success() and threshold_round.details[&"state"] == &"DEFENDERS_RETREATED", "THRESHOLD retreats at or below configured health percentage")
_expect(threshold.get_raid(&"threshold")[&"combatants"][&"guard"][&"state"] == DefenseServiceType.CombatantState.RETREATED, "automatic retreat updates active defenders")
var protect := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(protect.create_raid(&"protect", 0, _definition({&"retreat_policy": &"PROTECT_AND_RETREAT", &"defender_retreat_threshold": 50})), "protect Raid is created")
_must(protect.register_combatant(&"protect", &"guard", &"DEFENDER", 10, 100), "protect guard is registered")
_must(protect.register_asset(&"protect", &"core", &"OBJECTIVE", 100, 5, {&"integrity": 40}), "pre-damaged protected objective is registered")
_must(protect.start_raid(&"protect", 0), "protect Raid starts")
var protect_round = protect.resolve_next_round(&"protect", &"protect_round", 5)
_expect(protect_round.is_success() and protect_round.details[&"reason"] == &"RETREAT_POLICY_TRIGGERED", "PROTECT_AND_RETREAT observes objective integrity against full capacity")
for policy: StringName in [&"HOLD", &"MANUAL"]:
var holding := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(holding.create_raid(policy, 0, _definition({&"retreat_policy": policy, &"defender_retreat_threshold": 90})), "%s Raid is created" % policy)
_must(holding.register_combatant(policy, &"guard", &"DEFENDER", 10, 100, {&"health": 10}), "%s wounded guard is registered" % policy)
_must(holding.register_asset(policy, &"core", &"OBJECTIVE", 100, 5), "%s objective is registered" % policy)
_must(holding.start_raid(policy, 0), "%s Raid starts" % policy)
var round = holding.resolve_next_round(policy, StringName("%s_round" % policy), 5)
_expect(round.is_success() and round.details[&"state"] == &"ACTIVE", "%s never triggers automatic retreat" % policy)
var outcome_count := {&"apply": 0}
var manual := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50), Callable(),
func(_payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase == &"apply":
outcome_count[&"apply"] = int(outcome_count[&"apply"]) + 1
return {&"ok": true, &"token": token}
)
_prepare_basic(manual, &"manual")
_must(manual.queue_active_skill(&"manual", &"unused_skill", &"guard", &"RALLY", {&"potency": 2}), "manual retreat fixture queues an unused skill")
var retreated = manual.retreat_defenders(&"manual", &"manual_resolution", 3)
var replay = manual.retreat_defenders(&"manual", &"manual_resolution", 99)
_expect(retreated.is_success() and retreated.details[&"result"] == &"DEFENDERS_RETREATED", "manual retreat reaches its terminal state")
_expect(replay.is_success() and replay.is_replay and replay.details == retreated.details, "manual retreat resolution ID replays exact stored outcome")
_expect(outcome_count[&"apply"] == 1, "manual retreat consequence applies once across replay")
_expect(retreated.details[&"retreated_defender_ids"] == [&"guard"] and retreated.details[&"retreated_attacker_ids"].is_empty(), "outcome separates defender and attacker retreat IDs for reconciliation")
_expect(manual.get_raid(&"manual")[&"waves"][0][&"state"] == &"RETREATED", "manual retreat updates current wave state")
_expect(manual.get_raid(&"manual")[&"queued_skills"].is_empty(), "manual retreat discards skills that never crossed the apply boundary")
_expect(manual.validate_invariants()[&"ok"], "all retreat policy outcomes preserve invariants")
func _test_cancel_transaction_and_reconciliation() -> void:
var boundary_state := {&"apply_attempts": 0, &"rollbacks": 0, &"last_payload": {}}
var resolved_signals := {&"count": 0, &"payload": {}}
var outcome_boundary := func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase == &"apply":
boundary_state[&"apply_attempts"] = int(boundary_state[&"apply_attempts"]) + 1
boundary_state[&"last_payload"] = payload.duplicate(true)
if int(boundary_state[&"apply_attempts"]) == 1:
return {&"ok": false, &"token": &"cancel_attempt"}
elif phase == &"rollback":
boundary_state[&"rollbacks"] = int(boundary_state[&"rollbacks"]) + 1
return {&"ok": true, &"token": token}
var service := DefenseServiceType.new().configure_callbacks(
_fixed_roll(50), Callable(), outcome_boundary
)
service.raid_resolved.connect(func(_raid_id: StringName, payload: Dictionary) -> void:
resolved_signals[&"count"] = int(resolved_signals[&"count"]) + 1
resolved_signals[&"payload"] = payload.duplicate(true)
)
_must(service.create_raid(&"cancel_tx", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "cancel transaction Raid is created")
_must(service.register_combatant(&"cancel_tx", &"guard", &"DEFENDER", 10, 100), "cancel transaction guard is registered")
_must(service.register_asset(&"cancel_tx", &"core", &"OBJECTIVE", 1_000, 0), "cancel transaction objective is registered")
_must(service.register_asset(&"cancel_tx", &"door", &"DOOR", 30, 0), "cancel transaction door is registered")
_must(service.start_raid(&"cancel_tx", 0), "cancel transaction Raid starts")
_must(service.resolve_next_round(&"cancel_tx", &"damage_round", 5), "cancel transaction accumulates one round of damage")
_must(service.queue_active_skill(&"cancel_tx", &"pending_on_cancel", &"guard", &"RALLY", {
&"potency": 3, &"metadata": {&"source": "cancel-test"},
}), "cancel transaction retains a pending skill before cancellation")
var before_cancel := service.create_snapshot()
var rejected = service.cancel_raid(&"cancel_tx", 6, &"PLAYER_ABORT", &"cancel_resolution")
_expect(not rejected.is_success(), "cancel outcome rejection aborts cancellation")
_expect(service.create_snapshot() == before_cancel, "rejected cancellation preserves accumulated Raid state atomically")
_expect(boundary_state[&"apply_attempts"] == 1 and boundary_state[&"rollbacks"] == 1, "failed cancel apply invokes outcome rollback")
_expect(resolved_signals[&"count"] == 0, "rejected cancellation emits no raid_resolved signal")
var cancelled = service.cancel_raid(&"cancel_tx", 6, &"PLAYER_ABORT", &"cancel_resolution")
_expect(cancelled.is_success() and cancelled.details[&"result"] == &"CANCELLED", "accepted cancellation reaches CANCELLED through the outcome boundary")
_expect(cancelled.details[&"operation_kind"] == &"CANCEL", "cancel result stores its operation namespace")
_expect(cancelled.details[&"asset_results"] == boundary_state[&"last_payload"][&"asset_results"], "cancel boundary receives final accumulated asset integrity")
_expect(cancelled.details[&"combatant_results"] == boundary_state[&"last_payload"][&"combatant_results"], "cancel boundary receives final accumulated combatant state")
_expect(service.get_raid(&"cancel_tx")[&"queued_skills"].is_empty(), "accepted cancel discards skills that never crossed the apply boundary")
_expect(resolved_signals[&"count"] == 1 and resolved_signals[&"payload"] == cancelled.details, "accepted cancel emits one exact raid_resolved payload")
var replay = service.cancel_raid(&"cancel_tx", 999, &"DIFFERENT_REASON", &"cancel_resolution")
_expect(replay.is_success() and replay.is_replay and replay.details == cancelled.details, "cancel resolution replays canonically after terminal state")
_expect(boundary_state[&"apply_attempts"] == 2 and resolved_signals[&"count"] == 1, "cancel replay cannot reapply consequences or signals")
_expect(service.validate_invariants()[&"ok"], "transactional cancellation satisfies invariants")
func _test_resolution_operation_namespace_and_legacy_v1() -> void:
var round_service := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_prepare_basic(round_service, &"namespace")
var round_result = round_service.resolve_next_round(&"namespace", &"shared_id", 5)
_expect(round_result.is_success() and round_result.details[&"operation_kind"] == &"ROUND", "round history stores ROUND operation kind")
var after_round := round_service.create_snapshot()
var wrong_retreat = round_service.retreat_defenders(&"namespace", &"shared_id", 6)
_expect(not wrong_retreat.is_success() and not wrong_retreat.is_replay, "round resolution ID cannot replay as manual retreat")
_expect(round_service.create_snapshot() == after_round, "cross-operation retreat collision is atomic")
var wrong_cancel = round_service.cancel_raid(&"namespace", 6, &"CANCELLED", &"shared_id")
_expect(not wrong_cancel.is_success() and not wrong_cancel.is_replay, "round resolution ID cannot replay as cancellation")
_expect(round_service.create_snapshot() == after_round, "cross-operation cancel collision is atomic")
var retreat_service := DefenseServiceType.new()
_prepare_basic(retreat_service, &"retreat_namespace")
var retreat = retreat_service.retreat_defenders(&"retreat_namespace", &"terminal_id", 3)
_expect(retreat.is_success() and retreat.details[&"operation_kind"] == &"RETREAT", "manual retreat history stores RETREAT operation kind")
var wrong_round = retreat_service.resolve_next_round(&"retreat_namespace", &"terminal_id", 500)
_expect(not wrong_round.is_success() and not wrong_round.is_replay, "retreat resolution ID cannot replay as a round")
var legacy_round_snapshot := after_round.duplicate(true)
legacy_round_snapshot[&"raids"][0][&"resolved_rounds"][0][&"payload"].erase("operation_kind")
var legacy_round := DefenseServiceType.new()
_expect(legacy_round.restore_snapshot(legacy_round_snapshot), "version-1 round snapshot without operation_kind restores")
_expect(legacy_round.create_snapshot() == legacy_round_snapshot, "legacy round snapshot round-trips without forced schema rewrite")
var legacy_round_replay = legacy_round.resolve_next_round(&"namespace", &"shared_id", 999)
_expect(legacy_round_replay.is_success() and legacy_round_replay.is_replay, "legacy round operation kind is inferred for exact replay")
_expect(not legacy_round.retreat_defenders(&"namespace", &"shared_id", 999).is_success(), "legacy inferred round still rejects cross-operation replay")
var legacy_retreat_snapshot := retreat_service.create_snapshot()
legacy_retreat_snapshot[&"raids"][0][&"resolved_rounds"][0][&"payload"].erase("operation_kind")
var legacy_retreat := DefenseServiceType.new()
_expect(legacy_retreat.restore_snapshot(legacy_retreat_snapshot), "version-1 retreat snapshot without operation_kind restores")
var legacy_retreat_replay = legacy_retreat.retreat_defenders(&"retreat_namespace", &"terminal_id", 999)
_expect(legacy_retreat_replay.is_success() and legacy_retreat_replay.is_replay, "legacy retreat operation kind is inferred for exact replay")
var cancel_service := DefenseServiceType.new()
_prepare_basic(cancel_service, &"cancel_namespace")
_must(cancel_service.cancel_raid(&"cancel_namespace", 3, &"LEGACY_CANCEL", &"cancel_terminal_id"), "legacy cancel fixture reaches its terminal state")
var legacy_cancel_snapshot := cancel_service.create_snapshot()
legacy_cancel_snapshot[&"raids"][0][&"resolved_rounds"][0][&"payload"].erase("operation_kind")
var legacy_cancel := DefenseServiceType.new()
_expect(legacy_cancel.restore_snapshot(legacy_cancel_snapshot), "version-1 cancel snapshot without operation_kind restores")
_expect(legacy_cancel.create_snapshot() == legacy_cancel_snapshot, "legacy cancel snapshot round-trips without forced schema rewrite")
var legacy_cancel_replay = legacy_cancel.cancel_raid(&"cancel_namespace", 999, &"DIFFERENT", &"cancel_terminal_id")
_expect(legacy_cancel_replay.is_success() and legacy_cancel_replay.is_replay, "legacy cancel operation kind is inferred for exact replay")
_expect(not legacy_cancel.retreat_defenders(&"cancel_namespace", &"cancel_terminal_id", 999).is_success(), "legacy inferred cancel rejects cross-operation replay")
_expect(round_service.validate_invariants()[&"ok"] and retreat_service.validate_invariants()[&"ok"] and cancel_service.validate_invariants()[&"ok"], "operation namespaces preserve valid aggregates")
func _test_multiple_waves_require_explicit_transition() -> void:
var service := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(service.create_raid(&"multi", 0, _definition({
&"waves": [
{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 1},
{&"wave_id": &"wave_2", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 1},
],
})), "two-wave Raid is created")
_must(service.register_asset(&"multi", &"core", &"OBJECTIVE", 1_000, 0), "durable multi-wave objective is registered")
_must(service.start_raid(&"multi", 0), "multi-wave Raid starts")
var wave_one = service.resolve_next_round(&"multi", &"wave_one_round", 5)
_expect(wave_one.is_success() and wave_one.details[&"state"] == &"BETWEEN_WAVES", "first max-round wave ends BETWEEN_WAVES")
var between_snapshot := service.create_snapshot()
_expect(not service.resolve_next_round(&"multi", &"illegal_between_round", 6).is_success(), "round cannot resolve while waiting between waves")
_expect(service.create_snapshot() == between_snapshot, "failed between-wave round is atomic")
_expect(service.get_raid(&"multi")[&"current_wave_index"] == 0, "next wave never starts implicitly")
var begun = service.begin_next_wave(&"multi", 7)
_expect(begun.is_success() and begun.details[&"wave_id"] == &"wave_2" and begun.details[&"wave_index"] == 1, "explicit command starts exactly the next wave")
_expect(service.get_raid(&"multi")[&"waves"][1][&"state"] == &"ACTIVE", "next wave state becomes ACTIVE")
_expect(not service.begin_next_wave(&"multi", 7).is_success(), "active wave cannot be skipped")
var wave_two = service.resolve_next_round(&"multi", &"wave_two_round", 12)
_expect(wave_two.is_success() and wave_two.details[&"state"] == &"DEFENDED", "last wave completion defends the Raid")
var raid := service.get_raid(&"multi")
_expect(raid[&"current_round"] == 2 and raid[&"elapsed_minutes"] == 10, "round and elapsed counters span all waves")
_expect(raid[&"waves"][0][&"state"] == &"RESOLVED" and raid[&"waves"][1][&"state"] == &"RESOLVED", "each completed wave retains its own state")
_expect(not service.begin_next_wave(&"multi", 13).is_success(), "terminal Raid has no phantom next wave")
_expect(service.validate_invariants()[&"ok"], "multi-wave terminal state satisfies invariants")
func _test_raid_duration_and_wave_round_limits() -> void:
var duration := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(duration.create_raid(&"duration", 0, _definition({
&"max_duration_minutes": 5,
&"round_minutes": 5,
&"waves": [
{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 10},
{&"wave_id": &"wave_2", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 10},
],
})), "duration-limited Raid is created")
_must(duration.register_asset(&"duration", &"core", &"OBJECTIVE", 1_000, 0), "duration objective is registered")
_must(duration.start_raid(&"duration", 0), "duration Raid starts")
var duration_result = duration.resolve_next_round(&"duration", &"duration_round", 5)
_expect(duration_result.is_success() and duration_result.details[&"state"] == &"DEFENDED", "Raid-wide duration is a hard terminal boundary")
_expect(duration_result.details[&"reason"] == &"RAID_DURATION_EXHAUSTED", "duration terminal reason is explicit")
_expect(duration.get_raid(&"duration")[&"current_wave_index"] == 0, "duration never opens a later wave")
var max_round := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(max_round.create_raid(&"max_round", 0, _definition({
&"max_duration_minutes": 100,
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 1}],
})), "max-round Raid is created")
_must(max_round.register_asset(&"max_round", &"core", &"OBJECTIVE", 1_000, 0), "max-round objective is registered")
_must(max_round.start_raid(&"max_round", 0), "max-round Raid starts")
var round_result = max_round.resolve_next_round(&"max_round", &"max_round_resolution", 5)
_expect(round_result.is_success() and round_result.details[&"reason"] == &"WAVE_DURATION_EXHAUSTED", "wave max-round limit has a stable terminal reason")
_expect(max_round.get_raid(&"max_round")[&"result"] == &"DEFENDED", "surviving the final wave round limit defends the Raid")
_expect(duration.validate_invariants()[&"ok"] and max_round.validate_invariants()[&"ok"], "both duration boundaries remain valid")
func _test_round_resolution_replay() -> void:
var roll_calls := {&"count": 0}
var roll_keys: Array[StringName] = []
var service := DefenseServiceType.new().configure_callbacks(
func(key: StringName, _context: Dictionary) -> int:
roll_calls[&"count"] = int(roll_calls[&"count"]) + 1
roll_keys.append(key)
return 50
)
_prepare_basic(service, &"replay")
var first = service.resolve_next_round(&"replay", &"same_resolution", 5)
var snapshot_after_first := service.create_snapshot()
var replay = service.resolve_next_round(&"replay", &"same_resolution", 500)
_expect(first.is_success() and replay.is_success() and replay.is_replay, "duplicate resolution ID returns an idempotent replay")
_expect(replay.details == first.details, "resolution replay returns the exact canonical payload")
_expect(service.create_snapshot() == snapshot_after_first, "resolution replay mutates no Raid or clock state")
_expect(roll_calls[&"count"] == 2, "defense and attack rolls are consumed only on first resolution")
_expect(roll_keys == [&"replay:wave_1:1:DEFENSE", &"replay:wave_1:1:ATTACK"], "round roll keys and call order are stable")
_expect(service.get_raid(&"replay")[&"current_round"] == 1, "replay cannot advance the round twice")
_expect(service.validate_invariants()[&"ok"], "replayed Raid remains valid")
var default_a := DefenseServiceType.new()
var default_b := DefenseServiceType.new()
_prepare_basic(default_a, &"default_roll")
_prepare_basic(default_b, &"default_roll")
var default_result_a = default_a.resolve_next_round(&"default_roll", &"default_round", 5)
var default_result_b = default_b.resolve_next_round(&"default_roll", &"default_round", 5)
_expect(default_result_a.details[&"defense_roll"] == default_result_b.details[&"defense_roll"] and default_result_a.details[&"attack_roll"] == default_result_b.details[&"attack_roll"], "built-in fallback rolls are deterministic from stable keys")
_expect(default_result_a.details == default_result_b.details and default_a.create_snapshot() == default_b.create_snapshot(), "built-in deterministic roll reproduces the full round")
func _test_round_clock_contract() -> void:
var service := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_prepare_basic(service, &"clock_contract")
var initial := service.create_snapshot()
var too_early = service.resolve_next_round(&"clock_contract", &"too_early", 4)
_expect(not too_early.is_success() and too_early.details[&"earliest_minute"] == 5, "first round waits one full configured interval after start")
_expect(service.create_snapshot() == initial, "early first round rejection is atomic")
_must(service.resolve_next_round(&"clock_contract", &"round_1", 5), "first round resolves at its exact boundary")
var after_first := service.create_snapshot()
_expect(not service.resolve_next_round(&"clock_contract", &"same_minute_other_id", 5).is_success(), "different resolution ID cannot create a second round in the same minute")
_expect(not service.resolve_next_round(&"clock_contract", &"before_interval", 9).is_success(), "later round cannot violate previous-round interval")
_expect(service.create_snapshot() == after_first, "same-minute and interval failures preserve round history")
_must(service.resolve_next_round(&"clock_contract", &"round_2", 10), "second round resolves after the full interval")
_must(service.advance_time(20), "Defense clock advances independently")
var after_clock := service.create_snapshot()
_expect(not service.resolve_next_round(&"clock_contract", &"past_global_clock", 19).is_success(), "round cannot be inserted behind the current Defense clock")
_expect(service.create_snapshot() == after_clock, "past-global-clock rejection is atomic")
_must(service.resolve_next_round(&"clock_contract", &"round_3", 20), "round at current clock succeeds when interval is satisfied")
var historical := DefenseServiceType.new()
_must(historical.advance_time(10), "historical fixture clock advances")
_expect(not historical.create_raid(&"past_create", 9, _definition()).is_success(), "Raid cannot be created in the past of the Defense clock")
_must(historical.create_raid(&"future_start", 10, _definition()), "Raid can be created at the current clock")
_must(historical.register_asset(&"future_start", &"core", &"OBJECTIVE", 100, 0), "future-start objective is registered")
_must(historical.advance_time(15), "clock advances before Raid start")
_expect(not historical.start_raid(&"future_start", 14).is_success(), "Raid cannot start behind the current Defense clock")
_must(historical.start_raid(&"future_start", 15), "Raid starts at the current Defense clock")
_expect(not historical.resolve_next_round(&"future_start", &"early_after_start", 19).is_success(), "first round interval is measured from actual start time")
_must(historical.resolve_next_round(&"future_start", &"on_time_after_start", 20), "first round resolves exactly one interval after late start")
_expect(service.validate_invariants()[&"ok"] and historical.validate_invariants()[&"ok"], "clock-contract fixtures satisfy invariants")
func _test_wave_entry_and_metadata_validation() -> void:
var entry_service := DefenseServiceType.new()
_must(entry_service.create_raid(&"entry_scope", 0, _definition({
&"entry_points": [&"east", &"west"],
&"waves": [{
&"wave_id": &"east_wave", &"threat_strength": 20,
&"entry_points": [&"east"], &"objective_ids": [&"core"],
}],
})), "entry-scope Raid is created")
_expect(not entry_service.register_combatant(&"entry_scope", &"wrong_gate", &"ATTACKER", 10, 10, {
&"wave_id": &"east_wave", &"entry_point_id": &"west",
}).is_success(), "attacker cannot use a global entry omitted by its assigned wave")
_expect(entry_service.register_combatant(&"entry_scope", &"east_raider", &"ATTACKER", 10, 10, {
&"wave_id": &"east_wave", &"entry_point_id": &"east",
}).is_success(), "attacker can use an entry included by its assigned wave")
var metadata_service := DefenseServiceType.new()
_prepare_basic(metadata_service, &"metadata")
var metadata_before := metadata_service.create_snapshot()
var invalid_metadata: Array = [
{&"label": "non-dictionary root", &"value": 7},
{&"label": "Object", &"value": {"bad": RefCounted.new()}},
{&"label": "Callable", &"value": {"bad": Callable(self, "_run")}},
{&"label": "RID", &"value": {"bad": RID()}},
{&"label": "NaN", &"value": {"bad": NAN}},
{&"label": "positive infinity", &"value": {"nested": [1, INF]}},
{&"label": "non-string dictionary key", &"value": {1: "bad"}},
]
for fixture: Dictionary in invalid_metadata:
var result = metadata_service.queue_active_skill(&"metadata", &"invalid_meta", &"guard", &"RALLY", {
&"potency": 1, &"metadata": fixture[&"value"],
})
_expect(not result.is_success(), "skill metadata rejects %s" % fixture[&"label"])
_expect(metadata_service.create_snapshot() == metadata_before, "rejected %s metadata is atomic" % fixture[&"label"])
var metadata_a := {"zeta": [&"name", 2, true, null], "alpha": {"b": 2.5, "a": "first"}}
var metadata_b := {"alpha": {"a": "first", "b": 2.5}, "zeta": [&"name", 2, true, null]}
_must(metadata_service.queue_active_skill(&"metadata", &"canonical", &"guard", &"RALLY", {
&"potency": 1, &"metadata": metadata_a,
}), "nested saveable metadata is accepted")
var stored_metadata: Dictionary = metadata_service.get_raid(&"metadata")[&"queued_skills"][&"canonical"][&"metadata"]
_expect(stored_metadata.keys() == ["alpha", "zeta"], "metadata dictionary keys are stored in lexical order")
_expect(stored_metadata["zeta"][0] == "name" and stored_metadata["alpha"].keys() == ["a", "b"], "metadata recursively canonicalizes StringName values and nested keys")
var metadata_peer := DefenseServiceType.new()
_prepare_basic(metadata_peer, &"metadata")
_must(metadata_peer.queue_active_skill(&"metadata", &"canonical", &"guard", &"RALLY", {
&"potency": 1, &"metadata": metadata_b,
}), "reverse-order metadata fixture is accepted")
_expect(metadata_service.create_snapshot() == metadata_peer.create_snapshot(), "metadata snapshot is independent of dictionary insertion order")
var corrupt_metadata := metadata_service.create_snapshot()
corrupt_metadata[&"raids"][0][&"queued_skills"][0][&"metadata"] = {"bad": NAN}
var restored := DefenseServiceType.new()
_prepare_basic(restored, &"restored_baseline")
var restored_before := restored.create_snapshot()
_expect(not restored.restore_snapshot(corrupt_metadata), "snapshot restore rejects non-finite queued metadata")
_expect(restored.create_snapshot() == restored_before, "corrupt metadata snapshot rejection is atomic")
_expect(metadata_service.validate_invariants()[&"ok"] and metadata_peer.validate_invariants()[&"ok"], "canonical metadata fixtures satisfy invariants")
func _test_armor_pool_accounting() -> void:
var blocked := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(blocked.create_raid(&"armor_block", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 20, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "armor-block Raid is created")
_must(blocked.register_combatant(&"armor_block", &"guard", &"DEFENDER", 100, 100), "armor-block guard is registered")
_must(blocked.register_combatant(&"armor_block", &"armored_raider", &"ATTACKER", 1, 100, {
&"wave_id": &"wave_1", &"armor": 100,
}), "heavily armored attacker is registered")
_must(blocked.register_asset(&"armor_block", &"core", &"OBJECTIVE", 1_000, 0), "armor-block objective is registered")
_must(blocked.start_raid(&"armor_block", 0), "armor-block Raid starts")
var blocked_round = blocked.resolve_next_round(&"armor_block", &"blocked_round", 5)
_expect(blocked_round.is_success() and blocked_round.details[&"damage_to_threat"] > 0, "armored target receives a positive raw damage budget")
_expect(blocked_round.details[&"damage_applied_to_threat"] == 0, "armor uses max(0, damage - armor) without forced chip damage")
_expect(blocked_round.details[&"aggregate_damage"] == 0, "damage absorbed by explicit armor cannot leak into aggregate threat")
_expect(blocked_round.details[&"damage_mitigated_by_attackers"] == blocked_round.details[&"damage_to_threat"], "mitigation accounts for the entire blocked damage pool")
_expect(blocked.get_raid(&"armor_block")[&"waves"][0][&"remaining_threat"] == 20 and blocked.get_raid(&"armor_block")[&"combatants"][&"armored_raider"][&"health"] == 100, "full armor block changes neither explicit nor aggregate durability")
var overflow := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(overflow.create_raid(&"armor_overflow", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 10, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "armor-overflow Raid is created")
_must(overflow.register_combatant(&"armor_overflow", &"guard", &"DEFENDER", 200, 100), "armor-overflow guard is registered")
_must(overflow.register_combatant(&"armor_overflow", &"front_raider", &"ATTACKER", 2, 2, {
&"wave_id": &"wave_1", &"armor": 5,
}), "front armored attacker is registered")
_must(overflow.register_asset(&"armor_overflow", &"core", &"OBJECTIVE", 1_000, 0), "armor-overflow objective is registered")
_must(overflow.start_raid(&"armor_overflow", 0), "armor-overflow Raid starts")
var overflow_round = overflow.resolve_next_round(&"armor_overflow", &"overflow_round", 5)
_expect(overflow_round.details[&"damage_to_threat"] == 12, "raw damage is capped by combined explicit and aggregate durability")
_expect(overflow_round.details[&"attacker_damage"][0][&"damage"] == 2 and overflow_round.details[&"attacker_damage"][0][&"mitigated"] == 5, "front attacker consumes armor plus remaining health from the pool")
_expect(overflow_round.details[&"aggregate_damage"] == 5, "only true raw overflow reaches aggregate threat")
_expect(overflow_round.details[&"damage_applied_to_threat"] == 7 and overflow_round.details[&"damage_mitigated_by_attackers"] == 5, "applied plus mitigated damage reconciles the complete pool")
_expect(overflow.get_raid(&"armor_overflow")[&"waves"][0][&"remaining_threat"] == 5, "aggregate durability decreases by overflow exactly once")
var defended := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(defended.create_raid(&"defense_armor", 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "defense-armor Raid is created")
_must(defended.register_asset(&"defense_armor", &"core", &"OBJECTIVE", 1_000, 0), "defense-armor objective is registered")
_must(defended.register_asset(&"defense_armor", &"door", &"DOOR", 100, 0, {&"armor": 100}), "fully armored defense door is registered")
_must(defended.start_raid(&"defense_armor", 0), "defense-armor Raid starts")
var defense_round = defended.resolve_next_round(&"defense_armor", &"defense_armor_round", 5)
_expect(defense_round.details[&"damage_to_defense"] > 0 and defense_round.details[&"defense_damage"][&"damage"] == 0, "defense armor also applies max(0, damage - armor)")
_expect(defense_round.details[&"defense_damage"][&"mitigated"] == defense_round.details[&"damage_to_defense"] and defended.get_raid(&"defense_armor")[&"assets"][&"door"][&"integrity"] == 100, "fully mitigated defense damage leaves integrity unchanged")
_expect(blocked.validate_invariants()[&"ok"] and overflow.validate_invariants()[&"ok"] and defended.validate_invariants()[&"ok"], "armor pool fixtures satisfy invariants")
func _test_snapshot_round_trip_and_corrupt_atomicity() -> void:
var source := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_must(source.create_raid(&"snapshot_raid", 0, _definition({
&"entry_points": [&"west", &"east"],
&"waves": [
{&"wave_id": &"wave_1", &"threat_strength": 60, &"entry_points": [&"east", &"west"], &"max_rounds": 10},
{&"wave_id": &"wave_2", &"threat_strength": 30, &"entry_points": [&"west"], &"max_rounds": 10},
],
})), "snapshot Raid is created")
_must(source.register_combatant(&"snapshot_raid", &"guard_b", &"DEFENDER", 20, 100), "snapshot defender B is registered")
_must(source.register_combatant(&"snapshot_raid", &"guard_a", &"DEFENDER", 10, 80), "snapshot defender A is registered")
_must(source.register_combatant(&"snapshot_raid", &"raider", &"ATTACKER", 15, 30, {&"wave_id": &"wave_1", &"entry_point_id": &"east"}), "snapshot attacker is registered")
_must(source.register_asset(&"snapshot_raid", &"core", &"OBJECTIVE", 200, 20), "snapshot objective is registered")
_must(source.register_asset(&"snapshot_raid", &"door", &"DOOR", 50, 10, {&"entry_points": [&"east"]}), "snapshot door is registered")
_must(source.start_raid(&"snapshot_raid", 0), "snapshot Raid starts")
_must(source.queue_active_skill(&"snapshot_raid", &"applied", &"guard_a", &"RALLY", {&"potency": 4, &"metadata": {&"source": "test"}}), "snapshot applied skill is queued")
_must(source.resolve_next_round(&"snapshot_raid", &"snapshot_round", 5), "snapshot round is resolved")
_must(source.queue_active_skill(&"snapshot_raid", &"queued", &"guard_b", &"POWER_STRIKE", {&"potency": 2, &"metadata": {&"source": "test"}}), "snapshot pending skill is queued")
var snapshot := source.create_snapshot()
var restored := DefenseServiceType.new()
_expect(restored.restore_snapshot(snapshot), "valid Defense snapshot restores")
_expect(restored.create_snapshot() == snapshot, "Defense snapshot has an exact round trip")
_expect(restored.validate_invariants()[&"ok"], "restored Defense snapshot satisfies invariants")
_expect(restored.get_raid_ids() == [&"snapshot_raid"] and restored.get_active_raid_ids() == [&"snapshot_raid"], "restored indexes expose canonical Raid IDs")
var restored_replay = restored.resolve_next_round(&"snapshot_raid", &"snapshot_round", 999)
_expect(restored_replay.is_success() and restored_replay.is_replay and restored.get_raid(&"snapshot_raid")[&"current_round"] == 1, "restored resolution history remains idempotent")
_expect(JSON.stringify(restored_replay.details) == JSON.stringify(snapshot[&"raids"][0][&"resolved_rounds"][0][&"payload"]), "restored replay returns its exact canonical payload")
var json_snapshot: Variant = JSON.parse_string(JSON.stringify(snapshot))
var json_restored := DefenseServiceType.new()
_expect(json_snapshot is Dictionary and json_restored.restore_snapshot(json_snapshot), "JSON-serialized Defense snapshot restores")
var expected_json := JSON.stringify(snapshot)
var actual_json := JSON.stringify(json_restored.create_snapshot())
_expect(actual_json == expected_json, "JSON transport preserves the canonical Defense snapshot")
# Each corrupt restore is attempted against a non-empty live aggregate. A
# rejection must preserve the previously restored snapshot byte-for-value.
var corruptions: Array[Dictionary] = []
var duplicate := snapshot.duplicate(true)
duplicate[&"raids"].append(duplicate[&"raids"][0].duplicate(true))
corruptions.append({&"name": "duplicate Raid ID", &"snapshot": duplicate})
var bad_remaining := snapshot.duplicate(true)
bad_remaining[&"raids"][0][&"waves"][0][&"remaining_threat"] = 9_999
corruptions.append({&"name": "remaining threat above maximum", &"snapshot": bad_remaining})
var bad_wave_state := snapshot.duplicate(true)
bad_wave_state[&"raids"][0][&"waves"][0][&"state"] = "TELEPORTING"
corruptions.append({&"name": "unknown wave state", &"snapshot": bad_wave_state})
var bad_clock := snapshot.duplicate(true)
bad_clock[&"current_minute"] = -1
corruptions.append({&"name": "negative Defense clock", &"snapshot": bad_clock})
var future_clock := snapshot.duplicate(true)
future_clock[&"current_minute"] = 0
future_clock[&"raids"][0][&"state_changed_at"] = 1
corruptions.append({&"name": "Raid timestamp beyond clock", &"snapshot": future_clock})
var bad_health := snapshot.duplicate(true)
bad_health[&"raids"][0][&"combatants"][0][&"health"] = -1
corruptions.append({&"name": "negative combatant health", &"snapshot": bad_health})
var bad_asset_entry := snapshot.duplicate(true)
bad_asset_entry[&"raids"][0][&"assets"][0][&"entry_points"] = ["not_declared"]
corruptions.append({&"name": "unknown asset entry", &"snapshot": bad_asset_entry})
var bad_attacker_wave_entry := snapshot.duplicate(true)
bad_attacker_wave_entry[&"raids"][0][&"waves"][0][&"entry_points"] = ["west"]
corruptions.append({
&"name": "attacker entry omitted by assigned wave",
&"snapshot": bad_attacker_wave_entry,
})
var bad_skill := snapshot.duplicate(true)
bad_skill[&"raids"][0][&"queued_skills"][0][&"potency"] = 101
corruptions.append({&"name": "invalid queued skill potency", &"snapshot": bad_skill})
for corruption: Dictionary in corruptions:
var before := restored.create_snapshot()
_expect(not restored.restore_snapshot(corruption[&"snapshot"]), "corrupt snapshot rejects %s" % corruption[&"name"])
_expect(restored.create_snapshot() == before, "rejected %s is atomic" % corruption[&"name"])
_expect(not restored.last_error.is_empty(), "corrupt restore exposes a diagnostic error")
_expect(restored.validate_invariants()[&"ok"], "all corrupt snapshot attempts preserve live invariants")
func _test_cross_time_snapshot_rejection() -> void:
var source := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
_prepare_basic(source, &"time_snapshot")
_must(source.resolve_next_round(&"time_snapshot", &"time_round_1", 5), "time snapshot first round resolves")
_must(source.resolve_next_round(&"time_snapshot", &"time_round_2", 10), "time snapshot second round resolves")
var valid := source.create_snapshot()
var restored := DefenseServiceType.new()
_expect(restored.restore_snapshot(valid), "valid cross-time snapshot restores")
var corruptions: Array[Dictionary] = []
var start_after_state := valid.duplicate(true)
start_after_state[&"raids"][0][&"started_at"] = 11
corruptions.append({&"name": "start after state change", &"snapshot": start_after_state})
var first_too_early := valid.duplicate(true)
first_too_early[&"raids"][0][&"resolved_rounds"][0][&"payload"]["absolute_minute"] = 4
corruptions.append({&"name": "first round before start interval", &"snapshot": first_too_early})
var same_minute_rounds := valid.duplicate(true)
same_minute_rounds[&"raids"][0][&"resolved_rounds"][1][&"payload"]["absolute_minute"] = 5
corruptions.append({&"name": "two rounds in one minute", &"snapshot": same_minute_rounds})
var resolution_after_state := valid.duplicate(true)
resolution_after_state[&"raids"][0][&"resolved_rounds"][1][&"payload"]["absolute_minute"] = 11
corruptions.append({&"name": "resolution after state change", &"snapshot": resolution_after_state})
var missing_history := valid.duplicate(true)
missing_history[&"raids"][0][&"resolved_rounds"].remove_at(0)
corruptions.append({&"name": "missing round history", &"snapshot": missing_history})
var invalid_operation := valid.duplicate(true)
invalid_operation[&"raids"][0][&"resolved_rounds"][0][&"payload"]["operation_kind"] = "DUEL"
corruptions.append({&"name": "invalid explicit operation kind", &"snapshot": invalid_operation})
var clock_before_state := valid.duplicate(true)
clock_before_state[&"current_minute"] = 9
corruptions.append({&"name": "clock before latest state", &"snapshot": clock_before_state})
var live_finished := valid.duplicate(true)
live_finished[&"raids"][0][&"finished_at"] = 10
corruptions.append({&"name": "live Raid with finish time", &"snapshot": live_finished})
for corruption: Dictionary in corruptions:
var before := restored.create_snapshot()
_expect(not restored.restore_snapshot(corruption[&"snapshot"]), "cross-time snapshot rejects %s" % corruption[&"name"])
_expect(restored.create_snapshot() == before, "rejected %s remains atomic" % corruption[&"name"])
var terminal := DefenseServiceType.new()
_prepare_basic(terminal, &"terminal_time")
_must(terminal.cancel_raid(&"terminal_time", 2, &"TEST_CANCEL", &"terminal_cancel"), "terminal time fixture cancels")
var bad_finish := terminal.create_snapshot()
bad_finish[&"raids"][0][&"finished_at"] = 1
var before_terminal_restore := restored.create_snapshot()
_expect(not restored.restore_snapshot(bad_finish), "terminal snapshot rejects finish time different from state change")
_expect(restored.create_snapshot() == before_terminal_restore, "terminal finish-time rejection is atomic")
var bad_terminal_resolution_time := terminal.create_snapshot()
bad_terminal_resolution_time[&"raids"][0][&"resolved_rounds"][0][&"payload"]["absolute_minute"] = 1
_expect(not restored.restore_snapshot(bad_terminal_resolution_time), "terminal command resolution must occur at the Raid finish time")
_expect(restored.create_snapshot() == before_terminal_restore, "terminal resolution-time rejection is atomic")
var wrong_terminal_operation := terminal.create_snapshot()
wrong_terminal_operation[&"raids"][0][&"resolved_rounds"][0][&"payload"]["operation_kind"] = "RETREAT"
_expect(not restored.restore_snapshot(wrong_terminal_operation), "terminal operation kind must agree with its result and Raid state")
_expect(restored.create_snapshot() == before_terminal_restore, "terminal operation mismatch rejection is atomic")
_expect(restored.validate_invariants()[&"ok"], "cross-time corruptions preserve restored invariants")
func _test_input_order_determinism() -> void:
var forward := _ordered_fixture(false)
var reverse := _ordered_fixture(true)
_expect(forward.create_snapshot() == reverse.create_snapshot(), "canonical snapshot is independent of registration and collection input order")
var forward_round = forward.resolve_next_round(&"ordered", &"ordered_round", 5)
var reverse_round = reverse.resolve_next_round(&"ordered", &"ordered_round", 5)
_expect(forward_round.is_success() and reverse_round.is_success(), "both input-order variants resolve")
_expect(forward_round.details == reverse_round.details, "round payload is input-order deterministic")
_expect(forward.create_snapshot() == reverse.create_snapshot(), "post-round aggregate is input-order deterministic")
_expect(forward_round.details[&"defense_damage"][&"target_id"] == &"a_door", "equal-priority asset targeting uses lexical stable ID")
_expect(forward_round.details[&"attacker_damage"][0][&"target_id"] == &"a_attacker", "explicit attacker damage uses lexical stable ID")
_expect(forward.validate_invariants()[&"ok"] and reverse.validate_invariants()[&"ok"], "both deterministic orderings preserve invariants")
func _definition(overrides: Dictionary = {}) -> Dictionary:
var definition := {
&"source_id": &"fixture",
&"entry_points": [&"gate"],
&"objective_ids": [&"core"],
&"waves": [{
&"wave_id": &"wave_1",
&"threat_strength": 30,
&"retreat_threshold": 0,
&"max_rounds": 12,
}],
&"max_duration_minutes": 120,
&"round_minutes": 5,
&"retreat_policy": &"HOLD",
&"defender_retreat_threshold": 20,
&"terrain_bonus": 0,
&"command_bonus": 0,
&"surprise_penalty": 0,
&"multiple_entry_penalty": 5,
}
for key: Variant in overrides.keys():
definition[key] = overrides[key]
return definition
func _fixed_roll(value: int) -> Callable:
return func(_key: StringName, _context: Dictionary) -> int: return value
func _prepare_basic(service: DefenseService, raid_id: StringName) -> void:
_must(service.create_raid(raid_id, 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": 100, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "basic Raid is created: %s" % raid_id)
_must(service.register_combatant(raid_id, &"guard", &"DEFENDER", 20, 100), "basic guard is registered: %s" % raid_id)
_must(service.register_asset(raid_id, &"core", &"OBJECTIVE", 1_000, 10), "basic objective is registered: %s" % raid_id)
_must(service.start_raid(raid_id, 0), "basic Raid starts: %s" % raid_id)
func _attack_mix_service(raid_id: StringName, include_explicit: bool) -> DefenseService:
var service := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
var aggregate_strength := 10 if include_explicit else 12
_must(service.create_raid(raid_id, 0, _definition({
&"waves": [{&"wave_id": &"wave_1", &"threat_strength": aggregate_strength, &"retreat_threshold": 0, &"max_rounds": 10}],
})), "attack mix Raid is created: %s" % raid_id)
_must(service.register_combatant(raid_id, &"guard", &"DEFENDER", 100, 100), "attack mix guard is registered: %s" % raid_id)
if include_explicit:
_must(service.register_combatant(raid_id, &"visible_raider", &"ATTACKER", 2, 2, {&"wave_id": &"wave_1"}), "visible attacker is registered")
_must(service.register_asset(raid_id, &"core", &"OBJECTIVE", 1_000, 0), "attack mix objective is registered: %s" % raid_id)
_must(service.start_raid(raid_id, 0), "attack mix Raid starts: %s" % raid_id)
return service
func _ordered_fixture(reverse: bool) -> DefenseService:
var service := DefenseServiceType.new().configure_callbacks(_fixed_roll(50))
var entries: Array[StringName] = [&"east", &"west"]
if reverse:
entries.reverse()
_must(service.create_raid(&"ordered", 0, _definition({
&"entry_points": entries,
&"waves": [{
&"wave_id": &"wave_1", &"threat_strength": 40,
&"entry_points": entries.duplicate(), &"objective_ids": [&"core"],
&"retreat_threshold": 0, &"max_rounds": 10,
}],
})), "ordered Raid definition is created")
var combatants := [
[&"z_guard", &"DEFENDER", 10, 100, {}],
[&"a_guard", &"DEFENDER", 20, 100, {}],
[&"z_attacker", &"ATTACKER", 5, 20, {&"wave_id": &"wave_1"}],
[&"a_attacker", &"ATTACKER", 5, 20, {&"wave_id": &"wave_1"}],
]
var assets := [
[&"core", &"OBJECTIVE", 1_000, 10, {}],
[&"z_door", &"DOOR", 50, 5, {&"target_order": 10}],
[&"a_door", &"DOOR", 50, 5, {&"target_order": 10}],
]
if reverse:
combatants.reverse()
assets.reverse()
for row: Array in combatants:
_must(service.register_combatant(&"ordered", row[0], row[1], row[2], row[3], row[4]), "ordered combatant registers: %s" % row[0])
for row: Array in assets:
_must(service.register_asset(&"ordered", row[0], row[1], row[2], row[3], row[4]), "ordered asset registers: %s" % row[0])
_must(service.start_raid(&"ordered", 0), "ordered Raid starts")
return service
func _must(result: CommandResult, message: String) -> bool:
return _expect(result != null and result.is_success(), message)
func _expect(condition: bool, message: String) -> bool:
_checks += 1
if condition:
return true
_failures += 1
push_error(" CHECK FAILED: %s" % message)
return false
+1
View File
@@ -0,0 +1 @@
uid://ceq33fn7vgm51
+542
View File
@@ -0,0 +1,542 @@
extends SceneTree
const PLAYER_CASH := &"PLAYER_CASH"
const AUTHORITY_FINE_EXPENSE := &"AUTHORITY_FINE_EXPENSE"
var _checks := 0
var _failures := 0
var _clock: Variant
var _state: Variant
var _runtime: Variant
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S5: integrated Authority runtime cycle")
_test_incident_visibility_and_daily_evaluation()
_test_resolution_replay_unreachable_exit_and_long_horizon()
_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()
_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 Authority aggregate")
_expect(
_runtime.authority_service.current_minute == _clock.absolute_minute,
"Authority time begins aligned with SimulationKernel"
)
_expect(
_runtime.economy_service.has_account(AUTHORITY_FINE_EXPENSE),
"Authority fine expense account exists at initialization"
)
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_incident_visibility_and_daily_evaluation() -> void:
_reset_runtime(5_501, 5, 59)
var reported := _submit(
&"report_authority_incident",
{
&"incident_id": &"incident.runtime.shared",
&"incident_type": &"illegal_facility",
&"detector_id": &"detector.noise",
&"options": {
&"severity": 80,
&"heat_delta": 100,
&"facility_id": &"facility.hidden",
},
},
&"s5.incident.first"
)
var duplicate := _submit(
&"report_authority_incident",
{
&"incident_id": &"incident.runtime.shared",
&"incident_type": &"conflicting_payload",
&"detector_id": &"detector.witness",
&"options": {&"severity": 100, &"heat_delta": 100},
},
&"s5.incident.second-detector"
)
var incident: AuthorityIncident = _runtime.authority_service.get_incident(&"incident.runtime.shared")
_expect(reported.is_success(), "incident report executes through Runtime Command")
_expect(
duplicate.is_success() and duplicate.is_replay,
"incident_id deduplicates reports from independent detectors"
)
_expect(
_runtime.authority_service.get_authority_state().heat == 100,
"duplicate detector cannot apply Heat twice"
)
_expect(
incident.detector_ids == [&"detector.noise", &"detector.witness"]
and incident.incident_type == &"illegal_facility",
"dedup retains sorted detector audit data and the first canonical payload"
)
_expect(
_count_domain_events(&"INCIDENT_CREATED", incident.incident_id) == 1
and _count_domain_events(&"INCIDENT_DETECTOR_ADDED", incident.incident_id) == 1,
"Authority signals project once into DomainEventStream"
)
var wall := Vector3i(1, 0, WorldService.LAYER_FRONT)
var evidence_cell := Vector3i(2, 0, WorldService.LAYER_FRONT)
_expect(_runtime.world_service.set_walkable(wall, false), "LOS fixture wall is installed")
_expect(_runtime.rebuild_path_graph_from_world(), "Path graph rebuilds after wall installation")
var registered := _submit(
&"register_authority_evidence",
{
&"evidence_id": &"evidence.runtime.wall",
&"source": &"visual_trace",
&"severity": 35,
&"options": {
&"incident_id": incident.incident_id,
&"facility_id": &"facility.hidden",
&"location_cell": evidence_cell,
&"expires_at": 2_000,
},
},
&"s5.evidence.register"
)
var blocked := _submit(
&"discover_authority_evidence",
{
&"evidence_id": &"evidence.runtime.wall",
&"inspector_id": &"inspector.los",
&"inspector_cell": Vector3i(0, 0, WorldService.LAYER_FRONT),
&"context": {},
},
&"s5.evidence.blocked"
)
_expect(registered.is_success(), "latent evidence registers through Runtime Command")
_expect(
not blocked.is_success() and blocked.details.get(&"reason", &"") == &"LINE_OF_SIGHT_BLOCKED",
"WorldService wall blocks automatic evidence discovery"
)
_expect(
_runtime.authority_service.get_authority_state().evidence_points == 0,
"wall-blocked evidence contributes no Authority score"
)
_expect(_runtime.world_service.set_walkable(wall, true), "LOS fixture wall is removed")
_expect(_runtime.rebuild_path_graph_from_world(), "Path graph rebuilds after opening sight line")
var discovered := _submit(
&"discover_authority_evidence",
{
&"evidence_id": &"evidence.runtime.wall",
&"inspector_id": &"inspector.los",
&"inspector_cell": Vector3i(0, 0, WorldService.LAYER_FRONT),
&"context": {&"illegal_confirmed": true},
},
&"s5.evidence.visible"
)
_expect(
discovered.is_success() and discovered.details[&"discovery_method"] == &"LINE_OF_SIGHT",
"evidence becomes visible only after the World callback confirms sight"
)
_expect(
_runtime.authority_service.get_authority_state().evidence_points == 35,
"visible evidence contributes its deterministic Incident score"
)
var authority_rng: DeterministicRng = _runtime.get_rng_stream(&"authority")
var expected_rng := DeterministicRng.from_root_seed(0, &"authority") as DeterministicRng
_expect(expected_rng.restore(authority_rng.snapshot()), "Authority RNG snapshot clones for prediction")
var expected_roll := expected_rng.randi_range(0, 99)
var before_six: Dictionary = _runtime.create_snapshot()
_clock.advance_one_minute()
var evaluation := _evaluation_for_day(0)
_expect(
_clock.absolute_minute == 360 and _runtime.authority_service.current_minute == 360,
"INCIDENTS_AND_EVENTS advances Authority time at 06:00"
)
_expect(
int(evaluation.get(&"roll", -1)) == expected_roll
and bool(evaluation.get(&"selected", false)) == (expected_roll < int(evaluation.get(&"chance", 0))),
"06:00 inspection decision consumes only the deterministic Authority RNG stream"
)
_expect(
int(evaluation.get(&"chance", -1)) == 95
and not StringName(evaluation.get(&"inspection_id", &"")).is_empty(),
"Heat and active-case terms deterministically schedule the selected inspection"
)
_expect(
_runtime.get_rng_stream(&"authority").get_counter() == expected_rng.get_counter(),
"06:00 evaluation advances the Authority RNG counter exactly once"
)
_expect(
_count_domain_events(&"AUTHORITY_INSPECTION_EVALUATED") == 1
and _count_domain_events(&"INSPECTION_SCHEDULED") == 1,
"automatic inspection selection emits its Domain Events once"
)
var first_six_snapshot: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(before_six), "pre-06:00 Runtime snapshot restores atomically")
_clock.advance_one_minute()
_expect(
_runtime.create_snapshot() == first_six_snapshot,
"restored 06:00 Authority selection reproduces the full Runtime snapshot"
)
var stable_before_corruption: Dictionary = _runtime.create_snapshot()
var corrupt := stable_before_corruption.duplicate(true)
corrupt[&"systems"][&"runtime"][&"authority"][&"current_minute"] = 359
_expect(
not _runtime.restore_snapshot(corrupt),
"restore rejects an Authority/SimulationKernel minute mismatch"
)
_expect(
_runtime.create_snapshot() == stable_before_corruption,
"rejected Authority restore leaves the live Runtime snapshot unchanged"
)
_expect(
_runtime.authority_service.validate_invariants()[&"ok"],
"integrated incident, evidence and inspection selection preserve Authority invariants"
)
func _test_resolution_replay_unreachable_exit_and_long_horizon() -> void:
_reset_runtime(5_502)
_expect(_submit(
&"report_authority_incident",
{
&"incident_id": &"incident.runtime.fine",
&"incident_type": &"contraband",
&"detector_id": &"detector.audit",
&"options": {&"severity": 80, &"heat_delta": 20},
},
&"s5.fine.incident"
).is_success(), "fine fixture Incident registers")
_expect(_submit(
&"register_authority_evidence",
{
&"evidence_id": &"evidence.runtime.fine",
&"source": &"physical_record",
&"severity": 60,
&"options": {
&"incident_id": &"incident.runtime.fine",
&"facility_id": &"facility.illegal",
&"location_cell": Vector3i(2, 0, WorldService.LAYER_FRONT),
},
},
&"s5.fine.evidence"
).is_success(), "fine fixture Evidence registers")
_expect(_submit(
&"schedule_authority_inspection",
{&"options": {
&"inspection_id": &"inspection.runtime.fine",
&"inspector_id": &"inspector.runtime.fine",
&"entrance_cell": Vector3i(0, 0, WorldService.LAYER_FRONT),
}},
&"s5.fine.schedule"
).is_success(), "inspection schedules through Runtime Command")
_expect(_submit(
&"start_authority_inspection",
{&"inspection_id": &"inspection.runtime.fine"},
&"s5.fine.start"
).is_success(), "inspection starts through Runtime Command")
_expect(_submit(
&"arrive_authority_inspection",
{&"inspection_id": &"inspection.runtime.fine"},
&"s5.fine.arrive"
).is_success(), "inspector checks in through Runtime Command")
_expect(_submit(
&"plan_authority_inspection",
{
&"inspection_id": &"inspection.runtime.fine",
&"candidates": [{
&"waypoint_id": &"waypoint.illegal.room",
&"cell": Vector3i(2, 0, WorldService.LAYER_FRONT),
&"suspect_score": 100,
&"evidence_potential": 100,
&"travel_minutes": 2,
&"inspection_minutes": 3,
}],
&"time_budget_minutes": 30,
},
&"s5.fine.plan"
).is_success(), "deterministic inspection patrol is planned")
var moved := _submit(
&"advance_authority_inspection",
{&"inspection_id": &"inspection.runtime.fine", &"context": {}},
&"s5.fine.advance"
)
_expect(
moved.is_success() and moved.details.get(&"reachable", false),
"PathService callback advances the inspector to one reachable waypoint"
)
var mid_inspection: Dictionary = _runtime.create_snapshot()
var mid_digest := SimulationDigest.compute(mid_inspection)
var first_final := _run_resolution_timeline()
_expect(first_final.get(&"ok", false), "inspection resolution timeline completes")
var first_final_snapshot: Dictionary = _runtime.create_snapshot()
var first_final_digest := SimulationDigest.compute(first_final_snapshot)
_expect(_runtime.restore_snapshot(mid_inspection), "mid-inspection Runtime snapshot restores")
_expect(
SimulationDigest.compute(_runtime.create_snapshot()) == mid_digest,
"mid-inspection restore reproduces its exact digest"
)
var replay_final := _run_resolution_timeline()
_expect(replay_final.get(&"ok", false), "restored inspection timeline completes again")
_expect(
SimulationDigest.compute(_runtime.create_snapshot()) == first_final_digest
and _runtime.create_snapshot() == first_final_snapshot,
"mid-inspection replay reproduces Authority, ledger and Domain Events exactly"
)
_expect(_run_unreachable_inspection(), "unreachable patrol target follows the safe exit path")
_expect(
_runtime.authority_service.validate_invariants()[&"ok"]
and _runtime.economy_service.validate_ledger()[&"ok"],
"resolved and unreachable inspections preserve Authority and ledger invariants"
)
var horizon_snapshot: Dictionary = _runtime.create_snapshot()
_clock.advance_minutes(1_440)
var first_horizon_digest: String = _runtime.latest_digest
var first_horizon_snapshot: Dictionary = _runtime.create_snapshot()
_expect(
_runtime.authority_service.current_minute == _clock.absolute_minute,
"Authority stays aligned through a 24-hour simulation horizon"
)
_expect(_runtime.restore_snapshot(horizon_snapshot), "24-hour Authority replay origin restores")
_clock.advance_minutes(1_440)
_expect(
_runtime.latest_digest == first_horizon_digest
and _runtime.create_snapshot() == first_horizon_snapshot,
"24-hour Runtime replay reproduces daily Heat and inspection evaluation exactly"
)
_expect(
_runtime.authority_service.validate_invariants()[&"ok"]
and _runtime.economy_service.validate_ledger()[&"ok"],
"long replay ends with valid Authority and Economy aggregates"
)
func _run_resolution_timeline() -> Dictionary:
var inspected := _submit(
&"inspect_authority_waypoint",
{
&"inspection_id": &"inspection.runtime.fine",
&"evidence_ids": [&"evidence.runtime.fine"],
&"context": {&"illegal_confirmed": true},
},
&"s5.fine.inspect"
)
var reviewed := _submit(
&"advance_authority_inspection",
{&"inspection_id": &"inspection.runtime.fine", &"context": {}},
&"s5.fine.review"
)
var authority_before_failure: Dictionary = _runtime.authority_service.create_snapshot()
var economy_before_failure: Dictionary = _runtime.economy_service.create_snapshot()
var rejected := _submit(
&"resolve_authority_inspection",
{
&"inspection_id": &"inspection.runtime.fine",
&"resolution_id": &"resolution.runtime.unfunded",
&"options": {&"fine_per_point": 100},
},
&"s5.fine.resolve.unfunded"
)
var rejection_atomic: bool = not rejected.is_success() \
and _runtime.authority_service.create_snapshot() == authority_before_failure \
and _runtime.economy_service.create_snapshot() == economy_before_failure
var resolved := _submit(
&"resolve_authority_inspection",
{
&"inspection_id": &"inspection.runtime.fine",
&"resolution_id": &"resolution.runtime.once",
&"options": {&"fine_per_point": 10},
},
&"s5.fine.resolve.once"
)
var duplicate := _submit(
&"resolve_authority_inspection",
{
&"inspection_id": &"inspection.runtime.fine",
&"resolution_id": &"resolution.runtime.once",
&"options": {&"fine_per_point": 999},
},
&"s5.fine.resolve.retry"
)
var departed := _submit(
&"depart_authority_inspection",
{&"inspection_id": &"inspection.runtime.fine"},
&"s5.fine.depart"
)
var fine_entries: Array = _runtime.economy_service.get_entries().filter(
func(entry: LedgerEntry) -> bool: return entry.category == &"AUTHORITY_FINE"
)
var incident: AuthorityIncident = _runtime.authority_service.get_incident(&"incident.runtime.fine")
var inspection: AuthorityInspection = _runtime.authority_service.get_inspection(&"inspection.runtime.fine")
_expect(inspected.is_success(), "reached waypoint discovers only the supplied Evidence IDs")
_expect(
reviewed.is_success()
and reviewed.details.get(&"new_state", &"") == &"REVIEWING"
and inspection != null,
"completed patrol enters REVIEWING before resolution"
)
_expect(rejection_atomic, "unfunded Authority fine leaves Authority and Economy snapshots unchanged")
_expect(
resolved.is_success() and resolved.details.get(&"fine_amount", -1) == 600
and resolved.details.get(&"sanction", &"") == &"TEMPORARY_CLOSURE",
"evidence score projects the deterministic fine and sanction"
)
_expect(
duplicate.is_success() and duplicate.is_replay
and duplicate.details.get(&"fine_amount", -1) == 600,
"duplicate resolution_id replays the canonical outcome"
)
_expect(
fine_entries.size() == 1
and _runtime.economy_service.get_balance(AUTHORITY_FINE_EXPENSE) == 600
and _runtime.economy_service.get_balance(PLAYER_CASH) == 4_400
and _state.get_resource(&"gold") == 4_400,
"fine posts exactly once to the ledger and synchronized Gold mirror"
)
_expect(
incident != null and incident.state == AuthorityIncident.State.RESOLVED
and departed.is_success() and inspection.state == AuthorityInspection.State.CLOSED,
"successful resolution closes its Incident and inspection lifecycle"
)
_expect(
_count_domain_events(&"INSPECTION_RESOLVED", &"inspection.runtime.fine") == 1,
"resolution replay emits no duplicate INSPECTION_RESOLVED event"
)
return {
&"ok": inspected.is_success()
and reviewed.is_success()
and rejection_atomic
and resolved.is_success()
and duplicate.is_success()
and departed.is_success(),
}
func _run_unreachable_inspection() -> bool:
var inspection_id := &"inspection.runtime.blocked"
var target := Vector3i(0, 1, WorldService.LAYER_FRONT)
var scheduled := _submit(
&"schedule_authority_inspection",
{&"options": {
&"inspection_id": inspection_id,
&"inspector_id": &"inspector.runtime.blocked",
&"entrance_cell": Vector3i(0, 0, WorldService.LAYER_FRONT),
}},
&"s5.blocked.schedule"
)
var started := _submit(
&"start_authority_inspection",
{&"inspection_id": inspection_id},
&"s5.blocked.start"
)
var arrived := _submit(
&"arrive_authority_inspection",
{&"inspection_id": inspection_id},
&"s5.blocked.arrive"
)
var planned := _submit(
&"plan_authority_inspection",
{
&"inspection_id": inspection_id,
&"candidates": [{
&"waypoint_id": &"waypoint.sealed",
&"cell": target,
&"suspect_score": 90,
&"evidence_potential": 90,
&"travel_minutes": 5,
&"inspection_minutes": 5,
}],
},
&"s5.blocked.plan"
)
var blocked := _submit(
&"advance_authority_inspection",
{&"inspection_id": inspection_id, &"context": {}},
&"s5.blocked.advance"
)
var inspection: AuthorityInspection = _runtime.authority_service.get_inspection(inspection_id)
var safe_departure: bool = blocked.is_success() \
and not bool(blocked.details.get(&"reachable", true)) \
and inspection != null \
and inspection.state == AuthorityInspection.State.DEPARTING \
and inspection.exit_reason == &"PATH_UNREACHABLE" \
and inspection.visited_waypoint_ids.is_empty() \
and inspection.inaccessible_waypoint_ids == [&"waypoint.sealed"] \
and not inspection.result_reason.is_empty() \
and _count_domain_events(&"INSPECTION_PATH_BLOCKED", inspection_id) == 1
var departed := _submit(
&"depart_authority_inspection",
{&"inspection_id": inspection_id},
&"s5.blocked.depart"
)
return scheduled.is_success() \
and started.is_success() \
and arrived.is_success() \
and planned.is_success() \
and safe_departure \
and departed.is_success() \
and inspection.state == AuthorityInspection.State.CLOSED
func _submit(command_type: StringName, payload: Dictionary, idempotency_key: StringName) -> CommandResult:
return _runtime.submit_command(
command_type,
payload,
&"authority_controller",
&"",
80,
-1,
idempotency_key
)
func _evaluation_for_day(day_index: int) -> Dictionary:
var snapshot: Dictionary = _runtime.authority_service.create_snapshot()
for value: Variant in snapshot[&"evaluated_inspection_days"]:
if value is Dictionary and int(value.get(&"day_index", -1)) == day_index:
return (value[&"result"] as Dictionary).duplicate(true)
return {}
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 _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)
@@ -0,0 +1 @@
uid://dxr8o0wmgfy0l
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
uid://ct3xctiqfuurj