class_name AuthorityService extends RefCounted ## Deterministic Authority aggregate. World visibility, path finding and ## financial/sanction application are explicit callback boundaries so this ## service never scans the map or mutates another domain directly. signal heat_changed(old_value: int, new_value: int, source_id: StringName) signal incident_created(incident: AuthorityIncident) signal incident_detector_added(incident_id: StringName, detector_id: StringName) signal evidence_registered(evidence: AuthorityEvidence) signal evidence_discovered(evidence: AuthorityEvidence, method: StringName) signal inspection_scheduled(inspection: AuthorityInspection) signal inspection_state_changed(inspection_id: StringName, old_state: int, new_state: int, reason: StringName) signal resolution_applied(inspection_id: StringName, resolution: Dictionary) 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 CommandResultType = preload("res://scripts/core/command_result.gd") const SNAPSHOT_VERSION := 1 const INSPECTION_EVALUATION_MINUTE := 360 const DEFAULT_HEAT_DECAY := 5 const DEFAULT_ACTIVE_CASE_FLOOR := 20 var current_minute := 0 var last_error := "" var _state: HeatState = HeatStateType.new() var _incidents: Dictionary = {} var _evidence: Dictionary = {} var _inspections: Dictionary = {} var _scored_incident_points: Dictionary = {} var _applied_resolutions: Dictionary = {} var _evaluated_inspection_days: Dictionary = {} var _next_inspection_sequence := 1 var _next_inspector_sequence := 1 var _line_of_sight := Callable() var _path_query := Callable() var _resolution_applier := Callable() var _inspection_roll := Callable() func configure_callbacks( line_of_sight: Callable = Callable(), path_query: Callable = Callable(), resolution_applier: Callable = Callable(), inspection_roll: Callable = Callable() ) -> AuthorityService: _line_of_sight = line_of_sight _path_query = path_query _resolution_applier = resolution_applier _inspection_roll = inspection_roll return self func get_authority_state() -> HeatState: return _state func get_incident(incident_id: StringName) -> AuthorityIncident: return _incidents.get(incident_id) as AuthorityIncident func get_incidents() -> Array[AuthorityIncident]: var result: Array[AuthorityIncident] = [] for incident_id: StringName in _sorted_keys(_incidents): result.append(_incidents[incident_id]) return result func get_evidence(evidence_id: StringName) -> AuthorityEvidence: return _evidence.get(evidence_id) as AuthorityEvidence func get_all_evidence() -> Array[AuthorityEvidence]: var result: Array[AuthorityEvidence] = [] for evidence_id: StringName in _sorted_keys(_evidence): result.append(_evidence[evidence_id]) return result func get_inspection(inspection_id: StringName) -> AuthorityInspection: return _inspections.get(inspection_id) as AuthorityInspection func get_inspections() -> Array[AuthorityInspection]: var result: Array[AuthorityInspection] = [] for inspection_id: StringName in _sorted_keys(_inspections): result.append(_inspections[inspection_id]) return result func get_resolution(resolution_id: StringName) -> Dictionary: if not _applied_resolutions.has(resolution_id): return {} return (_applied_resolutions[resolution_id] as Dictionary).duplicate(true) func apply_heat_delta( delta: int, source_id: StringName, absolute_minute: int, reason: StringName = &"" ) -> CommandResult: var details := _state.apply_heat_delta(delta, source_id, absolute_minute, reason) if not bool(details.get(&"ok", false)): return _invalid("Heat delta requires a non-negative minute.", details) if int(details[&"old_value"]) != int(details[&"new_value"]): heat_changed.emit(int(details[&"old_value"]), int(details[&"new_value"]), source_id) last_error = "" return _success(details) func set_suspicion(value: int) -> CommandResult: var old_value := _state.suspicion _state.set_suspicion(value) last_error = "" return _success({&"old_value": old_value, &"new_value": _state.suspicion}) func create_incident( incident_id: StringName, incident_type: StringName, absolute_minute: int, detector_id: StringName, options: Dictionary = {} ) -> CommandResult: var incident := IncidentType.new().configure( incident_id, incident_type, absolute_minute, options ) return report_incident(incident, detector_id) ## incident_id is the authoritative deduplication key. A second detector is ## retained as audit metadata, but Heat and evidence are never added again. func report_incident(value: Variant, detector_id: StringName) -> CommandResult: if value == null or not value is AuthorityIncident: return _invalid("AuthorityIncident is missing or invalid.") var incoming: AuthorityIncident = value if not incoming.is_valid() or detector_id.is_empty(): return _invalid("AuthorityIncident fails schema validation or has no detector.") if incoming.state != IncidentType.State.OPEN: return _invalid("New Incident reports must begin in OPEN state.") if not incoming.evidence_ids.is_empty(): return _invalid("New Incident reports cannot arrive with pre-attached Evidence.") if _incidents.has(incoming.incident_id): var existing := get_incident(incoming.incident_id) var detector_added := existing.add_detector(detector_id) if detector_added: incident_detector_added.emit(existing.incident_id, detector_id) var replay := _success({ &"incident_id": existing.incident_id, &"detector_added": detector_added, &"heat_applied": 0, &"canonical_severity": existing.severity, }) replay.is_replay = true last_error = "" return replay incoming.add_detector(detector_id) _incidents[incoming.incident_id] = incoming if incoming.state == IncidentType.State.OPEN: _state.add_active_case(incoming.incident_id) var heat_result := _state.apply_heat_delta( incoming.heat_delta, incoming.incident_id, incoming.created_at, &"INCIDENT" ) if int(heat_result[&"old_value"]) != int(heat_result[&"new_value"]): heat_changed.emit( int(heat_result[&"old_value"]), int(heat_result[&"new_value"]), incoming.incident_id ) incident_created.emit(incoming) last_error = "" return _success({ &"incident_id": incoming.incident_id, &"detector_added": true, &"heat_applied": int(heat_result[&"applied_delta"]), }) func dismiss_incident( incident_id: StringName, reason: StringName = &"DISMISSED" ) -> CommandResult: var incident := get_incident(incident_id) if incident == null: return _not_found("AuthorityIncident", incident_id) if not incident.dismiss(reason): return _invalid("Only an open Incident can be dismissed.") _state.remove_active_case(incident_id) last_error = "" return _success({&"incident_id": incident_id, &"state": &"DISMISSED"}) func register_evidence(value: Variant) -> CommandResult: if value == null or not value is AuthorityEvidence: return _invalid("AuthorityEvidence is missing or invalid.") var evidence: AuthorityEvidence = value if not evidence.is_valid(): return _invalid("AuthorityEvidence fails schema validation.") if evidence.is_discovered(): return _invalid("Live Evidence must enter as latent and use the discovery boundary.") if _evidence.has(evidence.evidence_id): return _duplicate("AuthorityEvidence", evidence.evidence_id) if not evidence.incident_id.is_empty() and not _incidents.has(evidence.incident_id): return _invalid("AuthorityEvidence references an unknown Incident.", { &"incident_id": evidence.incident_id, }) _evidence[evidence.evidence_id] = evidence if not evidence.incident_id.is_empty(): get_incident(evidence.incident_id).add_evidence(evidence.evidence_id) evidence_registered.emit(evidence) if evidence.is_discovered(): _recompute_authority_evidence_points(current_minute) last_error = "" return _success({&"evidence_id": evidence.evidence_id}) func create_evidence( evidence_id: StringName, source: StringName, severity: int, options: Dictionary = {} ) -> CommandResult: return register_evidence(EvidenceType.new().configure(evidence_id, source, severity, options)) ## No visibility callback means no line-of-sight proof. Reaching the cell, ## trusted records/testimony and an already-known illegal facility are the only ## callback-free discovery bases. func attempt_discover_evidence( evidence_id: StringName, inspector_id: StringName, inspector_cell: Vector3i, absolute_minute: int, context: Dictionary = {} ) -> CommandResult: var evidence := get_evidence(evidence_id) if evidence == null: return _not_found("AuthorityEvidence", evidence_id) if inspector_id.is_empty() or absolute_minute < 0: return _invalid("Evidence discovery requires an inspector and non-negative minute.") if evidence.is_expired(absolute_minute): return _invalid("Evidence has expired.", {&"reason": &"EVIDENCE_EXPIRED"}) if evidence.is_discovered(): var replay := _success({ &"evidence_id": evidence_id, &"discovery_method": EvidenceType.DiscoveryMethod.keys()[evidence.discovery_method], &"evidence_points": _state.evidence_points, }) replay.is_replay = true last_error = "" return replay var method := EvidenceType.DiscoveryMethod.LATENT var reason: StringName = &"NO_VISIBILITY_PROOF" if inspector_cell == evidence.location_cell: method = EvidenceType.DiscoveryMethod.REACHED elif bool(_value(context, &"trusted_source", false)): method = EvidenceType.DiscoveryMethod.TRUSTED_SOURCE elif not evidence.facility_id.is_empty() \ and _state.known_illegal_facility_ids.has(evidence.facility_id): method = EvidenceType.DiscoveryMethod.KNOWN_ILLEGAL elif inspector_cell.z != evidence.location_cell.z: reason = &"DIFFERENT_LAYER" elif not _line_of_sight.is_valid(): reason = &"LINE_OF_SIGHT_UNAVAILABLE" else: var visibility_context := context.duplicate(true) visibility_context[&"evidence_id"] = evidence.evidence_id visibility_context[&"facility_id"] = evidence.facility_id var visible := bool(_line_of_sight.call( inspector_cell, evidence.location_cell, visibility_context )) if visible: method = EvidenceType.DiscoveryMethod.LINE_OF_SIGHT else: reason = &"LINE_OF_SIGHT_BLOCKED" if method == EvidenceType.DiscoveryMethod.LATENT: return _invalid("Evidence is not discoverable from the inspector's position.", { &"reason": reason, &"evidence_id": evidence_id, }) if not evidence.mark_discovered(inspector_id, absolute_minute, method): return _invalid("Evidence discovery transition failed.") if bool(_value(context, &"illegal_confirmed", false)) and not evidence.facility_id.is_empty(): _state.add_known_illegal_facility(evidence.facility_id) _recompute_authority_evidence_points(absolute_minute) evidence_discovered.emit(evidence, StringName(EvidenceType.DiscoveryMethod.keys()[method])) last_error = "" return _success({ &"evidence_id": evidence_id, &"discovery_method": StringName(EvidenceType.DiscoveryMethod.keys()[method]), &"evidence_points": _state.evidence_points, }) func advance_time( absolute_minute: int, daily_heat_decay: int = DEFAULT_HEAT_DECAY, active_case_floor: int = DEFAULT_ACTIVE_CASE_FLOOR ) -> CommandResult: if absolute_minute < current_minute or daily_heat_decay < 0: return _invalid("Authority time cannot move backwards and decay cannot be negative.") var old_day := current_minute / 1_440 var new_day := absolute_minute / 1_440 var decay_records: Array[Dictionary] = [] for day_index: int in range(old_day + 1, new_day + 1): var old_heat := _state.heat var decay := _state.apply_daily_decay(day_index, daily_heat_decay, active_case_floor) decay_records.append(decay) if old_heat != _state.heat: heat_changed.emit(old_heat, _state.heat, &"daily_decay") current_minute = absolute_minute _recompute_authority_evidence_points(absolute_minute) last_error = "" return _success({ &"current_minute": current_minute, &"decays": decay_records, &"evidence_points": _state.evidence_points, }) func evaluate_inspection(absolute_minute: int, terms: Dictionary = {}) -> CommandResult: if absolute_minute < 0 or absolute_minute % 1_440 != INSPECTION_EVALUATION_MINUTE: return _invalid("Inspection selection is evaluated only at 06:00.") var day_index := absolute_minute / 1_440 if _evaluated_inspection_days.has(day_index): var replay := _success((_evaluated_inspection_days[day_index] as Dictionary).duplicate(true)) replay.is_replay = true last_error = "" return replay if absolute_minute < _state.next_inspection_window: return _invalid("Inspection evaluation is outside the next Window.", { &"next_inspection_window": _state.next_inspection_window, }) var base_chance := int(_value(terms, &"base_chance", 5)) var heat_weight := int(_value(terms, &"heat_weight", 1)) var suspicion_weight := int(_value(terms, &"suspicion_weight", 1)) var overdue_bonus := int(_value( terms, &"overdue_case_bonus", 20 if not _state.active_case_ids.is_empty() else 0 )) var reputation_protection := int(_value(terms, &"reputation_protection", 0)) var chance := clampi( base_chance + _state.heat * heat_weight + _state.suspicion * suspicion_weight + overdue_bonus - reputation_protection, 0, 95 ) var roll := -1 if _has_key(terms, &"roll"): roll = clampi(int(_value(terms, &"roll", 99)), 0, 99) elif _inspection_roll.is_valid(): roll = clampi(int(_inspection_roll.call(day_index, chance, terms.duplicate(true))), 0, 99) else: roll = _stable_percent("inspection:%d:%d:%d:%d" % [ day_index, _state.heat, _state.suspicion, _state.active_case_ids.size(), ]) var selected := roll < chance var details := { &"day_index": day_index, &"chance": chance, &"roll": roll, &"selected": selected, &"inspection_id": &"", } if selected and not _has_open_inspection(): var scheduled := schedule_inspection(absolute_minute, terms) if not scheduled.is_success(): return scheduled details[&"inspection_id"] = scheduled.details[&"inspection_id"] elif selected: details[&"selected"] = false details[&"reason"] = &"INSPECTION_ALREADY_ACTIVE" _state.next_inspection_window = (day_index + 1) * 1_440 + INSPECTION_EVALUATION_MINUTE _state.revision += 1 _evaluated_inspection_days[day_index] = details.duplicate(true) last_error = "" return _success(details) func schedule_inspection( scheduled_at: int, options: Dictionary = {} ) -> CommandResult: if scheduled_at < 0: return _invalid("Inspection schedule requires a non-negative minute.") var inspection_id := StringName(_value(options, &"inspection_id", &"")) var generated_inspection_id := inspection_id.is_empty() if generated_inspection_id: inspection_id = StringName("inspection_%08d" % _next_inspection_sequence) if _inspections.has(inspection_id): return _duplicate("AuthorityInspection", inspection_id) var inspector_id := StringName(_value(options, &"inspector_id", &"")) var generated_inspector_id := inspector_id.is_empty() if generated_inspector_id: inspector_id = StringName("authority_inspector_%08d" % _next_inspector_sequence) var inspection := InspectionType.new().configure( inspection_id, inspector_id, scheduled_at, options ) if not inspection.is_valid(): return _invalid("AuthorityInspection fails schema validation.") _inspections[inspection_id] = inspection if generated_inspection_id: _next_inspection_sequence += 1 if generated_inspector_id: _next_inspector_sequence += 1 inspection_scheduled.emit(inspection) last_error = "" return _success({ &"inspection_id": inspection_id, &"inspector_id": inspector_id, }) func start_inspection(inspection_id: StringName, absolute_minute: int) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.ARRIVING, absolute_minute, &"INSPECTION_STARTED" ) func mark_inspector_arrived(inspection_id: StringName, absolute_minute: int) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.CHECK_IN, absolute_minute, &"ARRIVED" ) func mark_inspection_blocked_entry( inspection_id: StringName, absolute_minute: int, reason: StringName = &"BLOCKED_ENTRY" ) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.BLOCKED_ENTRY, absolute_minute, reason ) func interrupt_inspection( inspection_id: StringName, absolute_minute: int, reason: StringName = &"INTERRUPTED" ) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.INTERRUPTED, absolute_minute, reason ) func mark_inspection_hostile( inspection_id: StringName, absolute_minute: int, reason: StringName = &"HOSTILE" ) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.HOSTILE, absolute_minute, reason ) func cancel_inspection( inspection_id: StringName, absolute_minute: int, reason: StringName = &"CANCELLED" ) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.CANCELLED, absolute_minute, reason ) func plan_patrol( inspection_id: StringName, candidates: Array, absolute_minute: int, time_budget_minutes: int = 120 ) -> CommandResult: var inspection := get_inspection(inspection_id) if inspection == null: return _not_found("AuthorityInspection", inspection_id) if inspection.state != InspectionType.State.CHECK_IN: return _invalid("Patrol can only be planned after CHECK_IN.") if time_budget_minutes <= 0: return _invalid("Patrol time budget must be positive.") if absolute_minute < inspection.state_changed_at: return _invalid("Patrol plan time cannot move backwards.") var normalized: Array[Dictionary] = [] for raw_candidate: Variant in candidates: if raw_candidate is Dictionary: var waypoint := InspectionType.normalize_waypoint(raw_candidate) if not waypoint.is_empty(): normalized.append(waypoint) normalized.sort_custom(_waypoint_less) var selected: Array[Dictionary] = [] var selected_ids: Dictionary = {} var used_minutes := 0 for waypoint: Dictionary in normalized: var waypoint_id: StringName = waypoint[&"waypoint_id"] if selected_ids.has(waypoint_id): continue selected_ids[waypoint_id] = true var cost := int(waypoint[&"travel_minutes"]) + int(waypoint[&"inspection_minutes"]) if used_minutes + cost > time_budget_minutes: continue selected.append(waypoint.duplicate(true)) used_minutes += cost inspection.time_budget_minutes = time_budget_minutes inspection.time_spent_minutes = 0 inspection.waypoint_route = selected inspection.current_waypoint_index = 0 inspection.visited_waypoint_ids.clear() inspection.inaccessible_waypoint_ids.clear() inspection.revision += 1 var state_result := _change_inspection_state( inspection_id, InspectionType.State.PATROLLING, absolute_minute, &"PATROL_PLANNED" ) if not state_result.is_success(): return state_result last_error = "" return _success({ &"inspection_id": inspection_id, &"waypoint_ids": _waypoint_ids(selected), &"planned_minutes": used_minutes, &"time_budget_minutes": time_budget_minutes, }) ## Moves only to the next chosen waypoint. It never scans other cells. A ## failed path immediately records a reason and starts a safe departure. func advance_patrol( inspection_id: StringName, absolute_minute: int, context: Dictionary = {} ) -> CommandResult: var inspection := get_inspection(inspection_id) if inspection == null: return _not_found("AuthorityInspection", inspection_id) if inspection.state != InspectionType.State.PATROLLING: return _invalid("Inspection is not PATROLLING.") if absolute_minute < inspection.state_changed_at: return _invalid("Patrol time cannot move backwards.") if inspection.current_waypoint_index >= inspection.waypoint_route.size(): return _change_inspection_state( inspection_id, InspectionType.State.REVIEWING, absolute_minute, &"PATROL_COMPLETE" ) var waypoint: Dictionary = inspection.waypoint_route[inspection.current_waypoint_index] var from_cell := inspection.entrance_cell if inspection.current_waypoint_index > 0: from_cell = inspection.waypoint_route[inspection.current_waypoint_index - 1][&"cell"] var to_cell: Vector3i = waypoint[&"cell"] var path_result := { &"reachable": false, &"reason": &"PATH_QUERY_UNAVAILABLE", &"travel_minutes": int(waypoint[&"travel_minutes"]), } if _path_query.is_valid(): var path_context := context.duplicate(true) path_context[&"inspection_id"] = inspection_id path_context[&"waypoint_id"] = waypoint[&"waypoint_id"] var raw_result: Variant = _path_query.call(from_cell, to_cell, path_context) path_result = _normalize_path_result(raw_result, int(waypoint[&"travel_minutes"])) if not bool(path_result[&"reachable"]): var waypoint_id: StringName = waypoint[&"waypoint_id"] inspection.add_inaccessible_waypoint(waypoint_id) inspection.exit_reason = &"PATH_UNREACHABLE" inspection.result_reason = StringName(path_result.get(&"reason", &"PATH_UNREACHABLE")) var departure := _change_inspection_state( inspection_id, InspectionType.State.DEPARTING, absolute_minute, inspection.result_reason ) if not departure.is_success(): return departure return _success({ &"inspection_id": inspection_id, &"reachable": false, &"waypoint_id": waypoint_id, &"exit_reason": inspection.exit_reason, &"path_reason": inspection.result_reason, }) var travel_minutes := maxi(int(path_result[&"travel_minutes"]), 0) inspection.add_visited_waypoint(waypoint[&"waypoint_id"], travel_minutes) last_error = "" return _success({ &"inspection_id": inspection_id, &"reachable": true, &"waypoint_id": waypoint[&"waypoint_id"], &"cell": to_cell, &"travel_minutes": travel_minutes, }) func inspect_current_waypoint( inspection_id: StringName, evidence_ids: Array, absolute_minute: int, context: Dictionary = {} ) -> CommandResult: var inspection := get_inspection(inspection_id) if inspection == null: return _not_found("AuthorityInspection", inspection_id) if inspection.state != InspectionType.State.PATROLLING or inspection.current_waypoint_index <= 0: return _invalid("Inspector must first reach a patrol waypoint.") if absolute_minute < inspection.state_changed_at: return _invalid("Evidence inspection time cannot move backwards.") var waypoint: Dictionary = inspection.waypoint_route[inspection.current_waypoint_index - 1] var inspector_cell: Vector3i = waypoint[&"cell"] var discovered: Array[StringName] = [] var rejected: Array[Dictionary] = [] for evidence_id: StringName in _sorted_unique_names(evidence_ids): var result := attempt_discover_evidence( evidence_id, inspection.inspector_id, inspector_cell, absolute_minute, context ) if result.is_success(): inspection.add_discovered_evidence(evidence_id) discovered.append(evidence_id) else: rejected.append({ &"evidence_id": String(evidence_id), &"message": result.message, &"details": result.details.duplicate(true), }) inspection.time_spent_minutes += int(waypoint[&"inspection_minutes"]) inspection.evidence_score = _calculate_inspection_evidence_score(inspection, absolute_minute) inspection.revision += 1 last_error = "" return _success({ &"inspection_id": inspection_id, &"waypoint_id": waypoint[&"waypoint_id"], &"discovered_evidence_ids": discovered, &"rejected": rejected, &"evidence_score": inspection.evidence_score, }) func finish_patrol(inspection_id: StringName, absolute_minute: int) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.REVIEWING, absolute_minute, &"PATROL_COMPLETE" ) func begin_inspection_departure( inspection_id: StringName, absolute_minute: int, reason: StringName = &"DEPARTING" ) -> CommandResult: return _change_inspection_state( inspection_id, InspectionType.State.DEPARTING, absolute_minute, reason ) func resolve_inspection( inspection_id: StringName, resolution_id: StringName, absolute_minute: int, options: Dictionary = {} ) -> CommandResult: if resolution_id.is_empty(): return _invalid("Inspection resolution requires a resolution_id.") if _applied_resolutions.has(resolution_id): var existing: Dictionary = _applied_resolutions[resolution_id] var replay := _success(existing.duplicate(true)) replay.is_replay = true last_error = "" return replay var inspection := get_inspection(inspection_id) if inspection == null: return _not_found("AuthorityInspection", inspection_id) if inspection.state != InspectionType.State.REVIEWING: return _invalid("Inspection can only resolve from REVIEWING.") if not inspection.resolution_id.is_empty(): return _invalid("Inspection already has a different resolution.") inspection.evidence_score = _calculate_inspection_evidence_score(inspection, absolute_minute) var outcome := _determine_resolution(inspection.evidence_score, options) var fine_per_point := maxi(int(_value(options, &"fine_per_point", 10)), 0) var fine_amount := 0 if outcome >= InspectionType.Result.FINE: fine_amount = inspection.evidence_score * fine_per_point var sanction := _sanction_for_result(outcome) var payload := { &"resolution_id": resolution_id, &"inspection_id": inspection_id, &"absolute_minute": absolute_minute, &"result": StringName(InspectionType.Result.keys()[outcome]), &"evidence_score": inspection.evidence_score, &"fine_amount": fine_amount, &"sanction": sanction, &"evidence_ids": _names_to_strings(inspection.discovered_evidence_ids), } if absolute_minute < inspection.state_changed_at: return _invalid("Inspection resolution time cannot move backwards.") if _resolution_applier.is_valid(): var validation := _normalize_callback_result( _resolution_applier.call(payload.duplicate(true), &"validate", null) ) if not bool(validation[&"ok"]): return _invalid("Inspection resolution validation failed.", validation) var applied := _normalize_callback_result( _resolution_applier.call( payload.duplicate(true), &"apply", validation.get(&"token", null) ) ) if not bool(applied[&"ok"]): var rollback := _normalize_callback_result(_resolution_applier.call( payload.duplicate(true), &"rollback", applied.get(&"token", validation.get(&"token", null)) )) var failure_details := applied.duplicate(true) failure_details[&"rollback_ok"] = bool(rollback[&"ok"]) return _invalid("Inspection resolution application failed.", failure_details) var transition := _change_inspection_state( inspection_id, InspectionType.State.RESOLVING, absolute_minute, &"RESOLUTION_STARTED" ) if not transition.is_success(): return transition if fine_amount > 0: _state.add_fine_due(fine_amount) inspection.result = outcome inspection.resolution_id = resolution_id inspection.result_reason = &"RESOLUTION_APPLIED" inspection.revision += 1 _applied_resolutions[resolution_id] = payload.duplicate(true) for evidence_id: StringName in inspection.discovered_evidence_ids: var evidence := get_evidence(evidence_id) if evidence == null or evidence.incident_id.is_empty(): continue var incident := get_incident(evidence.incident_id) if incident != null and incident.state == IncidentType.State.OPEN: incident.resolve(resolution_id, &"INSPECTION_RESOLVED") _state.remove_active_case(incident.incident_id) _change_inspection_state( inspection_id, InspectionType.State.DEPARTING, absolute_minute, &"RESOLUTION_APPLIED" ) resolution_applied.emit(inspection_id, payload.duplicate(true)) last_error = "" return _success(payload) func depart_inspection(inspection_id: StringName, absolute_minute: int) -> CommandResult: var inspection := get_inspection(inspection_id) if inspection == null: return _not_found("AuthorityInspection", inspection_id) var departure_reason := inspection.result_reason if departure_reason.is_empty(): departure_reason = &"DEPARTED" var result := _change_inspection_state( inspection_id, InspectionType.State.CLOSED, absolute_minute, departure_reason ) if not result.is_success(): return result last_error = "" return _success({ &"inspection_id": inspection_id, &"state": &"CLOSED", &"exit_reason": inspection.exit_reason, &"result_reason": inspection.result_reason, }) func validate_invariants() -> Dictionary: return _validate_candidate_invariants( _state, _incidents, _evidence, _inspections, _scored_incident_points, _applied_resolutions, current_minute ) func create_snapshot() -> Dictionary: var incidents: Array[Dictionary] = [] for incident: AuthorityIncident in get_incidents(): incidents.append(incident.create_snapshot()) var evidence_snapshots: Array[Dictionary] = [] for evidence: AuthorityEvidence in get_all_evidence(): evidence_snapshots.append(evidence.create_snapshot()) var inspections: Array[Dictionary] = [] for inspection: AuthorityInspection in get_inspections(): inspections.append(inspection.create_snapshot()) var scored_points: Array[Dictionary] = [] for score_key: StringName in _sorted_keys(_scored_incident_points): scored_points.append({ &"score_key": String(score_key), &"points": int(_scored_incident_points[score_key]), }) var resolutions: Array[Dictionary] = [] for resolution_id: StringName in _sorted_keys(_applied_resolutions): resolutions.append({ &"resolution_id": String(resolution_id), &"payload": (_applied_resolutions[resolution_id] as Dictionary).duplicate(true), }) var evaluated_days: Array[int] = [] for day_value: Variant in _evaluated_inspection_days.keys(): evaluated_days.append(int(day_value)) evaluated_days.sort() var evaluations: Array[Dictionary] = [] for day_index: int in evaluated_days: evaluations.append({ &"day_index": day_index, &"result": (_evaluated_inspection_days[day_index] as Dictionary).duplicate(true), }) return { &"version": SNAPSHOT_VERSION, &"current_minute": current_minute, &"authority_state": _state.create_snapshot(), &"incidents": incidents, &"evidence": evidence_snapshots, &"inspections": inspections, &"scored_incident_points": scored_points, &"applied_resolutions": resolutions, &"evaluated_inspection_days": evaluations, &"next_inspection_sequence": _next_inspection_sequence, &"next_inspector_sequence": _next_inspector_sequence, } ## Parses every object and cross-reference before replacing any live field. func restore_snapshot(snapshot: Dictionary) -> bool: var parsed := _parse_snapshot(snapshot) if not bool(parsed.get(&"ok", false)): last_error = String(parsed.get(&"error", "Invalid Authority snapshot.")) return false _state = parsed[&"authority_state"] _incidents = parsed[&"incidents"] _evidence = parsed[&"evidence"] _inspections = parsed[&"inspections"] _scored_incident_points = parsed[&"scored_incident_points"] _applied_resolutions = parsed[&"applied_resolutions"] _evaluated_inspection_days = parsed[&"evaluated_inspection_days"] current_minute = int(parsed[&"current_minute"]) _next_inspection_sequence = int(parsed[&"next_inspection_sequence"]) _next_inspector_sequence = int(parsed[&"next_inspector_sequence"]) last_error = "" return true func _parse_snapshot(snapshot: Dictionary) -> Dictionary: if int(_value(snapshot, &"version", 0)) != SNAPSHOT_VERSION: return {&"ok": false, &"error": "Unsupported Authority snapshot version."} for collection_key: StringName in [ &"incidents", &"evidence", &"inspections", &"scored_incident_points", &"applied_resolutions", &"evaluated_inspection_days", ]: if not _value(snapshot, collection_key, null) is Array: return {&"ok": false, &"error": "Malformed Authority collection: %s" % collection_key} var raw_state: Variant = _value(snapshot, &"authority_state", null) if not raw_state is Dictionary: return {&"ok": false, &"error": "Malformed AuthorityState snapshot."} var next_state := HeatStateType.from_snapshot(raw_state) if next_state == null: return {&"ok": false, &"error": "Invalid AuthorityState snapshot."} var next_incidents: Dictionary = {} for raw_incident: Variant in _value(snapshot, &"incidents", []): if not raw_incident is Dictionary: return {&"ok": false, &"error": "Malformed Incident snapshot."} var incident := IncidentType.from_snapshot(raw_incident) if incident == null or next_incidents.has(incident.incident_id): return {&"ok": false, &"error": "Invalid or duplicate Incident snapshot."} next_incidents[incident.incident_id] = incident var next_evidence: Dictionary = {} for raw_evidence: Variant in _value(snapshot, &"evidence", []): if not raw_evidence is Dictionary: return {&"ok": false, &"error": "Malformed Evidence snapshot."} var evidence := EvidenceType.from_snapshot(raw_evidence) if evidence == null or next_evidence.has(evidence.evidence_id): return {&"ok": false, &"error": "Invalid or duplicate Evidence snapshot."} next_evidence[evidence.evidence_id] = evidence var next_inspections: Dictionary = {} for raw_inspection: Variant in _value(snapshot, &"inspections", []): if not raw_inspection is Dictionary: return {&"ok": false, &"error": "Malformed Inspection snapshot."} var inspection := InspectionType.from_snapshot(raw_inspection) if inspection == null or next_inspections.has(inspection.inspection_id): return {&"ok": false, &"error": "Invalid or duplicate Inspection snapshot."} next_inspections[inspection.inspection_id] = inspection var next_scored_points: Dictionary = {} for raw_score: Variant in _value(snapshot, &"scored_incident_points", []): if not raw_score is Dictionary: return {&"ok": false, &"error": "Malformed Evidence score snapshot."} var score_key := StringName(_value(raw_score, &"score_key", &"")) var points := int(_value(raw_score, &"points", -1)) if score_key.is_empty() or points < 0 or next_scored_points.has(score_key): return {&"ok": false, &"error": "Invalid or duplicate Evidence score snapshot."} next_scored_points[score_key] = points var next_resolutions: Dictionary = {} for raw_resolution: Variant in _value(snapshot, &"applied_resolutions", []): if not raw_resolution is Dictionary or not _value(raw_resolution, &"payload", null) is Dictionary: return {&"ok": false, &"error": "Malformed resolution snapshot."} var resolution_id := StringName(_value(raw_resolution, &"resolution_id", &"")) if resolution_id.is_empty() or next_resolutions.has(resolution_id): return {&"ok": false, &"error": "Invalid or duplicate resolution snapshot."} next_resolutions[resolution_id] = (_value(raw_resolution, &"payload", {}) as Dictionary).duplicate(true) var next_evaluations: Dictionary = {} for raw_evaluation: Variant in _value(snapshot, &"evaluated_inspection_days", []): if not raw_evaluation is Dictionary or not _value(raw_evaluation, &"result", null) is Dictionary: return {&"ok": false, &"error": "Malformed inspection evaluation snapshot."} var day_index := int(_value(raw_evaluation, &"day_index", -1)) if day_index < 0 or next_evaluations.has(day_index): return {&"ok": false, &"error": "Invalid or duplicate inspection evaluation day."} next_evaluations[day_index] = (_value(raw_evaluation, &"result", {}) as Dictionary).duplicate(true) var next_current_minute := int(_value(snapshot, &"current_minute", -1)) var next_inspection_sequence := int(_value(snapshot, &"next_inspection_sequence", 0)) var next_inspector_sequence := int(_value(snapshot, &"next_inspector_sequence", 0)) if next_current_minute < 0 or next_inspection_sequence < 1 or next_inspector_sequence < 1: return {&"ok": false, &"error": "Authority snapshot counters are invalid."} var invariant_report := _validate_candidate_invariants( next_state, next_incidents, next_evidence, next_inspections, next_scored_points, next_resolutions, next_current_minute ) if not bool(invariant_report[&"ok"]): return { &"ok": false, &"error": "Authority snapshot violates invariants: %s" % [str(invariant_report[&"errors"])], } return { &"ok": true, &"authority_state": next_state, &"incidents": next_incidents, &"evidence": next_evidence, &"inspections": next_inspections, &"scored_incident_points": next_scored_points, &"applied_resolutions": next_resolutions, &"evaluated_inspection_days": next_evaluations, &"current_minute": next_current_minute, &"next_inspection_sequence": next_inspection_sequence, &"next_inspector_sequence": next_inspector_sequence, } func _validate_candidate_invariants( state: HeatState, incidents: Dictionary, evidence_by_id: Dictionary, inspections: Dictionary, scored_points: Dictionary, resolutions: Dictionary, reference_minute: int ) -> Dictionary: var errors: Array[String] = [] if state == null or not state.is_valid(): errors.append("AuthorityState is invalid.") return {&"ok": false, &"errors": errors} if state.last_decay_day > reference_minute / 1_440: errors.append("AuthorityState daily decay is ahead of current time.") for incident_id: StringName in _sorted_keys(incidents): var incident: AuthorityIncident = incidents[incident_id] if incident == null or incident.incident_id != incident_id or not incident.is_valid(): errors.append("Invalid Incident: %s" % incident_id) continue if incident.detector_ids.is_empty(): errors.append("Incident has no reporting detector: %s" % incident_id) var should_be_active := incident.state == IncidentType.State.OPEN if state.active_case_ids.has(incident_id) != should_be_active: errors.append("Incident active-case mismatch: %s" % incident_id) for evidence_id: StringName in incident.evidence_ids: if not evidence_by_id.has(evidence_id): errors.append("Incident references missing Evidence: %s" % evidence_id) if incident.state == IncidentType.State.RESOLVED and not resolutions.has(incident.resolution_id): errors.append("Resolved Incident references missing resolution: %s" % incident_id) for case_id: StringName in state.active_case_ids: if not incidents.has(case_id): errors.append("AuthorityState references missing active case: %s" % case_id) for evidence_id: StringName in _sorted_keys(evidence_by_id): var evidence: AuthorityEvidence = evidence_by_id[evidence_id] if evidence == null or evidence.evidence_id != evidence_id or not evidence.is_valid(): errors.append("Invalid Evidence: %s" % evidence_id) continue if not evidence.incident_id.is_empty(): if not incidents.has(evidence.incident_id): errors.append("Evidence references missing Incident: %s" % evidence_id) elif not (incidents[evidence.incident_id] as AuthorityIncident).evidence_ids.has(evidence_id): errors.append("Incident/Evidence reverse reference mismatch: %s" % evidence_id) var expected_scores: Dictionary = {} for evidence_id: StringName in _sorted_keys(evidence_by_id): var scored_evidence: AuthorityEvidence = evidence_by_id[evidence_id] if scored_evidence == null or not scored_evidence.is_discovered() \ or scored_evidence.is_expired(reference_minute): continue var expected_key := scored_evidence.incident_id if expected_key.is_empty(): expected_key = StringName("evidence:%s" % evidence_id) expected_scores[expected_key] = maxi( int(expected_scores.get(expected_key, 0)), scored_evidence.severity ) for score_key: StringName in _sorted_keys(scored_points): if int(scored_points[score_key]) < 0 or int(scored_points[score_key]) > 100: errors.append("Invalid Evidence score: %s" % score_key) if scored_points != expected_scores: errors.append("Evidence score index does not match discovered, unexpired evidence.") var score_sum := 0 for points: Variant in scored_points.values(): score_sum += int(points) if score_sum != state.evidence_points: errors.append("Authority evidence_points cache mismatch.") for inspection_id: StringName in _sorted_keys(inspections): var inspection: AuthorityInspection = inspections[inspection_id] if inspection == null or inspection.inspection_id != inspection_id or not inspection.is_valid(): errors.append("Invalid Inspection: %s" % inspection_id) continue for evidence_id: StringName in inspection.discovered_evidence_ids: if not evidence_by_id.has(evidence_id) or not (evidence_by_id[evidence_id] as AuthorityEvidence).is_discovered(): errors.append("Inspection references unavailable Evidence: %s" % evidence_id) if not inspection.resolution_id.is_empty() and not resolutions.has(inspection.resolution_id): errors.append("Inspection resolution reference is missing: %s" % inspection.resolution_id) var expected_fines_due := 0 for resolution_id: StringName in _sorted_keys(resolutions): var payload: Dictionary = resolutions[resolution_id] if StringName(_value(payload, &"resolution_id", &"")) != resolution_id: errors.append("Resolution payload ID mismatch: %s" % resolution_id) var inspection_id := StringName(_value(payload, &"inspection_id", &"")) var fine_amount := int(_value(payload, &"fine_amount", -1)) if fine_amount < 0: errors.append("Resolution has an invalid fine amount: %s" % resolution_id) else: expected_fines_due += fine_amount if not inspections.has(inspection_id): errors.append("Resolution references missing Inspection: %s" % resolution_id) else: var resolved_inspection: AuthorityInspection = inspections[inspection_id] if resolved_inspection.resolution_id != resolution_id: errors.append("Resolution/Inspection reverse reference mismatch: %s" % resolution_id) if String(_value(payload, &"result", "")) != InspectionType.Result.keys()[resolved_inspection.result]: errors.append("Resolution result cache mismatch: %s" % resolution_id) if int(_value(payload, &"evidence_score", -1)) != resolved_inspection.evidence_score: errors.append("Resolution Evidence score cache mismatch: %s" % resolution_id) if expected_fines_due != state.fines_due: errors.append("Authority fines_due cache mismatch.") return {&"ok": errors.is_empty(), &"errors": errors} func _recompute_authority_evidence_points(absolute_minute: int) -> void: var next_scores: Dictionary = {} for evidence_id: StringName in _sorted_keys(_evidence): var evidence := get_evidence(evidence_id) if evidence == null or not evidence.is_discovered() or evidence.is_expired(absolute_minute): continue var score_key := evidence.incident_id if score_key.is_empty(): score_key = StringName("evidence:%s" % evidence.evidence_id) next_scores[score_key] = maxi(int(next_scores.get(score_key, 0)), evidence.severity) _scored_incident_points = next_scores var total := 0 for points: Variant in _scored_incident_points.values(): total += int(points) _state.set_evidence_points(total) func _calculate_inspection_evidence_score( inspection: AuthorityInspection, absolute_minute: int ) -> int: var incident_scores: Dictionary = {} for evidence_id: StringName in inspection.discovered_evidence_ids: var evidence := get_evidence(evidence_id) if evidence == null or not evidence.is_discovered() or evidence.is_expired(absolute_minute): continue var score_key := evidence.incident_id if score_key.is_empty(): score_key = StringName("evidence:%s" % evidence.evidence_id) incident_scores[score_key] = maxi(int(incident_scores.get(score_key, 0)), evidence.severity) var total := 0 for points: Variant in incident_scores.values(): total += int(points) return total func _change_inspection_state( inspection_id: StringName, new_state: AuthorityInspection.State, absolute_minute: int, reason: StringName ) -> CommandResult: var inspection := get_inspection(inspection_id) if inspection == null: return _not_found("AuthorityInspection", inspection_id) if absolute_minute < inspection.state_changed_at: return _invalid("Inspection state time cannot move backwards.") if not _transition_is_allowed(inspection.state, new_state): return _invalid("Illegal Inspection state transition.", { &"old_state": InspectionType.State.keys()[inspection.state], &"new_state": InspectionType.State.keys()[new_state], }) var old_state := inspection.state if not inspection.set_state(new_state, absolute_minute, reason): return _invalid("Inspection state transition did not change state.") inspection_state_changed.emit(inspection_id, old_state, new_state, reason) last_error = "" return _success({ &"inspection_id": inspection_id, &"old_state": StringName(InspectionType.State.keys()[old_state]), &"new_state": StringName(InspectionType.State.keys()[new_state]), &"reason": reason, }) static func _transition_is_allowed( old_state: AuthorityInspection.State, new_state: AuthorityInspection.State ) -> bool: match old_state: InspectionType.State.SCHEDULED: return new_state in [InspectionType.State.ARRIVING, InspectionType.State.CANCELLED] InspectionType.State.ARRIVING: return new_state in [ InspectionType.State.CHECK_IN, InspectionType.State.BLOCKED_ENTRY, InspectionType.State.INTERRUPTED, InspectionType.State.HOSTILE, InspectionType.State.CANCELLED, ] InspectionType.State.CHECK_IN: return new_state in [ InspectionType.State.PATROLLING, InspectionType.State.DEPARTING, InspectionType.State.INTERRUPTED, InspectionType.State.HOSTILE, InspectionType.State.CANCELLED, ] InspectionType.State.PATROLLING: return new_state in [ InspectionType.State.REVIEWING, InspectionType.State.DEPARTING, InspectionType.State.INTERRUPTED, InspectionType.State.HOSTILE, InspectionType.State.CANCELLED, ] InspectionType.State.REVIEWING: return new_state in [ InspectionType.State.RESOLVING, InspectionType.State.DEPARTING, InspectionType.State.CANCELLED, ] InspectionType.State.RESOLVING: return new_state in [InspectionType.State.REVIEWING, InspectionType.State.DEPARTING] InspectionType.State.BLOCKED_ENTRY, InspectionType.State.INTERRUPTED, InspectionType.State.HOSTILE: return new_state == InspectionType.State.DEPARTING InspectionType.State.DEPARTING: return new_state == InspectionType.State.CLOSED return false static func _determine_resolution(score: int, options: Dictionary) -> AuthorityInspection.Result: if _has_key(options, &"result"): var requested := String(_value(options, &"result", "PENDING")) var index := InspectionType.Result.keys().find(requested) if index > InspectionType.Result.PENDING: return index as AuthorityInspection.Result var warning_threshold := maxi(int(_value(options, &"warning_threshold", 1)), 1) var fine_threshold := maxi(int(_value(options, &"fine_threshold", 20)), warning_threshold) var closure_threshold := maxi(int(_value(options, &"closure_threshold", 50)), fine_threshold) var confiscation_threshold := maxi(int(_value(options, &"confiscation_threshold", 80)), closure_threshold) var arrest_threshold := maxi(int(_value(options, &"arrest_threshold", 110)), confiscation_threshold) var raid_threshold := maxi(int(_value(options, &"raid_threshold", 140)), arrest_threshold) if score >= raid_threshold: return InspectionType.Result.RAID_ESCALATION if score >= arrest_threshold: return InspectionType.Result.ARREST_ATTEMPT if score >= confiscation_threshold: return InspectionType.Result.CONFISCATION if score >= closure_threshold: return InspectionType.Result.TEMPORARY_CLOSURE if score >= fine_threshold: return InspectionType.Result.FINE if score >= warning_threshold: return InspectionType.Result.WARNING return InspectionType.Result.NO_ACTION static func _sanction_for_result(result: AuthorityInspection.Result) -> StringName: match result: InspectionType.Result.TEMPORARY_CLOSURE: return &"TEMPORARY_CLOSURE" InspectionType.Result.CONFISCATION: return &"CONFISCATION" InspectionType.Result.ARREST_ATTEMPT: return &"ARREST_ATTEMPT" InspectionType.Result.RAID_ESCALATION: return &"RAID_ESCALATION" return &"NONE" static func _normalize_path_result(value: Variant, fallback_minutes: int) -> Dictionary: if value is bool: return { &"reachable": value, &"reason": &"OK" if value else &"PATH_UNREACHABLE", &"travel_minutes": fallback_minutes, } if value is Dictionary: return { &"reachable": bool(_value(value, &"reachable", false)), &"reason": StringName(_value(value, &"reason", &"OK")), &"travel_minutes": maxi(int(_value(value, &"travel_minutes", fallback_minutes)), 0), } return { &"reachable": false, &"reason": &"PATH_QUERY_INVALID", &"travel_minutes": fallback_minutes, } static func _normalize_callback_result(value: Variant) -> Dictionary: if value is bool: return {&"ok": value} if value is Dictionary: var result: Dictionary = value.duplicate(true) result[&"ok"] = bool(_value(value, &"ok", false)) return result return {&"ok": false, &"reason": &"INVALID_CALLBACK_RESULT"} func _has_open_inspection() -> bool: for inspection: AuthorityInspection in get_inspections(): if not inspection.is_terminal(): return true return false static func _waypoint_less(a: Dictionary, b: Dictionary) -> bool: var utility_a := int(a.get(&"utility", 0)) var utility_b := int(b.get(&"utility", 0)) if utility_a != utility_b: return utility_a > utility_b return String(a.get(&"waypoint_id", "")) < String(b.get(&"waypoint_id", "")) static func _waypoint_ids(waypoints: Array[Dictionary]) -> Array[StringName]: var result: Array[StringName] = [] for waypoint: Dictionary in waypoints: result.append(StringName(waypoint[&"waypoint_id"])) return result static func _stable_percent(text: String) -> int: var value := 17 for index: int in range(text.length()): value = (value * 131 + text.unicode_at(index)) % 10_000_019 return value % 100 static func _sorted_keys(source: Dictionary) -> Array[StringName]: var result: Array[StringName] = [] for raw_key: Variant in source.keys(): result.append(StringName(raw_key)) result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) return result static func _sorted_unique_names(values: Variant) -> Array[StringName]: var result: Array[StringName] = [] if values is Array: for raw_value: Variant in values: var value := StringName(raw_value) if not value.is_empty() and not result.has(value): result.append(value) result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) return result static func _names_to_strings(values: Array[StringName]) -> Array[String]: var result: Array[String] = [] for value: StringName in values: result.append(String(value)) return result static func _has_key(source: Dictionary, key: StringName) -> bool: return source.has(key) or source.has(String(key)) static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant: if source.has(key): return source[key] return source.get(String(key), fallback) func _success(details: Dictionary = {}) -> CommandResult: return CommandResultType.succeeded(0, details) func _invalid(message: String, details: Dictionary = {}) -> CommandResult: last_error = message return CommandResultType.failed( CommandResultType.Code.VALIDATION_FAILED, 0, message, details ) func _not_found(kind: String, entity_id: StringName) -> CommandResult: return _invalid("%s not found: %s" % [kind, entity_id], {&"entity_id": entity_id}) func _duplicate(kind: String, entity_id: StringName) -> CommandResult: return _invalid("Duplicate %s ID: %s" % [kind, entity_id], {&"entity_id": entity_id})