extends SceneTree const ProgressionType := preload("res://scripts/progression/progression_service.gd") var _checks := 0 var _failures := 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: _test_rank_bounds_and_evidence() _test_metrics_are_atomic_and_lifetime_monotonic() _test_rank_one_through_seven() _test_external_transaction_and_idempotency() _test_failed_rollback_quarantines_progression() _test_callback_reentry_and_internal_tamper() _test_snapshot_round_trip_and_atomic_rejection() _test_promotion_evidence_and_legacy_migration() _test_input_and_history_bounds() if _failures == 0: print("PASS: ProgressionService (%d checks)" % _checks) else: push_error("FAIL: ProgressionService (%d of %d checks failed)" % [_failures, _checks]) quit(_failures) func _test_rank_bounds_and_evidence() -> void: var service := ProgressionType.new() _expect(service.configure(), "progression starts at Rank 1") _expect(service.current_rank == 1, "configured rank is authoritative") _expect(service.get_rank_bounds(1) == {&"min_x":-12, &"max_x":12, &"min_y":0, &"max_y":6}, "Rank 1 bounds match the system specification") _expect(service.get_rank_bounds(3) == {&"min_x":-24, &"max_x":24, &"min_y":-8, &"max_y":16}, "Rank 3 MVP bounds include eight underground levels") _expect(service.get_rank_bounds(7) == {&"min_x":-60, &"max_x":60, &"min_y":-40, &"max_y":56}, "Rank 7 bounds match the final expansion") _expect(service.get_rank_bounds(8).is_empty(), "unknown rank has no implicit clamped bounds") var report := service.evaluate_rank(2) _expect(not report[&"eligible"] and report[&"reason"] == &"REQUIREMENTS_NOT_MET", "Rank 2 reports unmet requirements") _expect(_requirement_ids(report) == [&"reputation", &"asset_gold"], "Rank 2 evidence order is stable and player-readable") _expect(report[&"requirements"][0][&"current"] == 0 and report[&"requirements"][0][&"required"] == 25, "eligibility evidence includes current and required values") var skip := service.evaluate_rank(3) _expect(not skip[&"eligible"] and skip[&"reason"] == &"TARGET_MUST_BE_NEXT_RANK", "rank skipping is rejected before metric evaluation") func _test_metrics_are_atomic_and_lifetime_monotonic() -> void: var service := ProgressionType.new() service.configure() _expect(service.update_metrics({&"reputation": 25, &"asset_gold": 10_000, &"faith_lifetime": 100}), "valid metric patch commits atomically") var before := service.metrics.duplicate(true) _expect(not service.update_metrics({&"reputation": 101, &"asset_gold": 20_000}), "out-of-range percentage rejects the whole patch") _expect(service.metrics == before, "failed metric patch preserves every prior metric") _expect(not service.update_metrics({&"faith_lifetime": 99}), "lifetime Faith cannot decrease after spending current Faith") _expect(not service.update_metrics({&"guest_lifetime": -1}), "lifetime guest count cannot become negative") _expect(not service.update_metrics({&"unknown": 1}), "unknown progression metric is rejected") _expect(not service.update_metrics({&"asset_gold": "10000"}), "string numbers are not coerced into progression facts") _expect(service.metrics == before, "all rejected patches leave progression facts unchanged") _expect(service.complete_quest(&"important_rank_5"), "valid stable quest ID records once") _expect(service.complete_quest(&"important_rank_5"), "completing the same quest is idempotent") _expect(service.completed_quest_ids.size() == 1, "idempotent quest completion creates no duplicate") _expect(not service.complete_quest(&"Bad Quest"), "invalid quest ID cannot enter persistent progression") func _test_rank_one_through_seven() -> void: var service := ProgressionType.new() service.configure() service.update_metrics({&"reputation":25, &"asset_gold":10_000}) _expect(service.request_rank_up(&"rank_up_2", 2).is_success(), "Rank 2 unlocks at Reputation 25 and assets 10,000G") service.update_metrics({&"faith_lifetime":100, &"guest_lifetime":300}) _expect(service.request_rank_up(&"rank_up_3", 3).is_success(), "Rank 3 unlocks at lifetime Faith 100 and 300 guests") service.update_metrics({ &"reputation":45, &"asset_gold":50_000, &"active_above_ground_facilities":1, &"active_underground_facilities":1, }) _expect(service.request_rank_up(&"rank_up_4", 4).is_success(), "Rank 4 additionally requires active facilities above and below ground") service.update_metrics({&"faith_lifetime":500}) var rank_five_missing := service.evaluate_rank(5) _expect(not rank_five_missing[&"eligible"], "Rank 5 does not unlock from numbers alone") service.complete_quest(&"important_rank_5") _expect(service.request_rank_up(&"rank_up_5", 5).is_success(), "Rank 5 requires its explicit important quest") service.update_metrics({&"underground_influence":65}) var rank_six := service.evaluate_rank(6) _expect(rank_six[&"eligible"], "Rank 6 accepts underground influence as the specified Reputation alternative") _expect(service.request_rank_up(&"rank_up_6", 6).is_success(), "Rank 6 commits through the alternative branch") service.update_metrics({&"faith_lifetime":1500, &"reputation":60}) var rank_seven_missing := service.evaluate_rank(7) _expect(not rank_seven_missing[&"eligible"], "Rank 7 still requires the main chapter flag") service.complete_quest(&"main_chapter_complete") _expect(service.request_rank_up(&"rank_up_7", 7).is_success(), "Rank 7 requires Faith, both progress axes, dual operation and chapter completion") _expect(service.current_rank == 7, "rank progression is monotonic through the final rank") _expect(not service.request_rank_up(&"rank_up_8", 8).is_success(), "Rank 8 is rejected") func _test_external_transaction_and_idempotency() -> void: var service := _eligible_rank_two_service() var external := {&"rank":1, &"bounds":service.get_rank_bounds(1), &"apply_calls":0, &"rollback_calls":0} var capture := func(_current: int, _target: int) -> Dictionary: return {&"ok":true, &"snapshot":{&"rank":external[&"rank"], &"bounds":external[&"bounds"].duplicate(true)}} var apply := func(target: int, bounds: Dictionary) -> Dictionary: external[&"apply_calls"] += 1 external[&"rank"] = target external[&"bounds"] = bounds.duplicate(true) return {&"ok":true} var rollback := func(snapshot: Dictionary) -> Dictionary: external[&"rollback_calls"] += 1 external[&"rank"] = snapshot[&"rank"] external[&"bounds"] = snapshot[&"bounds"].duplicate(true) return {&"ok":true} var result := service.request_rank_up(&"atomic_rank_2", 2, capture, apply, rollback) _expect(result.is_success(), "eligible rank-up commits through capture/apply boundary") _expect(service.current_rank == 2 and external[&"rank"] == 2, "internal rank and external world commit together") _expect(external[&"bounds"] == service.get_rank_bounds(2), "external world receives exact target bounds") var replay := service.request_rank_up(&"atomic_rank_2", 2) _expect(replay.is_success() and replay.is_replay, "same request ID and target returns its recorded success") _expect(external[&"apply_calls"] == 1, "idempotent replay does not apply external expansion twice") var conflict := service.request_rank_up(&"atomic_rank_2", 3) _expect(not conflict.is_success() and conflict.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY, "same request ID with another target conflicts") var partial := service.request_rank_up(&"partial_callbacks", 3, capture, Callable(), rollback) _expect(not partial.is_success() and partial.code == CommandResult.Code.INVALID_PAYLOAD, "partial callback set is rejected before mutation") var strict_callbacks := _eligible_rank_two_service() var bad_capture := func(_current: int, _target: int) -> Dictionary: return {&"ok":"true", &"snapshot":{}} var bad_capture_result := strict_callbacks.request_rank_up(&"bad_capture_type", 2, bad_capture, apply, rollback) _expect(not bad_capture_result.is_success() and external[&"apply_calls"] == 1, "truthy string capture status is rejected before external apply") var string_apply := func(_target: int, _bounds: Dictionary) -> Dictionary: return {&"ok":"true"} var strict_rollback := func(_snapshot: Dictionary) -> Dictionary: return {&"ok":true} var bad_apply_result := strict_callbacks.request_rank_up(&"bad_apply_type", 2, capture, string_apply, strict_rollback) _expect(not bad_apply_result.is_success() and strict_callbacks.current_rank == 1, "truthy string apply status is treated as failure and rolled back") func _test_failed_rollback_quarantines_progression() -> void: var rolled_back := _eligible_rank_two_service() var external_rank := 1 var capture := func(_current: int, _target: int) -> Dictionary: return {&"ok":true, &"snapshot":external_rank} var failed_apply := func(target: int, _bounds: Dictionary) -> Dictionary: external_rank = target return {&"ok":false} var successful_rollback := func(snapshot: int) -> Dictionary: external_rank = snapshot return {&"ok":true} var rolled_back_result := rolled_back.request_rank_up( &"failed_but_rolled_back", 2, capture, failed_apply, successful_rollback ) _expect(not rolled_back_result.is_success(), "failed external expansion rejects rank-up") _expect(rolled_back.current_rank == 1 and external_rank == 1, "successful rollback restores both sides to Rank 1") _expect(not rolled_back.quarantined, "verified rollback keeps progression available for a later request") var quarantined := _eligible_rank_two_service() external_rank = 1 var failed_rollback := func(_snapshot: int) -> Dictionary: return {&"ok":false} var quarantined_result := quarantined.request_rank_up( &"rollback_failure", 2, capture, failed_apply, failed_rollback ) _expect(not quarantined_result.is_success() and quarantined_result.code == CommandResult.Code.TRANSACTION_CLOSED, "rollback failure closes the progression boundary") _expect(quarantined.quarantined and quarantined.quarantine_reason == &"RANK_ROLLBACK_FAILED", "rollback failure records a durable quarantine reason") _expect(not quarantined.update_metrics({&"reputation":30}), "quarantine blocks later metric mutation") _expect(not quarantined.request_rank_up(&"retry_after_quarantine", 2).is_success(), "quarantine blocks retry and prevents double expansion") func _test_callback_reentry_and_internal_tamper() -> void: var service := _eligible_rank_two_service() var external_rank := 1 var observations := {&"metric_update":true, &"nested":null} var capture := func(_current: int, _target: int) -> Dictionary: return {&"ok":true, &"snapshot":external_rank} var reentrant_apply := func(target: int, _bounds: Dictionary) -> Dictionary: observations[&"metric_update"] = service.update_metrics({&"reputation":99}) observations[&"nested"] = service.request_rank_up(&"nested_rank_request", 2) external_rank = target return {&"ok":true} var rollback := func(snapshot: int) -> Dictionary: external_rank = snapshot return {&"ok":true} var result := service.request_rank_up(&"guarded_rank_request", 2, capture, reentrant_apply, rollback) _expect(result.is_success(), "blocked reentry cannot prevent an otherwise valid outer promotion") _expect(not bool(observations[&"metric_update"]), "rank callback cannot mutate eligibility metrics reentrantly") var nested := observations[&"nested"] as CommandResult _expect(nested != null and not nested.is_success() and nested.code == CommandResult.Code.TRANSACTION_CLOSED, "nested promotion is rejected while the outer transaction is active") _expect(service.metrics[&"reputation"] == 25 and service.current_rank == 2, "blocked callback reentry leaves one exact promotion and original evidence") _expect(service.create_snapshot()[&"processed_requests"].size() == 1, "reentry guard creates no hidden second idempotency record") var tampered := _eligible_rank_two_service() external_rank = 1 var tampering_apply := func(target: int, _bounds: Dictionary) -> Dictionary: external_rank = target tampered.metrics[&"reputation"] = 99 return {&"ok":true} result = tampered.request_rank_up(&"tampered_apply", 2, capture, tampering_apply, rollback) _expect(not result.is_success() and result.code == CommandResult.Code.TRANSACTION_APPLY_FAILED, "direct internal mutation by an apply callback invalidates the transaction") _expect(tampered.current_rank == 1 and tampered.metrics[&"reputation"] == 25 and external_rank == 1, "tampered apply rolls both internal and external state back exactly") _expect(not tampered.quarantined, "verified rollback after callback tamper keeps later promotion available") var rollback_tampered := _eligible_rank_two_service() external_rank = 1 var failed_apply := func(target: int, _bounds: Dictionary) -> Dictionary: external_rank = target return {&"ok":false} var mutating_rollback := func(snapshot: int) -> Dictionary: external_rank = snapshot rollback_tampered.current_rank = 7 return {&"ok":true} result = rollback_tampered.request_rank_up(&"tampered_rollback", 2, capture, failed_apply, mutating_rollback) _expect(not result.is_success() and rollback_tampered.current_rank == 1, "internal state is restored even when a successful rollback callback tampers with it") _expect(external_rank == 1 and not rollback_tampered.quarantined, "successful external rollback remains authoritative after internal tamper repair") var valid_capture_tampered := _eligible_rank_two_service() var callback_counts := {&"apply":0, &"rollback":0} var mutating_capture := func(_current: int, _target: int) -> Dictionary: valid_capture_tampered.metrics[&"reputation"] = 99 return {&"ok":true, &"snapshot":1} var counted_apply := func(_target: int, _bounds: Dictionary) -> Dictionary: callback_counts[&"apply"] += 1 return {&"ok":true} var counted_rollback := func(_snapshot: int) -> Dictionary: callback_counts[&"rollback"] += 1 return {&"ok":true} result = valid_capture_tampered.request_rank_up(&"valid_capture_tamper", 2, mutating_capture, counted_apply, counted_rollback) _expect(not result.is_success() and callback_counts[&"apply"] == 0, "valid-looking capture tamper is detected before external apply") _expect(callback_counts[&"rollback"] == 1 and valid_capture_tampered.metrics[&"reputation"] == 25, "capture tamper invokes one rollback and restores internal eligibility") var capture_tampered := _eligible_rank_two_service() var bad_capture := func(_current: int, _target: int) -> Dictionary: capture_tampered.current_rank = 7 return {&"ok":false} result = capture_tampered.request_rank_up(&"tampered_capture", 2, bad_capture, reentrant_apply, rollback) _expect(not result.is_success() and result.code == CommandResult.Code.TRANSACTION_CLOSED, "invalid capture that mutates progression enters quarantine") _expect(capture_tampered.current_rank == 1 and capture_tampered.quarantine_reason == &"RANK_CALLBACK_TAMPERED", "capture tamper restores the last valid rank and records the uncertainty reason") func _test_snapshot_round_trip_and_atomic_rejection() -> void: var source := _eligible_rank_two_service() source.complete_quest(&"important_rank_5") source.request_rank_up(&"snapshot_rank_2", 2) var snapshot := source.create_snapshot() var restored := ProgressionType.new() _expect(restored.restore_snapshot(snapshot), "valid progression snapshot restores") _expect(restored.create_snapshot() == snapshot, "progression snapshot round trip is exact") var json_snapshot: Variant = JSON.parse_string(JSON.stringify(snapshot)) var json_restored := ProgressionType.new() var json_ok := json_snapshot is Dictionary and json_restored.restore_snapshot(json_snapshot) _expect(json_ok, "JSON-decoded string-key snapshot restores through the same strict boundary (%s)" % json_restored.last_error) _expect(json_restored.create_snapshot() == snapshot, "JSON transport preserves all promotion evidence exactly") var before := restored.create_snapshot() var missing_metric := snapshot.duplicate(true) missing_metric[&"metrics"].erase("asset_gold") _expect(not restored.restore_snapshot(missing_metric), "snapshot missing one progression metric is rejected") _expect(restored.create_snapshot() == before, "missing-metric rejection is atomic") var bad_fingerprint := snapshot.duplicate(true) bad_fingerprint[&"processed_requests"][0][&"fingerprint"] = "rank:7" _expect(not restored.restore_snapshot(bad_fingerprint), "request fingerprint inconsistent with recorded rank is rejected") _expect(restored.create_snapshot() == before, "fingerprint rejection preserves live progression") var bad_bounds := snapshot.duplicate(true) bad_bounds[&"processed_requests"][0][&"details"][&"bounds"][&"max_x"] = 999 _expect(not restored.restore_snapshot(bad_bounds), "derived rank bounds cannot be tampered in a snapshot") _expect(restored.create_snapshot() == before, "derived-bounds rejection is atomic") var bad_quarantine := snapshot.duplicate(true) bad_quarantine[&"quarantined"] = true bad_quarantine[&"quarantine_reason"] = "" _expect(not restored.restore_snapshot(bad_quarantine), "quarantine boolean requires a non-empty reason") _expect(restored.create_snapshot() == before, "quarantine mismatch rejection is atomic") var string_rank := snapshot.duplicate(true) string_rank[&"current_rank"] = "2" _expect(not restored.restore_snapshot(string_rank), "string rank is not coerced during restore") _expect(restored.create_snapshot() == before, "type rejection preserves live progression") var string_version := snapshot.duplicate(true) string_version[&"version"] = "1" _expect(not restored.restore_snapshot(string_version), "string schema version is not coerced during restore") _expect(restored.create_snapshot() == before, "schema type rejection is atomic") var unknown_root := snapshot.duplicate(true) unknown_root[&"unexpected"] = true _expect(not restored.restore_snapshot(unknown_root), "unknown root snapshot field is rejected") _expect(restored.create_snapshot() == before, "unknown-field rejection preserves live progression") var duplicate_rank_history := snapshot.duplicate(true) var duplicate_record: Dictionary = duplicate_rank_history[&"processed_requests"][0].duplicate(true) duplicate_record[&"request_id"] = "snapshot_rank_2_b" duplicate_record[&"details"][&"request_id"] = &"snapshot_rank_2_b" duplicate_rank_history[&"processed_requests"].append(duplicate_record) _expect(not restored.restore_snapshot(duplicate_rank_history), "two successful request IDs cannot claim the same one-time rank transition") _expect(restored.create_snapshot() == before, "duplicate-transition rejection is atomic") func _test_promotion_evidence_and_legacy_migration() -> void: var source := _eligible_rank_two_service() source.request_rank_up(&"evidence_rank_2", 2) var snapshot := source.create_snapshot() var evidence: Dictionary = snapshot[&"processed_requests"][0][&"details"][&"eligibility"] _expect(snapshot[&"version"] == 2, "promotion evidence is stored in Progression Snapshot 2") _expect(evidence[&"eligible"] and evidence[&"current_rank"] == 1 and evidence[&"target_rank"] == 2, "saved eligibility header binds the exact one-step transition") _expect(_requirement_ids(evidence) == [&"reputation", &"asset_gold"], "saved eligibility retains deterministic condition order") var restored := ProgressionType.new() _expect(restored.configure(), "evidence rejection target starts from a known valid state") var before := restored.create_snapshot() var below_threshold := snapshot.duplicate(true) below_threshold[&"processed_requests"][0][&"details"][&"eligibility"][&"requirements"][0][&"current"] = 24 _expect(not restored.restore_snapshot(below_threshold), "promotion history below its threshold is rejected") _expect(restored.create_snapshot() == before, "invalid promotion evidence rejection is atomic") var changed_requirement := snapshot.duplicate(true) changed_requirement[&"processed_requests"][0][&"details"][&"eligibility"][&"requirements"][0][&"required"] = 24 _expect(not restored.restore_snapshot(changed_requirement), "saved threshold definitions cannot be weakened") _expect(restored.create_snapshot() == before, "threshold tamper preserves the live progression") var missing_history := snapshot.duplicate(true) missing_history[&"processed_requests"] = [] _expect(not restored.restore_snapshot(missing_history), "Rank 2 cannot be restored without its Rank 2 promotion record") _expect(restored.create_snapshot() == before, "incomplete-history rejection is atomic") var rank_three := _rank_three_service() var lifetime_tamper := rank_three.create_snapshot() lifetime_tamper[&"metrics"]["faith_lifetime"] = 99 _expect(not restored.restore_snapshot(lifetime_tamper), "current lifetime Faith cannot fall below its saved Rank 3 evidence") _expect(restored.create_snapshot() == before, "lifetime-history mismatch preserves the live progression") var rank_five := _rank_five_service() var quest_tamper := rank_five.create_snapshot() quest_tamper[&"completed_quest_ids"].erase("important_rank_5") _expect(not restored.restore_snapshot(quest_tamper), "a monotonic quest used for Rank 5 cannot disappear from the snapshot") _expect(restored.create_snapshot() == before, "quest-history mismatch is rejected atomically") var legacy_source := ProgressionType.new() legacy_source.configure(1, {&"reputation":10}, [&"tutorial_complete"]) var legacy_rank_one := legacy_source.create_snapshot() legacy_rank_one[&"version"] = 1 _expect(restored.restore_snapshot(legacy_rank_one), "unambiguous legacy Rank 1 snapshot migrates safely") _expect(restored.create_snapshot()[&"version"] == 2 and restored.current_rank == 1, "legacy restore is immediately re-emitted as Snapshot 2") var legacy_progressed := snapshot.duplicate(true) legacy_progressed[&"version"] = 1 legacy_progressed[&"processed_requests"][0][&"details"].erase(&"eligibility") var migrated_before := restored.create_snapshot() _expect(not restored.restore_snapshot(legacy_progressed), "legacy progressed snapshot without condition evidence is rejected as ambiguous") _expect(restored.create_snapshot() == migrated_before, "ambiguous legacy rejection preserves the migrated Rank 1 state") func _test_input_and_history_bounds() -> void: var service := ProgressionType.new() _expect(not service.configure(2), "configuration cannot bypass the one-rank-at-a-time promotion boundary") _expect(service.configure(), "valid Rank 1 configuration remains available after rejection") _expect(not service.update_metrics({&"asset_gold":ProgressionType.MAX_SAFE_INTEGER + 1}), "metrics above the JSON-safe integer range are rejected") _expect(not service.update_metrics({&"asset_gold":INF}), "non-finite metrics are rejected") var oversized_quests: Array = [] for index in ProgressionType.MAX_COMPLETED_QUESTS + 1: oversized_quests.append("quest_%03d" % index) _expect(not service.configure(1, {}, oversized_quests), "oversized initial quest input is rejected before per-entry work") service = _eligible_rank_two_service() var invalid_id := service.request_rank_up(&"rank request with spaces", 2) _expect(not invalid_id.is_success() and invalid_id.code == CommandResult.Code.INVALID_PAYLOAD, "idempotency keys must use stable lowercase IDs") _expect(service.current_rank == 1, "invalid idempotency key cannot mutate rank") var impossible := service.create_snapshot() impossible[&"current_rank"] = 7 var before := service.create_snapshot() _expect(not service.restore_snapshot(impossible), "fabricated high rank without contiguous promotion history is rejected") _expect(service.create_snapshot() == before, "fabricated-rank rejection is atomic") func _eligible_rank_two_service() -> ProgressionService: var service := ProgressionType.new() service.configure(1, {&"reputation":25, &"asset_gold":10_000}) return service func _rank_three_service() -> ProgressionService: var service := _eligible_rank_two_service() service.request_rank_up(&"history_rank_2", 2) service.update_metrics({&"faith_lifetime":100, &"guest_lifetime":300}) service.request_rank_up(&"history_rank_3", 3) return service func _rank_five_service() -> ProgressionService: var service := _rank_three_service() service.update_metrics({ &"reputation":45, &"asset_gold":50_000, &"active_above_ground_facilities":1, &"active_underground_facilities":1, }) service.request_rank_up(&"history_rank_4", 4) service.update_metrics({&"faith_lifetime":500}) service.complete_quest(&"important_rank_5") service.request_rank_up(&"history_rank_5", 5) return service func _requirement_ids(report: Dictionary) -> Array[StringName]: var result: Array[StringName] = [] for requirement: Dictionary in report[&"requirements"]: result.append(StringName(requirement[&"id"])) return result func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" ✓ %s" % label) return _failures += 1 push_error(" ✗ %s" % label)