class_name SimulationRuntimeService extends Node signal command_completed(result: CommandResult) signal snapshot_published(snapshot: Dictionary, digest: String) # UI subscribers only need notification; exact snapshots remain available. signal state_updated(snapshot: Dictionary) signal definition_validation_failed(report: Dictionary) signal save_operation_completed(result: Dictionary) # Optional, read-only UI contribution collected at the actual checkpoint. signal checkpoint_presentation_requested(presentation: Dictionary) const DEFAULT_WORLD_SEED := 20260827 const STARTER_MANA_OUTPUT := 100 const STARTER_MANA_SOURCE := &"world_tree.mana_source" const STARTER_MANA_BUS := &"world_tree.mana_bus" const PLAYER_CASH_ACCOUNT := &"PLAYER_CASH" const CONSTRUCTION_EXPENSE_ACCOUNT := &"CONSTRUCTION_EXPENSE" const OPERATING_EXPENSE_ACCOUNT := &"OPERATING_EXPENSE" const AUTHORITY_FINE_ACCOUNT := &"AUTHORITY_FINE_EXPENSE" const WAGE_EXPENSE_ACCOUNT := &"WAGE_EXPENSE" const RUNTIME_SNAPSHOT_VERSION := 14 const PeepingRooms := preload("res://scripts/special/peeping_room_service.gd") const AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION := 14 const EVENT_THREAT_RUNTIME_SNAPSHOT_VERSION := 13 const MIMI_PURCHASE_RUNTIME_SNAPSHOT_VERSION := 15 const PurchaseQuoteV2 := preload("res://scripts/supply/supply_purchase_quote_v2.gd") const AutoPurchasePlansType := preload("res://scripts/supply/supply_auto_purchase_quote_plans.gd") const CLEANING_RUNTIME_SNAPSHOT_VERSION := 12 const WEATHER_RUNTIME_SNAPSHOT_VERSION := 11 const FacilityCleanliness = preload("res://scripts/environment/facility_cleanliness_service.gd") const ManaReserveCycle = preload("res://scripts/mana/mana_reserve_cycle.gd") const WarpService = preload("res://scripts/transport/warp_service.gd") const CleaningBridge = preload("res://scripts/environment/facility_cleaning_bridge.gd") const CleaningPolicy = preload("res://scripts/environment/facility_cleaning_policy.gd") const UPKEEP_RUNTIME_SNAPSHOT_VERSION := 10 const WeatherTimeline = preload("res://scripts/environment/weather_timeline.gd") const STAIRS_RUNTIME_SNAPSHOT_VERSION := 9 const TRANSPORT_RUNTIME_SNAPSHOT_VERSION := 8 const SUPPLY_RUNTIME_SNAPSHOT_VERSION := 7 const INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION := 6 const LEGACY_RUNTIME_SNAPSHOT_VERSION := 1 const DEFENSE_RUNTIME_SNAPSHOT_VERSION := 2 const PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION := 3 const CONTENT_RUNTIME_SNAPSHOT_VERSION := 4 const OPERATIONS_RUNTIME_SNAPSHOT_VERSION := 5 const MVP_CONTENT_MANIFEST_VERSION := 2 const OPERATIONS_RUNTIME_WRAPPER_VERSION := 1 const OPERATIONS_START_MINUTE := 360 const OPERATIONS_COORDINATOR_ORIGIN_MINUTE := 359 const MAX_PERSONAL_ACTION_POINTS := 4 const OPERATIONS_TEST_PREPARE_PHASE: StringName = &"PREPARE" const OPERATIONS_TEST_AFTER_FIRST_MUTATION_PHASE: StringName = &"AFTER_FIRST_MUTATION" const DEFAULT_OPERATIONS_DEMAND_PROFILES := [ {&"facility_tag": &"hygiene", &"guest_class": &"cleric", &"time_band": &"MORNING"}, {&"facility_tag": &"service", &"guest_class": &"diplomat", &"time_band": &"MORNING"}, {&"facility_tag": &"lodging", &"guest_class": &"mage", &"time_band": &"DAY"}, {&"facility_tag": &"service", &"guest_class": &"merchant", &"time_band": &"DAY"}, {&"facility_tag": &"lodging", &"guest_class": &"noble", &"time_band": &"EVENING"}, {&"facility_tag": &"dining", &"guest_class": &"warrior", &"time_band": &"EVENING"}, {&"facility_tag": &"leisure", &"guest_class": &"outsider", &"time_band": &"NIGHT"}, {&"facility_tag": &"underground", &"guest_class": &"rogue", &"time_band": &"NIGHT"}, ] const MOVEMENT_CREDIT_SCALE := 1_000 const STAIR_MOVEMENT_CREDIT := 1_500 const MIN_MOVEMENT_CREDIT_PER_MINUTE := 250 const DEFAULT_AUTOSAVE_PATH := "user://saves/autosave" const SAVE_LIFECYCLE_VERSION := 1 const DEFAULT_SAVE_POLICY := { &"enabled": false, &"daily_checkpoint_enabled": true, &"autosave_path": DEFAULT_AUTOSAVE_PATH, &"autosave_generations": SaveService.DEFAULT_AUTOSAVE_GENERATIONS, &"auto_migrate": true, } const RNG_STREAM_IDS: Array[StringName] = [ &"world", &"guest_spawn", &"guest_decision", &"staff", &"relationship", &"authority", &"defense", &"event", &"loot", ] const DEFENSE_COMMAND_TYPES: Array[StringName] = [ &"create_defense_raid", &"register_defense_combatant", &"register_defense_asset", &"start_defense_raid", &"set_defense_retreat_policy", &"queue_defense_skill", &"resolve_defense_round", &"begin_next_defense_wave", &"retreat_defenders", &"cancel_defense_raid", ] var definition_registry := DefinitionRegistry.new() var command_queue := CommandQueue.new() var id_generator := StableIdGenerator.new() var peeping_room_service := PeepingRooms.new() var domain_events := DomainEventStream.new() var world_service := WorldService.new() var path_service := PathService.new() var mana_network_service := ManaNetworkService.new() var weather := WeatherTimeline.new() var facility_cleanliness := FacilityCleanliness.new() var cleaning_policy := CleaningPolicy.new() var cleaning_bridge := CleaningBridge.new() var facility_upkeep := FacilityUpkeepAccrual.new() var mana_reserve := ManaReserveCycle.new() var upkeep_settlement := FacilityUpkeepSettlement.new() var economy_service: EconomyService var construction_resource_state: LedgerBackedResourceState var operating_resource_state: LedgerBackedResourceState var inventory_service := InventoryService.new() var logistics_dispatcher := LogisticsDispatcher.new() var logistics_requests: Dictionary = {} var kitchen_production := KitchenProductionService.new() var supply_management := SupplyManagementService.new() var auto_purchase_plans := AutoPurchasePlansType.new() var supply_logistics := SupplyLogisticsBridge.new() var elevators := ElevatorService.new() var legacy_stairs := LegacyStairService.new() var staff_commute := StaffCommuteService.new() var facility_service := FacilityService.new() var guest_service := GuestService.new() var staff_service := StaffService.new() var mvp_content_catalog := MvpContentCatalog.new() var mvp_content_report: Dictionary = {} # Cache only catalog-derived immutable metadata, never GuestDefinition objects. var _mvp_manifest_cache_catalog: MvpContentCatalog var _mvp_manifest_cache_generation := -1 var _mvp_manifest_cache: Dictionary = {} var progression_service := ProgressionService.new() var operations_coordinator: OperationsCoordinator var relationship_service := RelationshipService.new() var event_service := EventService.new() var authority_service := AuthorityService.new() var defense_service := DefenseService.new() var construction_service: ConstructionService var excavation_service: ExcavationService var spatial_subslot_service: SpatialSubslotService var route_subslot_path_adapter: RouteSubslotPathAdapter var facility_infrastructure_projector: FacilityInfrastructureProjector var latest_snapshot: Dictionary = {} var _latest_digest := "" var _latest_digest_dirty := false var latest_digest: String: get: if _latest_digest_dirty: _latest_digest = SimulationDigest.compute(latest_snapshot) _latest_digest_dirty = false return _latest_digest var latest_save_operation_result: Dictionary = {} var latest_restore_safety_report: Dictionary = {} var world_seed := DEFAULT_WORLD_SEED var save_migration_registry := SaveMigrationRegistry.new() var _clock: SimulationKernel var _state: Variant var _rng_streams: Dictionary = {} var _executors: Dictionary = {} var _next_command_id := 1 var _staffing_projection_enabled := false var _movement_credits: Dictionary = {} var _hired_staff_candidate_ids: Dictionary = {} var _save_policy: Dictionary = DEFAULT_SAVE_POLICY.duplicate(true) var _last_daily_checkpoint_day := -1 var _pending_checkpoints: Array[Dictionary] = [] var _next_checkpoint_request_id := 1 var _initialized := false var _active_command_minute := -1 var _operations_enabled := false # Transient in-tick execution guards. Persisted timing lives inside Defense. var _defense_round_attempt_minute := -1 var _defense_round_attempted_raids: Dictionary = {} var _defense_automatic_failed_minute := -1 var _operations_tick_blocked := false var _operations_runtime_failure_latched := false var _operations_runtime_failure_report: Dictionary = {} var _operations_deferred_runtime_minute := -1 var _operations_resume_publish_pending := false var _personal_action_points := 0 var _operations_test_boundary_hooks: Dictionary = {} var _operations_transaction_active := false var _operations_queued_domain_events: Array[SimulationDomainEvent] = [] var _operations_queued_guest_spawn_notifications: Array[StringName] = [] var _operations_flushing_guest_spawn_notifications: Dictionary = {} var _operations_queued_economy_notifications: Array[StringName] = [] var _operations_flushing_economy_notifications: Dictionary = {} var _infrastructure_failure_latched := false var _infrastructure_failure_report: Dictionary = {} var _construction_completion_transaction_active := false var _construction_committing_notifications := false var _construction_queued_domain_events: Array[SimulationDomainEvent] = [] func _ready() -> void: # The autoload is also present in headless test SceneTrees, so merely entering # the tree must never opt into disk writes. The playable main scene enables # the production policy explicitly. if not _initialized: initialize(GameClock, GameState, DEFAULT_WORLD_SEED) func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_SEED) -> Dictionary: if _initialized and _clock != null: _clock.unregister_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.0_weather") _clock.unregister_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.a_upkeep") _clock.unregister_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.operations") _clock.unregister_tick_handler(_clock.TickPhase.APPLY_COMMANDS, &"simulation_runtime.commands") _clock.unregister_tick_handler(_clock.TickPhase.PUBLISH_SNAPSHOT, &"simulation_runtime.snapshot") _clock.unregister_tick_handler(_clock.TickPhase.CONSTRUCTION, &"simulation_runtime.construction") _clock.unregister_tick_handler(_clock.TickPhase.MANA, &"simulation_runtime.mana") _clock.unregister_tick_handler(_clock.TickPhase.LOGISTICS, &"simulation_runtime.logistics") _clock.unregister_tick_handler(_clock.TickPhase.FACILITY_COMPLETION, &"simulation_runtime.facilities") _clock.unregister_tick_handler(_clock.TickPhase.VITALS_AND_ENVIRONMENT, &"simulation_runtime.guest_vitals") _clock.unregister_tick_handler(_clock.TickPhase.FACILITY_QUEUES, &"simulation_runtime.guest_queues") _clock.unregister_tick_handler(_clock.TickPhase.INCIDENTS_AND_EVENTS, &"simulation_runtime.authority_incidents") _clock.unregister_tick_handler(_clock.TickPhase.INCIDENTS_AND_EVENTS, &"simulation_runtime.defense") _clock.unregister_tick_handler(_clock.TickPhase.INCIDENTS_AND_EVENTS, &"simulation_runtime.relationship_events") _clock.unregister_tick_handler(_clock.TickPhase.AI_DECISIONS, &"simulation_runtime.staff_ai") _clock.unregister_tick_handler(_clock.TickPhase.PUBLISH_SNAPSHOT, &"simulation_runtime.zz_save_checkpoints") _detach_defense_service(defense_service) _clear_mvp_content_manifest_cache() _clock = clock _state = state world_seed = seed mvp_content_catalog = MvpContentCatalog.new() mvp_content_report = mvp_content_catalog.rebuild() progression_service = ProgressionService.new() var initial_progression_ok := progression_service.configure(1, { &"asset_gold": maxi(int(_state.get_resource(&"gold")), 0), &"faith_lifetime": maxi(int(_state.get_resource(&"faith")), 0), }) _hired_staff_candidate_ids.clear() command_queue = CommandQueue.new() id_generator = StableIdGenerator.new() peeping_room_service = PeepingRooms.new() domain_events = DomainEventStream.new() world_service = WorldService.new() path_service = PathService.new() path_service.set_vertical_walk_requires_connector(true) legacy_stairs = LegacyStairService.new() legacy_stairs.configure(path_service) _movement_credits.clear() _connect_path_performance_signal() mana_network_service = ManaNetworkService.new() _initialize_world(maxi(int(_state.world_tree_rank), 1)) _initialize_mana_network() _initialize_economy() _initialize_s2_operations() _staffing_projection_enabled = false _initialize_s3_actors() _initialize_s4_relationship_events() _initialize_s5_authority() _initialize_s5_defense() construction_service = ConstructionService.new(world_service, construction_resource_state) construction_service.current_minute = _clock.absolute_minute peeping_room_service.configure(world_service, facility_service, construction_service, path_service, id_generator) _connect_construction_signals() var initial_infrastructure_ok := _initialize_infrastructure_services() _next_command_id = 1 _last_daily_checkpoint_day = -1 _pending_checkpoints.clear() _next_checkpoint_request_id = 1 _active_command_minute = -1 operations_coordinator = null _operations_enabled = false _operations_tick_blocked = false _operations_runtime_failure_latched = false _operations_runtime_failure_report.clear() _operations_deferred_runtime_minute = -1 _operations_resume_publish_pending = false _personal_action_points = 0 _operations_test_boundary_hooks.clear() _operations_transaction_active = false _operations_queued_domain_events.clear() _operations_queued_guest_spawn_notifications.clear() _operations_flushing_guest_spawn_notifications.clear() _operations_queued_economy_notifications.clear() _operations_flushing_economy_notifications.clear() _infrastructure_failure_latched = false _infrastructure_failure_report.clear() _construction_completion_transaction_active = false _construction_committing_notifications = false _construction_queued_domain_events.clear() latest_save_operation_result.clear() latest_restore_safety_report.clear() _initialize_rng_streams() supply_logistics = SupplyLogisticsBridge.new() supply_logistics.configure(self) cleaning_bridge.configure(self) elevators = ElevatorService.new() elevators.configure(path_service) elevators.synchronize([], _clock.absolute_minute) staff_commute = StaffCommuteService.new() staff_commute.configure(self) _executors.clear() register_command_executor(&"change_resources", Callable(self, "_execute_change_resources")) register_command_executor(&"spend_resources", Callable(self, "_execute_spend_resources")) register_command_executor(&"repay_loan_principal", Callable(self, "_execute_repay_loan_principal")) register_command_executor(&"build_facility", Callable(self, "_execute_build_facility")) register_command_executor(&"cancel_construction", Callable(self, "_execute_cancel_construction")) register_command_executor(&"demolish_facility", Callable(self, "_execute_demolish_facility")) for command_type: StringName in [&"peeping_room_validate", &"peeping_room_place", &"peeping_room_relink", &"peeping_room_record_evidence"]: register_command_executor(command_type, Callable(self, "_execute_peeping_room")) register_command_executor(&"set_elevator_operation", Callable(self, "_execute_set_elevator_operation")) register_command_executor(&"start_excavation", Callable(self, "_execute_start_excavation")) register_command_executor(&"pause_excavation", Callable(self, "_execute_pause_excavation")) register_command_executor(&"resume_excavation", Callable(self, "_execute_resume_excavation")) register_command_executor(&"set_world_tree_rank", Callable(self, "_execute_set_world_tree_rank")) register_command_executor(&"update_progression_metrics", Callable(self, "_execute_update_progression_metrics")) register_command_executor(&"complete_progression_quest", Callable(self, "_execute_complete_progression_quest")) register_command_executor(&"acknowledge_operations_pause", Callable(self, "_execute_acknowledge_operations_pause")) register_command_executor(&"purchase_supplies", Callable(self, "_execute_purchase_supplies")) register_command_executor(&"set_supply_auto_purchase", Callable(self, "_execute_set_supply_auto_purchase")) register_command_executor(&"set_kitchen_production", Callable(self, "_execute_set_kitchen_production")) register_command_executor(&"add_inventory_stack", Callable(self, "_execute_add_inventory_stack")) register_command_executor(&"create_logistics_request", Callable(self, "_execute_create_logistics_request")) register_command_executor(&"dispatch_logistics", Callable(self, "_execute_dispatch_logistics")) register_command_executor(&"advance_logistics_request", Callable(self, "_execute_advance_logistics_request")) register_command_executor(&"register_guest_wallet", Callable(self, "_execute_register_guest_wallet")) register_command_executor(&"create_service_request", Callable(self, "_execute_create_service_request")) register_command_executor(&"set_facility_conditions", Callable(self, "_execute_set_facility_conditions")) register_command_executor(&"set_facility_policy", Callable(self, "_execute_set_facility_policy")) register_command_executor(&"leave_facility_queue", Callable(self, "_execute_leave_facility_queue")) register_command_executor(&"spawn_guest", Callable(self, "_execute_spawn_guest")) register_command_executor(&"transition_guest", Callable(self, "_execute_transition_guest")) register_command_executor(&"decide_guest", Callable(self, "_execute_decide_guest")) register_command_executor(&"register_staff", Callable(self, "_execute_register_staff")) register_command_executor(&"hire_staff_candidate", Callable(self, "_execute_hire_staff_candidate")) register_command_executor(&"create_staff_shift", Callable(self, "_execute_create_staff_shift")) register_command_executor(&"set_staff_schedule_override", Callable(self, "_execute_set_staff_schedule_override")) register_command_executor(&"set_staff_work_assignment", Callable(self, "_execute_set_staff_work_assignment")) register_command_executor(&"create_staff_task", Callable(self, "_execute_create_staff_task")) register_command_executor(&"begin_staff_task", Callable(self, "_execute_begin_staff_task")) register_command_executor(&"progress_staff_task", Callable(self, "_execute_progress_staff_task")) register_command_executor(&"create_relationship", Callable(self, "_execute_create_relationship")) register_command_executor(&"register_original_relationship", Callable(self, "_execute_register_original_relationship")) register_command_executor(&"change_relationship", Callable(self, "_execute_change_relationship")) register_command_executor(&"attack_relationship_shield", Callable(self, "_execute_attack_relationship_shield")) register_command_executor(&"request_route_lock", Callable(self, "_execute_request_route_lock")) register_command_executor(&"confirm_route_lock", Callable(self, "_execute_confirm_route_lock")) register_command_executor(&"register_event_definition", Callable(self, "_execute_register_event_definition")) register_command_executor(&"enqueue_event", Callable(self, "_execute_enqueue_event")) register_command_executor(&"reserve_event_choice_cost", Callable(self, "_execute_reserve_event_choice_cost")) register_command_executor(&"submit_event_choice", Callable(self, "_execute_submit_event_choice")) register_command_executor(&"report_authority_incident", Callable(self, "_execute_report_authority_incident")) register_command_executor(&"register_authority_evidence", Callable(self, "_execute_register_authority_evidence")) register_command_executor(&"discover_authority_evidence", Callable(self, "_execute_discover_authority_evidence")) register_command_executor(&"evaluate_authority_inspection", Callable(self, "_execute_evaluate_authority_inspection")) register_command_executor(&"schedule_authority_inspection", Callable(self, "_execute_schedule_authority_inspection")) register_command_executor(&"start_authority_inspection", Callable(self, "_execute_start_authority_inspection")) register_command_executor(&"arrive_authority_inspection", Callable(self, "_execute_arrive_authority_inspection")) register_command_executor(&"plan_authority_inspection", Callable(self, "_execute_plan_authority_inspection")) register_command_executor(&"advance_authority_inspection", Callable(self, "_execute_advance_authority_inspection")) register_command_executor(&"inspect_authority_waypoint", Callable(self, "_execute_inspect_authority_waypoint")) register_command_executor(&"resolve_authority_inspection", Callable(self, "_execute_resolve_authority_inspection")) register_command_executor(&"depart_authority_inspection", Callable(self, "_execute_depart_authority_inspection")) register_command_executor(&"create_defense_raid", Callable(self, "_execute_create_defense_raid")) register_command_executor(&"register_defense_combatant", Callable(self, "_execute_register_defense_combatant")) register_command_executor(&"register_defense_asset", Callable(self, "_execute_register_defense_asset")) register_command_executor(&"start_defense_raid", Callable(self, "_execute_start_defense_raid")) register_command_executor(&"set_defense_retreat_policy", Callable(self, "_execute_set_defense_retreat_policy")) register_command_executor(&"queue_defense_skill", Callable(self, "_execute_queue_defense_skill")) register_command_executor(&"resolve_defense_round", Callable(self, "_execute_resolve_defense_round")) register_command_executor(&"begin_next_defense_wave", Callable(self, "_execute_begin_next_defense_wave")) register_command_executor(&"retreat_defenders", Callable(self, "_execute_retreat_defenders")) register_command_executor(&"cancel_defense_raid", Callable(self, "_execute_cancel_defense_raid")) # Weather commits before upkeep and arrivals, including same-minute retries. _clock.unregister_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.0_weather") _clock.register_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.0_weather", Callable(self, "_advance_weather")) _clock.unregister_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.a_upkeep") _clock.register_tick_handler(_clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.a_upkeep", Callable(self, "_advance_facility_upkeep")) _clock.register_tick_handler( _clock.TickPhase.APPLY_COMMANDS, &"simulation_runtime.commands", Callable(self, "_process_tick_commands") ) _clock.register_tick_handler( _clock.TickPhase.PUBLISH_SNAPSHOT, &"simulation_runtime.snapshot", Callable(self, "_publish_snapshot") ) _clock.register_tick_handler( _clock.TickPhase.MANA, &"simulation_runtime.mana", Callable(self, "_advance_mana") ) _clock.register_tick_handler( _clock.TickPhase.CONSTRUCTION, &"simulation_runtime.construction", Callable(self, "_advance_construction") ) _clock.register_tick_handler( _clock.TickPhase.LOGISTICS, &"simulation_runtime.logistics", Callable(self, "_advance_inventory_and_logistics") ) _clock.register_tick_handler( _clock.TickPhase.FACILITY_COMPLETION, &"simulation_runtime.facilities", Callable(self, "_advance_facilities") ) _clock.register_tick_handler( _clock.TickPhase.VITALS_AND_ENVIRONMENT, &"simulation_runtime.guest_vitals", Callable(self, "_advance_guest_vitals") ) _clock.register_tick_handler( _clock.TickPhase.FACILITY_QUEUES, &"simulation_runtime.guest_queues", Callable(self, "_advance_guest_queues") ) _clock.register_tick_handler( _clock.TickPhase.INCIDENTS_AND_EVENTS, &"simulation_runtime.authority_incidents", Callable(self, "_advance_authority_incidents") ) _clock.register_tick_handler( _clock.TickPhase.INCIDENTS_AND_EVENTS, &"simulation_runtime.defense", Callable(self, "_advance_defense") ) _clock.register_tick_handler( _clock.TickPhase.INCIDENTS_AND_EVENTS, &"simulation_runtime.relationship_events", Callable(self, "_advance_relationship_events") ) _clock.register_tick_handler( _clock.TickPhase.AI_DECISIONS, &"simulation_runtime.staff_ai", Callable(self, "_advance_staff_ai") ) # The `zz` ID deliberately orders persistence after the published simulation # snapshot at the final phase of a fully completed minute. _clock.register_tick_handler( _clock.TickPhase.PUBLISH_SNAPSHOT, &"simulation_runtime.zz_save_checkpoints", Callable(self, "_process_save_checkpoints") ) _initialized = true var report := definition_registry.rebuild_from_paths( FacilityCatalog.FACILITY_PATHS + FacilityCatalog.INFRASTRUCTURE_FACILITY_PATHS + FacilityCatalog.RESCUE_FACILITY_PATHS + FacilityCatalog.ORIGINAL_FACILITY_PATHS + FacilityCatalog.TRANSPORT_FACILITY_PATHS) var catalog_registration_ok := _catalog_definitions_are_registered(guest_service) report[&"content_catalog"] = mvp_content_report.duplicate(true) report[&"progression_initialized"] = initial_progression_ok report[&"catalog_guest_definitions_registered"] = catalog_registration_ok report[&"infrastructure_initialized"] = initial_infrastructure_ok report[&"cleaning_policy_valid"] = not cleaning_policy.create_snapshot().is_empty() report[&"valid"] = bool(report[&"valid"]) \ and bool(mvp_content_report.get(&"valid", false)) \ and catalog_registration_ok \ and initial_progression_ok \ and initial_infrastructure_ok \ and bool(report[&"cleaning_policy_valid"]) \ and int(_state.world_tree_rank) == ProgressionService.MIN_RANK if not report[&"valid"]: _clock.set_paused(true) definition_validation_failed.emit(report) _publish_snapshot(_clock.absolute_minute) return report func register_command_executor(command_type: StringName, executor: Callable) -> bool: if command_type.is_empty() or not executor.is_valid() or _executors.has(command_type): return false _executors[command_type] = executor return true func unregister_command_executor(command_type: StringName) -> void: _executors.erase(command_type) func submit_command( command_type: StringName, payload: Dictionary = {}, actor_id: StringName = &"player", target_id: StringName = &"", priority := 0, execute_at := -1, idempotency_key: StringName = &"" ) -> CommandResult: if not _initialized: return CommandResult.failed(CommandResult.Code.EXECUTOR_FAILED, 0, "Simulation runtime is not initialized.") if _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal(): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, 0, "ゲーム終了後は操作できません。帳簿の確認・保存・読み込みは利用できます。", {&"reason": &"BANKRUPT"} ) var scheduled_tick := _clock.absolute_minute if execute_at < 0 else execute_at var command := SimulationCommand.new( _next_command_id, _clock.absolute_minute, scheduled_tick, priority, command_type, actor_id, target_id, payload, idempotency_key ) var enqueue_result := command_queue.enqueue(command) if not enqueue_result.is_success(): return enqueue_result _next_command_id += 1 if _clock.paused and scheduled_tick <= _clock.absolute_minute: var resume_snapshot_published := _process_commands(_clock.absolute_minute) if not resume_snapshot_published: _publish_snapshot(_clock.absolute_minute) var completed := command_queue.get_processed_result(command.command_id) if completed != null: return completed return enqueue_result func get_rng_stream(stream_id: StringName) -> DeterministicRng: return _rng_streams.get(stream_id) as DeterministicRng func create_snapshot() -> Dictionary: var rng_data := {} for stream_id: StringName in _sorted_name_keys(_rng_streams): var stream: DeterministicRng = _rng_streams[stream_id] rng_data[String(stream_id)] = stream.snapshot() var owned_systems := { &"runtime": { &"runtime_schema_version": RUNTIME_SNAPSHOT_VERSION, &"weather": weather.create_snapshot(), &"world_seed": world_seed, &"next_command_id": _next_command_id, &"staffing_projection_enabled": _staffing_projection_enabled, &"save_lifecycle": { &"version": SAVE_LIFECYCLE_VERSION, &"last_daily_checkpoint_day": _last_daily_checkpoint_day, }, &"command_queue": command_queue.create_snapshot(), &"id_generator": id_generator.create_snapshot(), &"domain_events": domain_events.create_snapshot(), &"rng_streams": rng_data, &"world": world_service.create_snapshot(), &"path": path_service.create_snapshot(), &"movement_credits": _serialize_movement_credits(), &"mana": mana_network_service.create_snapshot(), &"mana_reserve": mana_reserve.create_snapshot(), &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"inventory": inventory_service.create_snapshot(), &"kitchen_production": kitchen_production.create_snapshot(), &"auto_purchase_plans": auto_purchase_plans.create_snapshot(), &"elevators": elevators.create_snapshot(), &"legacy_stairs": legacy_stairs.create_snapshot(), &"logistics_dispatcher": logistics_dispatcher.create_snapshot(), &"logistics_requests": _serialize_logistics_requests(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"guests": guest_service.create_snapshot(), &"staff": staff_service.create_snapshot(), &"catalog_manifest": _create_mvp_content_manifest(mvp_content_catalog), &"progression": progression_service.create_snapshot(), &"hired_staff_candidate_ids": _serialize_hired_staff_candidate_ids(), &"operations": _create_operations_runtime_snapshot(), &"relationships": relationship_service.create_snapshot(), &"event_definitions": _serialize_event_definitions(), &"events": event_service.create_snapshot(), &"authority": authority_service.create_snapshot(), &"defense": defense_service.create_snapshot(), &"construction": construction_service.create_snapshot(), &"excavation": excavation_service.create_snapshot(), &"spatial_subslots": spatial_subslot_service.create_snapshot(), &"route_projection": route_subslot_path_adapter.create_snapshot(), &"facility_infrastructure": facility_infrastructure_projector.create_snapshot(), }, } if PurchaseQuoteV2.has_history(owned_systems[&"runtime"]): owned_systems[&"runtime"][&"runtime_schema_version"] = MIMI_PURCHASE_RUNTIME_SNAPSHOT_VERSION if not peeping_room_service.is_empty(): owned_systems[&"runtime"][&"special_peeping_rooms"] = peeping_room_service.create_snapshot() # Every component above returns detached containers. Transfer this fresh # tree into the envelope instead of deep-copying all component snapshots a # second time. Keep generic capture's caller-owned-extra contract unchanged. var snapshot := SimulationSnapshot.capture(_clock, _state) snapshot[&"systems"] = owned_systems return snapshot func restore_snapshot(snapshot: Dictionary) -> bool: return _restore_snapshot_internal(snapshot, false) func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bool: var active_filter_profile := event_service.get_content_filter_profile() var staff_repair_report := { &"automatic_repairs_applied": false, &"reopened_task_ids": [], &"released_staff_ids": [], &"release_failed_task_ids": [], } var event_lock_repair_report := { &"automatic_repairs_applied": false, &"released_actor_ids": [], &"released_foreground_instance_id": &"", &"reason": &"NONE", } var validation := SimulationSnapshot.validate(snapshot) if not validation[&"ok"]: return false var systems: Variant = snapshot.get(&"systems", {}) if not systems is Dictionary or not systems.has(&"runtime") or not systems[&"runtime"] is Dictionary: return false var runtime_data: Dictionary = systems[&"runtime"] var restored_minute := int(snapshot[&"clock"].get(&"absolute_minute", -1)) if restored_minute < 0: return false var runtime_schema_version_value: Variant = runtime_data.get( &"runtime_schema_version", runtime_data.get("runtime_schema_version", LEGACY_RUNTIME_SNAPSHOT_VERSION) ) if typeof(runtime_schema_version_value) != TYPE_INT: return false var runtime_schema_version := int(runtime_schema_version_value) if runtime_schema_version not in [ LEGACY_RUNTIME_SNAPSHOT_VERSION, DEFENSE_RUNTIME_SNAPSHOT_VERSION, PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION, CONTENT_RUNTIME_SNAPSHOT_VERSION, OPERATIONS_RUNTIME_SNAPSHOT_VERSION, INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION, SUPPLY_RUNTIME_SNAPSHOT_VERSION, TRANSPORT_RUNTIME_SNAPSHOT_VERSION, STAIRS_RUNTIME_SNAPSHOT_VERSION, UPKEEP_RUNTIME_SNAPSHOT_VERSION, WEATHER_RUNTIME_SNAPSHOT_VERSION, CLEANING_RUNTIME_SNAPSHOT_VERSION, EVENT_THREAT_RUNTIME_SNAPSHOT_VERSION, RUNTIME_SNAPSHOT_VERSION, MIMI_PURCHASE_RUNTIME_SNAPSHOT_VERSION, ]: return false var has_purchase_v2_history := PurchaseQuoteV2.has_history(runtime_data) if (runtime_schema_version == MIMI_PURCHASE_RUNTIME_SNAPSHOT_VERSION) != has_purchase_v2_history: return false if runtime_schema_version < OPERATIONS_RUNTIME_SNAPSHOT_VERSION \ and not _legacy_operations_history_is_absent(runtime_data): return false if runtime_schema_version < INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION \ and not _legacy_infrastructure_history_is_absent(runtime_data): return false if runtime_schema_version >= OPERATIONS_RUNTIME_SNAPSHOT_VERSION: if not _runtime_v5_outer_snapshot_shape_is_valid(snapshot): return false var runtime_current_keys: Array[StringName] = [ &"runtime_schema_version", &"world_seed", &"next_command_id", &"staffing_projection_enabled", &"save_lifecycle", &"command_queue", &"id_generator", &"domain_events", &"rng_streams", &"world", &"path", &"movement_credits", &"mana", &"economy", &"inventory", &"logistics_dispatcher", &"logistics_requests", &"facilities", &"guests", &"staff", &"catalog_manifest", &"progression", &"hired_staff_candidate_ids", &"operations", &"relationships", &"event_definitions", &"events", &"authority", &"defense", &"construction", ] if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append_array([ &"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure", ]) if runtime_schema_version >= SUPPLY_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append(&"kitchen_production") if runtime_schema_version >= TRANSPORT_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append(&"elevators") if runtime_schema_version >= STAIRS_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append(&"legacy_stairs") if runtime_schema_version >= UPKEEP_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append_array([&"facility_upkeep", &"upkeep_settlement"]) if runtime_schema_version >= WEATHER_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append(&"weather") if runtime_schema_version >= CLEANING_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append(&"facility_cleanliness") if runtime_schema_version >= AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION: runtime_current_keys.append(&"auto_purchase_plans") if runtime_data.has(&"special_peeping_rooms"): runtime_current_keys.append(&"special_peeping_rooms") if runtime_data.has(&"mana_reserve"): runtime_current_keys.append(&"mana_reserve") if not _dictionary_has_exact_canonical_keys(runtime_data, runtime_current_keys) \ or typeof(runtime_data.get(&"runtime_schema_version", null)) != TYPE_INT \ or typeof(runtime_data.get(&"world_seed", null)) != TYPE_INT \ or typeof(runtime_data.get(&"next_command_id", null)) != TYPE_INT \ or typeof(runtime_data.get(&"staffing_projection_enabled", null)) != TYPE_BOOL: return false var has_defense_snapshot := runtime_data.has(&"defense") or runtime_data.has("defense") if runtime_schema_version >= DEFENSE_RUNTIME_SNAPSHOT_VERSION and not has_defense_snapshot: return false if runtime_schema_version >= PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION \ and not runtime_data.get(&"movement_credits", null) is Array: return false if runtime_schema_version >= CONTENT_RUNTIME_SNAPSHOT_VERSION \ and (not runtime_data.get(&"catalog_manifest", null) is Dictionary \ or not runtime_data.get(&"progression", null) is Dictionary \ or not runtime_data.get(&"hired_staff_candidate_ids", null) is Array): return false if runtime_schema_version >= OPERATIONS_RUNTIME_SNAPSHOT_VERSION \ and not runtime_data.get(&"operations", null) is Dictionary: return false if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION: for infrastructure_key: StringName in [ &"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure", ]: if not runtime_data.get(infrastructure_key, null) is Dictionary: return false if runtime_schema_version < SUPPLY_RUNTIME_SNAPSHOT_VERSION and runtime_data.has(&"kitchen_production"): return false if runtime_schema_version >= SUPPLY_RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"kitchen_production", null) is Dictionary: return false if runtime_schema_version < TRANSPORT_RUNTIME_SNAPSHOT_VERSION and runtime_data.has(&"elevators"): return false if runtime_schema_version >= TRANSPORT_RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"elevators", null) is Dictionary: return false if runtime_schema_version < STAIRS_RUNTIME_SNAPSHOT_VERSION and runtime_data.has(&"legacy_stairs"): return false if runtime_schema_version >= STAIRS_RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"legacy_stairs", null) is Dictionary: return false if runtime_schema_version < AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION and runtime_data.has(&"auto_purchase_plans"): return false if runtime_schema_version >= AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION and not runtime_data.get(&"auto_purchase_plans", null) is Dictionary: return false var restored_catalog := MvpContentCatalog.new() var restored_catalog_report := restored_catalog.rebuild() if not bool(restored_catalog_report.get(&"valid", false)): return false if runtime_schema_version >= CONTENT_RUNTIME_SNAPSHOT_VERSION \ and not _saved_content_manifest_matches(restored_catalog, runtime_data): return false var restored_last_daily_checkpoint_day := -1 var save_lifecycle_value: Variant = runtime_data.get(&"save_lifecycle", {}) if not save_lifecycle_value is Dictionary: return false var save_lifecycle: Dictionary = save_lifecycle_value if not save_lifecycle.is_empty(): if (runtime_schema_version >= OPERATIONS_RUNTIME_SNAPSHOT_VERSION \ and (not _dictionary_has_exact_canonical_keys( save_lifecycle, [&"version", &"last_daily_checkpoint_day"] ) \ or typeof(save_lifecycle.get(&"version", null)) != TYPE_INT \ or typeof(save_lifecycle.get(&"last_daily_checkpoint_day", null)) != TYPE_INT)) \ or int(save_lifecycle.get(&"version", -1)) != SAVE_LIFECYCLE_VERSION: return false restored_last_daily_checkpoint_day = int( save_lifecycle.get(&"last_daily_checkpoint_day", -1) ) if restored_last_daily_checkpoint_day < -1: return false if not runtime_data.get(&"command_queue", null) is Dictionary \ or not runtime_data.get(&"id_generator", null) is Dictionary \ or not runtime_data.get(&"domain_events", null) is Dictionary \ or not runtime_data.get(&"rng_streams", null) is Dictionary \ or not runtime_data.get(&"world", null) is Dictionary \ or not runtime_data.get(&"path", null) is Dictionary \ or not runtime_data.get(&"mana", null) is Dictionary \ or not runtime_data.get(&"economy", null) is Dictionary \ or not runtime_data.get(&"inventory", null) is Dictionary \ or not runtime_data.get(&"logistics_dispatcher", null) is Dictionary \ or not runtime_data.get(&"logistics_requests", null) is Array \ or not runtime_data.get(&"facilities", null) is Dictionary \ or not runtime_data.get(&"guests", null) is Dictionary \ or not runtime_data.get(&"staff", null) is Dictionary \ or not runtime_data.get(&"relationships", null) is Dictionary \ or not runtime_data.get(&"event_definitions", null) is Array \ or not runtime_data.get(&"events", null) is Dictionary \ or not runtime_data.get(&"authority", null) is Dictionary \ or (has_defense_snapshot and not runtime_data.get(&"defense", null) is Dictionary) \ or not runtime_data.get(&"construction", null) is Dictionary: return false var restored_queue := CommandQueue.new() if not restored_queue.restore_snapshot(runtime_data[&"command_queue"]): return false if runtime_schema_version < AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION: # Old saves must not reinterpret an unsupported queued action as a new # paid feature when loading into this release. Failed historical actions # remain ordinary recorded history; only resumable pending work is gated. for row: Dictionary in restored_queue.create_snapshot()["pending"]: if row["command_type"] == "set_supply_auto_purchase": return false var restored_id_generator := StableIdGenerator.new() if not restored_id_generator.restore_snapshot(runtime_data[&"id_generator"]): return false var restored_domain_events := DomainEventStream.new() if not restored_domain_events.restore_snapshot(runtime_data[&"domain_events"]): return false if runtime_schema_version >= OPERATIONS_RUNTIME_SNAPSHOT_VERSION \ and not _runtime_command_history_crosslinks_are_valid( runtime_data[&"command_queue"], int(runtime_data[&"next_command_id"]), restored_domain_events ): return false var restored_world := WorldService.new() if not restored_world.restore_snapshot(runtime_data[&"world"]): return false var restored_path := PathService.new() var restored_spatial := SpatialSubslotService.new() var restored_route_adapter := RouteSubslotPathAdapter.new() var restored_infrastructure_projector := FacilityInfrastructureProjector.new() if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION: # Configure against an empty Spatial source before any managed namespace is # present. Then restore the source, rebuild the base+ROUTE topology, and # finally restore Path traffic/actors and the two metadata owners. if not restored_path.rebuild_graph(restored_world.get_walkable_cells()) \ or not restored_route_adapter.configure( restored_spatial, restored_path, restored_world.get_walkable_cells() ) \ or not restored_infrastructure_projector.configure( restored_route_adapter, restored_spatial, restored_path ) \ or not restored_spatial.restore_snapshot(runtime_data[&"spatial_subslots"]): return false var prepared_projection := restored_route_adapter.synchronize_projection( &"runtime.v6.restore.prepare", restored_world.get_walkable_cells() ) if not prepared_projection.is_success() \ or not _path_snapshot_cells_match_world( runtime_data[&"path"], restored_route_adapter.get_projected_walkable_cells() ) \ or not restored_path.restore_snapshot(runtime_data[&"path"]) \ or not restored_route_adapter.restore_snapshot(runtime_data[&"route_projection"]) \ or not restored_infrastructure_projector.restore_snapshot( runtime_data[&"facility_infrastructure"] ): return false else: if runtime_schema_version >= PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION \ and not _path_snapshot_cells_match_world( runtime_data[&"path"], restored_world.get_walkable_cells() ): return false if not restored_path.restore_snapshot(runtime_data[&"path"]) \ or not restored_path.rebuild_graph(restored_world.get_walkable_cells()) \ or not restored_route_adapter.configure( restored_spatial, restored_path, restored_world.get_walkable_cells() ) \ or not restored_infrastructure_projector.configure( restored_route_adapter, restored_spatial, restored_path ): return false if runtime_schema_version < STAIRS_RUNTIME_SNAPSHOT_VERSION: var legacy_path_data: Dictionary = runtime_data[&"path"] if legacy_path_data.has(&"vertical_walk_requires_connector") or legacy_path_data.has(&"transit_actor_ids"): return false var restored_legacy_stairs := LegacyStairService.new() restored_legacy_stairs.configure(restored_path) if runtime_schema_version >= STAIRS_RUNTIME_SNAPSHOT_VERSION: if runtime_data[&"path"].get(&"schema_version", -1) != PathService.SNAPSHOT_VERSION \ or not restored_path.get_vertical_walk_requires_connector() \ or not restored_legacy_stairs.restore_snapshot(runtime_data[&"legacy_stairs"]): return false else: if restored_path.get_vertical_walk_requires_connector() or not restored_legacy_stairs.migrate_existing_vertical_walks(): return false var restored_mana := ManaNetworkService.new() if not restored_mana.restore_snapshot(runtime_data[&"mana"]): return false # A06: saves without the finite reserve keep an explicit stored amount # (0 stays stopped) only within capacity; nothing is silently truncated. var restored_mana_reserve := ManaReserveCycle.new() var saved_game_resources: Variant = snapshot[&"game_state"].get(&"resources", null) if not saved_game_resources is Dictionary: return false var saved_mana: Variant = saved_game_resources.get(&"mana", saved_game_resources.get("mana", null)) if runtime_data.has(&"mana_reserve"): # Between boundaries Mana may exceed capacity; the next boundary records # and discards the excess, so only a valid integer amount is required. if not ManaReserveCycle.stored_amount_is_valid(saved_mana) \ or not restored_mana_reserve.restore_snapshot(runtime_data[&"mana_reserve"]) \ or restored_mana_reserve.get_minute() > restored_minute: return false elif not ManaReserveCycle.amount_is_valid(saved_mana) \ or not restored_mana_reserve.start_at(restored_minute): return false var restored_economy := EconomyService.new() if not restored_economy.restore_snapshot(runtime_data[&"economy"]): return false if not _loan_principal_ledger_is_valid(restored_economy): return false if runtime_schema_version < OPERATIONS_RUNTIME_SNAPSHOT_VERSION: if not restored_economy.has_account(WAGE_EXPENSE_ACCOUNT) \ and not restored_economy.register_account(WAGE_EXPENSE_ACCOUNT): return false elif not restored_economy.has_account(WAGE_EXPENSE_ACCOUNT): return false var restored_upkeep := FacilityUpkeepAccrual.new() var restored_upkeep_settlement := FacilityUpkeepSettlement.new() if runtime_schema_version >= UPKEEP_RUNTIME_SNAPSHOT_VERSION: if not runtime_data.get(&"facility_upkeep") is Dictionary or not runtime_data.get(&"upkeep_settlement") is Dictionary: return false if not restored_upkeep.restore_snapshot(runtime_data[&"facility_upkeep"]) \ or restored_upkeep.get_current_minute() != restored_minute \ or not restored_upkeep_settlement.restore_snapshot(runtime_data[&"upkeep_settlement"], restored_economy): return false if not _upkeep_accrual_crosslinks_are_valid(restored_upkeep, restored_upkeep_settlement): return false else: if runtime_data.has(&"facility_upkeep") or runtime_data.has(&"upkeep_settlement") \ or restored_economy.has_account(FacilityUpkeepSettlement.EXPENSE) \ or restored_economy.has_account(FacilityUpkeepSettlement.PAYABLE): return false if not FacilityUpkeepSettlement.prepare_accounts(restored_economy) or not restored_upkeep.start_at(restored_minute)[&"ok"]: return false var saved_resources: Variant = snapshot[&"game_state"].get(&"resources", null) if not saved_resources is Dictionary \ or int(saved_resources.get(&"gold", saved_resources.get("gold", -1))) \ != restored_economy.get_balance(PLAYER_CASH_ACCOUNT): return false var restored_inventory := InventoryService.new() if not restored_inventory.restore_snapshot(runtime_data[&"inventory"]): return false var restored_dispatcher := LogisticsDispatcher.new() if not restored_dispatcher.restore_snapshot(runtime_data[&"logistics_dispatcher"]): return false var restored_logistics: Variant = _deserialize_logistics_requests(runtime_data[&"logistics_requests"]) if restored_logistics == null: return false if restored_dispatcher.restored_snapshot_version == LogisticsDispatcher.LEGACY_SNAPSHOT_VERSION: if runtime_schema_version >= PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION \ or not restored_dispatcher.migrate_legacy_assignment_ownership( _derive_legacy_logistics_ownership( restored_dispatcher, restored_logistics, restored_minute ) ): return false if not _validate_logistics_dispatcher_crosslinks( restored_dispatcher, restored_logistics, restored_minute ): return false var restored_facilities := FacilityService.new() if not restored_facilities.restore_snapshot(runtime_data[&"facilities"]): return false var restored_guests := GuestService.new() if not restored_guests.restore_snapshot(runtime_data[&"guests"]): return false if runtime_schema_version >= CONTENT_RUNTIME_SNAPSHOT_VERSION: if not _saved_catalog_guest_definitions_match( runtime_data[&"guests"], restored_catalog ): return false elif not _register_or_validate_legacy_catalog_definitions( restored_guests, restored_catalog ): return false var restored_staff := StaffService.new() if recovery_mode: restored_staff.configure_callbacks( Callable(), func( reservation_ids: Array[StringName], task: StaffTask, absolute_minute: int ) -> Dictionary: return _release_recovery_task_items( restored_inventory, reservation_ids, task, absolute_minute ), Callable() ) var restored_staff_ok := ( restored_staff.restore_snapshot_with_orphan_recovery(runtime_data[&"staff"]) if recovery_mode else restored_staff.restore_snapshot(runtime_data[&"staff"]) ) if not restored_staff_ok: return false if recovery_mode: staff_repair_report = restored_staff.last_recovery_repair_report.duplicate(true) if not staff_repair_report[&"release_failed_task_ids"].is_empty(): return false var saved_rank_value: Variant = snapshot[&"game_state"].get(&"world_tree_rank", null) if not saved_rank_value is int: return false var saved_rank := int(saved_rank_value) var restored_progression := ProgressionService.new() if runtime_schema_version >= CONTENT_RUNTIME_SNAPSHOT_VERSION: if not restored_progression.restore_snapshot(runtime_data[&"progression"]): return false else: # Pre-v4 saves never recorded promotion evidence. Rank 1 can be migrated # from authoritative live facts; a later rank would require guessing which # requirements and quests were actually satisfied, so it is rejected. if saved_rank != ProgressionService.MIN_RANK: return false var legacy_guest_lifetime := restored_guests.guest_count() \ + int((runtime_data[&"guests"] as Dictionary)[&"persistent_records"].size()) if not restored_progression.configure(ProgressionService.MIN_RANK, { &"active_above_ground_facilities": _count_active_facilities(restored_facilities, false), &"active_underground_facilities": _count_active_facilities(restored_facilities, true), &"asset_gold": restored_economy.get_balance(PLAYER_CASH_ACCOUNT), &"faith_lifetime": maxi(int(saved_resources.get(&"faith", saved_resources.get("faith", 0))), 0), &"guest_lifetime": legacy_guest_lifetime, }): return false if saved_rank != restored_progression.current_rank \ or not _strict_variants_equal( restored_progression.get_rank_bounds(saved_rank), restored_world.get_bounds() ) \ or not _progression_guest_lifetime_matches( restored_progression, restored_guests ): return false var restored_hired_staff_candidates: Variant if runtime_schema_version >= CONTENT_RUNTIME_SNAPSHOT_VERSION: restored_hired_staff_candidates = _deserialize_hired_staff_candidate_ids( runtime_data[&"hired_staff_candidate_ids"], restored_catalog ) if restored_hired_staff_candidates == null: return false else: restored_hired_staff_candidates = _derive_legacy_hired_staff_candidate_ids( restored_staff, runtime_data[&"staff"], restored_catalog ) if not _validate_catalog_staff_crosslinks( restored_staff, restored_hired_staff_candidates, restored_catalog ): return false var restored_operations_state := _parse_operations_runtime_snapshot( runtime_data.get(&"operations", {}), runtime_schema_version, restored_minute, restored_guests, restored_staff, restored_economy, int(runtime_data.get(&"world_seed", DEFAULT_WORLD_SEED)), snapshot[&"clock"].get(&"paused", false), restored_upkeep_settlement ) if not bool(restored_operations_state.get(&"ok", false)): return false if bool(restored_operations_state.get(&"enabled", false)): var restored_coordinator: OperationsCoordinator = restored_operations_state[&"coordinator"] var expected_minimum := 0 var expected_maximum := 0 for period: Dictionary in restored_upkeep.get_report()[&"closed_periods"]: if int(period[&"period_end"]) <= restored_coordinator.current_minute: expected_minimum += 1 if int(period[&"period_end"]) <= restored_minute: expected_maximum += 1 var actual_count := restored_upkeep_settlement.get_settled_period_count() if actual_count < expected_minimum or actual_count > expected_maximum: return false var restored_movement_credits: Variant if runtime_schema_version >= PATH_TRAFFIC_RUNTIME_SNAPSHOT_VERSION: restored_movement_credits = _deserialize_movement_credits( runtime_data[&"movement_credits"], restored_minute ) if restored_movement_credits == null: return false else: restored_movement_credits = _rebuild_legacy_path_actor_projection( restored_path, restored_guests, restored_staff, restored_minute ) if restored_movement_credits == null: return false if not _validate_runtime_path_crosslinks( restored_path, restored_guests, restored_staff, restored_movement_credits, restored_minute ): return false var restored_relationships := RelationshipService.new() restored_relationships.configure_original_identity_validator( func(payload: Dictionary) -> bool: return _original_relationship_identities_valid(payload, restored_staff, restored_catalog) ) if not restored_relationships.restore_snapshot(runtime_data[&"relationships"]): return false var restored_events := EventService.new() for raw_definition: Variant in runtime_data[&"event_definitions"]: var definition := EventDefinition.from_variant(raw_definition) if definition == null or not restored_events.register_definition(definition).is_success(): return false if not bool(restored_events.validate_definitions().get(&"ok", false)): return false var restored_events_ok := ( restored_events.restore_snapshot_with_lock_recovery(runtime_data[&"events"]) if recovery_mode else restored_events.restore_snapshot(runtime_data[&"events"]) ) if not restored_events_ok: return false if recovery_mode: event_lock_repair_report = restored_events.last_recovery_repair_report.duplicate(true) var saved_filter_profile := restored_events.get_content_filter_profile() var effective_filter_profile := _merge_content_filter_profiles( active_filter_profile, saved_filter_profile ) restored_events.configure_content_filter( effective_filter_profile[&"filter_mode"], effective_filter_profile[&"maximum_content_rating"], effective_filter_profile[&"disabled_content_tags"] ) var restored_authority := AuthorityService.new() if not restored_authority.restore_snapshot(runtime_data[&"authority"]): return false var restored_defense := DefenseService.new() if has_defense_snapshot: if not restored_defense.restore_snapshot(runtime_data[&"defense"]): return false else: # Runtime snapshots predating the Defense aggregate migrate to an empty # aggregate at the saved clock. No external entity or consequence is # synthesized by this compatibility path. if not restored_defense.advance_time(restored_minute).is_success(): return false var restored_staffing_projection := bool( runtime_data.get(&"staffing_projection_enabled", false) ) var restored_runtime_domain_minute := restored_minute if bool(restored_operations_state.get(&"enabled", false)): var restored_operations_coordinator: OperationsCoordinator = ( restored_operations_state.get(&"coordinator", null) ) if int(restored_operations_state.get(&"deferred_runtime_minute", -1)) \ == restored_minute \ or (restored_operations_coordinator != null \ and restored_operations_coordinator.is_terminal()): restored_runtime_domain_minute = restored_minute - 1 if not _validate_runtime_crosslinks( restored_facilities, restored_guests, restored_economy, restored_inventory, restored_staff, restored_runtime_domain_minute, restored_staffing_projection ): return false if restored_authority.current_minute < restored_runtime_domain_minute \ or restored_authority.current_minute > restored_minute \ or restored_defense.current_minute < restored_runtime_domain_minute \ or restored_defense.current_minute > restored_minute: return false if not _validate_authority_economy_crosslinks( restored_authority, restored_economy, restored_authority.current_minute ): return false if not _validate_defense_crosslinks( restored_defense, restored_staff, restored_facilities, restored_defense.current_minute ): return false if not _validate_event_defense_crosslinks(restored_events, restored_defense): return false var restored_construction_resource := _create_resource_adapter( restored_economy, CONSTRUCTION_EXPENSE_ACCOUNT, &"CONSTRUCTION", &"CONSTRUCTION_REFUND", &"construction_service" ) var restored_construction := ConstructionService.new(restored_world, restored_construction_resource) if not restored_construction.restore_snapshot(runtime_data[&"construction"]): return false var restored_excavation := ExcavationService.new( restored_world, restored_construction_resource ) if runtime_schema_version >= INFRASTRUCTURE_RUNTIME_SNAPSHOT_VERSION: if not restored_excavation.restore_snapshot(runtime_data[&"excavation"]): return false else: restored_excavation.current_minute = restored_construction.current_minute if not _migrate_legacy_infrastructure_proxies( restored_construction, restored_facilities, restored_infrastructure_projector ) or not restored_progression.update_metrics({ &"active_above_ground_facilities": _count_active_facilities( restored_facilities, false ), &"active_underground_facilities": _count_active_facilities( restored_facilities, true ), &"asset_gold": restored_economy.get_balance(PLAYER_CASH_ACCOUNT), }) or not _validate_runtime_crosslinks( restored_facilities, restored_guests, restored_economy, restored_inventory, restored_staff, restored_runtime_domain_minute, restored_staffing_projection ): return false if not _validate_construction_facility_crosslinks( restored_world, restored_construction, restored_facilities, restored_infrastructure_projector ) or not _validate_infrastructure_crosslinks( restored_world, restored_path, restored_construction, restored_construction_resource, restored_excavation, restored_spatial, restored_route_adapter, restored_infrastructure_projector ): return false # Older saves used logical attendance. Migrate their staff capacity once; # new saves must already agree with physical workplace positions. for facility: FacilityInstance in restored_facilities.get_instances(): var present_ids: Array[StringName] = [] for staff_id: StringName in facility.active_staff_ids: var worker := restored_staff.get_staff(staff_id) if not StringName(restored_staff.get_work_assignment(staff_id).get(&"facility_id", &"")).is_empty() and not StaffCommuteService.is_at_facility(worker, facility) and not CleaningBridge.is_deployed(worker, facility, restored_staff): if runtime_schema_version >= TRANSPORT_RUNTIME_SNAPSHOT_VERSION: return false if worker.role_id != &"porter" and worker.current_task_id.is_empty(): worker.state = StaffRuntime.State.COMMUTING else: present_ids.append(staff_id) if present_ids != facility.active_staff_ids: restored_facilities.set_operational_status(facility.instance_id, {&"active_staff_ids": present_ids}, restored_runtime_domain_minute) var restored_elevators := ElevatorService.new() restored_elevators.configure(restored_path) if runtime_schema_version >= TRANSPORT_RUNTIME_SNAPSHOT_VERSION: if not restored_elevators.restore_snapshot(runtime_data[&"elevators"]): return false else: if not bool(restored_elevators.synchronize([], restored_runtime_domain_minute).get(&"ok", false)): return false if not _validate_elevator_crosslinks(restored_elevators, restored_path, restored_construction, restored_guests, restored_staff, restored_runtime_domain_minute): return false if not WarpService.connectors_match(restored_path, restored_construction.get_facilities()): return false for facility: FacilityInstance in restored_facilities.get_instances(): for staff_id: StringName in facility.active_staff_ids: if not restored_elevators.get_actor_trip(_staff_path_actor_id(staff_id)).is_empty() and not CleaningBridge.is_deployed(restored_staff.get_staff(staff_id), facility, restored_staff): return false var restored_kitchen := KitchenProductionService.new() restored_kitchen.configure(restored_inventory, restored_facilities, definition_registry, restored_runtime_domain_minute) if runtime_schema_version >= SUPPLY_RUNTIME_SNAPSHOT_VERSION: if not restored_kitchen.restore_snapshot(runtime_data[&"kitchen_production"]): return false if restored_kitchen.current_minute != restored_runtime_domain_minute or not restored_kitchen.validate_crosslinks(): return false if runtime_schema_version == MIMI_PURCHASE_RUNTIME_SNAPSHOT_VERSION \ and not PurchaseQuoteV2.valid_history(runtime_data, restored_economy): return false var restored_auto_purchase := AutoPurchasePlansType.new() if runtime_schema_version >= AUTO_PURCHASE_RUNTIME_SNAPSHOT_VERSION: if not restored_auto_purchase.restore_snapshot(runtime_data[&"auto_purchase_plans"], restored_facilities, restored_economy, restored_runtime_domain_minute): return false if not SupplyLogisticsBridge.validate_crosslinks(restored_logistics, restored_staff, restored_inventory, restored_facilities): return false var restored_weather := WeatherTimeline.new() if runtime_schema_version >= WEATHER_RUNTIME_SNAPSHOT_VERSION: if not runtime_data.get(&"weather") is Dictionary or not restored_weather.restore_snapshot(runtime_data[&"weather"]) or restored_weather.get_minute() != restored_minute: return false elif runtime_data.has(&"weather") or not restored_weather.initialize(&"clear", restored_minute): return false var restored_cleanliness := FacilityCleanliness.new() if runtime_schema_version >= CLEANING_RUNTIME_SNAPSHOT_VERSION: if not runtime_data.get(&"facility_cleanliness") is Dictionary or not restored_cleanliness.restore_snapshot(runtime_data[&"facility_cleanliness"]): return false else: if runtime_data.has(&"facility_cleanliness"): return false var policy_values := cleaning_policy.create_snapshot() for facility: FacilityInstance in restored_facilities.get_instances(): if not restored_cleanliness.register_facility(facility.instance_id, policy_values["initial_cleanliness"], policy_values["maximum_cleanliness"]): return false if not _validate_cleanliness_crosslinks(restored_cleanliness, restored_facilities, restored_staff, restored_path): return false var restored_world_seed := int(runtime_data.get(&"world_seed", DEFAULT_WORLD_SEED)) var restored_streams := {} var saved_streams: Dictionary = runtime_data[&"rng_streams"] for stream_key: Variant in saved_streams: if not saved_streams[stream_key] is Dictionary: return false var normalized_stream_id := StringName(stream_key) if normalized_stream_id.is_empty() or restored_streams.has(normalized_stream_id): return false var stream := DeterministicRng.from_root_seed(0, normalized_stream_id) as DeterministicRng if stream == null or not stream.restore(saved_streams[stream_key]) \ or stream.get_stream_id() != normalized_stream_id \ or stream.get_root_seed() != restored_world_seed: return false restored_streams[normalized_stream_id] = stream if not restored_streams.has(&"defense"): if has_defense_snapshot: return false restored_streams[&"defense"] = DeterministicRng.from_root_seed(restored_world_seed, &"defense") if runtime_schema_version >= DEFENSE_RUNTIME_SNAPSHOT_VERSION: for required_stream_id: StringName in RNG_STREAM_IDS: if not restored_streams.has(required_stream_id): return false var restored_next_id := int(runtime_data.get(&"next_command_id", 0)) if restored_next_id <= 0: return false var restored_peeping_rooms := PeepingRooms.new() restored_peeping_rooms.configure(restored_world, restored_facilities, restored_construction, restored_path, restored_id_generator) if runtime_data.has(&"special_peeping_rooms"): if not runtime_data[&"special_peeping_rooms"] is Dictionary: return false for raw_key: Variant in runtime_data: if raw_key == &"special_peeping_rooms" and typeof(raw_key) != TYPE_STRING_NAME: return false if not restored_peeping_rooms.restore_section(runtime_data.get(&"special_peeping_rooms", null), restored_minute): return false if not SimulationSnapshot.restore(snapshot, _clock, _state): return false world_seed = restored_world_seed _next_command_id = restored_next_id _staffing_projection_enabled = restored_staffing_projection command_queue = restored_queue id_generator = restored_id_generator peeping_room_service = restored_peeping_rooms domain_events = restored_domain_events _rng_streams = restored_streams world_service = restored_world path_service = restored_path _movement_credits = (restored_movement_credits as Dictionary).duplicate(true) _connect_path_performance_signal() mana_network_service = restored_mana mana_reserve = restored_mana_reserve weather = restored_weather facility_cleanliness = restored_cleanliness facility_upkeep = restored_upkeep upkeep_settlement = restored_upkeep_settlement economy_service = restored_economy inventory_service = restored_inventory kitchen_production = restored_kitchen auto_purchase_plans = restored_auto_purchase logistics_dispatcher = restored_dispatcher logistics_requests = restored_logistics facility_service = restored_facilities guest_service = restored_guests staff_service = restored_staff elevators = restored_elevators legacy_stairs = restored_legacy_stairs staff_commute.configure(self) supply_logistics.configure(self) cleaning_bridge.configure(self) mvp_content_catalog = restored_catalog _clear_mvp_content_manifest_cache() mvp_content_report = restored_catalog_report.duplicate(true) progression_service = restored_progression _hired_staff_candidate_ids = (restored_hired_staff_candidates as Dictionary).duplicate(true) operations_coordinator = restored_operations_state.get(&"coordinator", null) _operations_enabled = bool(restored_operations_state[&"enabled"]) _personal_action_points = int(restored_operations_state[&"personal_action_points"]) _operations_runtime_failure_latched = bool(restored_operations_state[&"runtime_failure_latched"]) _operations_runtime_failure_report = ( restored_operations_state[&"runtime_failure_report"] as Dictionary ).duplicate(true) _operations_tick_blocked = _operations_should_block_tick() _operations_deferred_runtime_minute = int( restored_operations_state[&"deferred_runtime_minute"] ) _operations_resume_publish_pending = false _operations_test_boundary_hooks.clear() _operations_transaction_active = false _operations_queued_domain_events.clear() _operations_queued_guest_spawn_notifications.clear() _operations_flushing_guest_spawn_notifications.clear() _operations_queued_economy_notifications.clear() _operations_flushing_economy_notifications.clear() relationship_service = restored_relationships event_service = restored_events authority_service = restored_authority _detach_defense_service(defense_service) defense_service = restored_defense _reset_defense_round_attempts() construction_resource_state = restored_construction_resource operating_resource_state = _create_resource_adapter( economy_service, OPERATING_EXPENSE_ACCOUNT, &"OPERATING_EXPENSE", &"OPERATING_REFUND", &"simulation_runtime" ) construction_service = restored_construction excavation_service = restored_excavation spatial_subslot_service = restored_spatial route_subslot_path_adapter = restored_route_adapter facility_infrastructure_projector = restored_infrastructure_projector _infrastructure_failure_latched = false _infrastructure_failure_report.clear() _construction_completion_transaction_active = false _construction_committing_notifications = false _construction_queued_domain_events.clear() _last_daily_checkpoint_day = restored_last_daily_checkpoint_day _pending_checkpoints.clear() _next_checkpoint_request_id = 1 _connect_economy_signals() _configure_facility_boundaries() _connect_facility_signals() _configure_staff_boundaries() _connect_guest_signals() _connect_staff_signals() _configure_relationship_event_boundaries() _connect_relationship_signals() _connect_event_signals() _configure_authority_boundaries() _connect_authority_signals() _configure_defense_boundaries() _connect_defense_signals() _connect_construction_signals() var excavation_callback := Callable(self, "_on_excavation_completed") if not excavation_service.excavation_completed.is_connected(excavation_callback): excavation_service.excavation_completed.connect(excavation_callback) if _operations_enabled: _configure_operations_callbacks() _register_operations_tick_handler() else: _clock.unregister_tick_handler( _clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.operations" ) latest_restore_safety_report = { &"report_version": 1, &"recovery_mode": bool(recovery_mode), &"repair_policy": &"WHITELISTED_AUTHORITATIVE_ONLY", &"automatic_repairs_applied": ( bool(staff_repair_report[&"automatic_repairs_applied"]) or bool(event_lock_repair_report[&"automatic_repairs_applied"]) ), &"repairs": { &"orphan_tasks": staff_repair_report, &"orphan_event_locks": event_lock_repair_report, }, &"content_filter": _content_filter_restore_report( active_filter_profile, saved_filter_profile, effective_filter_profile ), &"forbidden_repairs_applied": false, &"forbidden_repair_categories": [ &"synthesize_gold", &"delete_facilities", &"rewrite_ledger", &"guess_route_locks", ], } _publish_snapshot(_clock.absolute_minute) return true func configure_save_policy(overrides: Dictionary) -> Dictionary: var configured := _save_policy.duplicate(true) if overrides.has(&"enabled"): configured[&"enabled"] = bool(overrides[&"enabled"]) if overrides.has(&"daily_checkpoint_enabled"): configured[&"daily_checkpoint_enabled"] = bool(overrides[&"daily_checkpoint_enabled"]) if overrides.has(&"auto_migrate"): configured[&"auto_migrate"] = bool(overrides[&"auto_migrate"]) if overrides.has(&"autosave_path"): var autosave_path := str(overrides[&"autosave_path"]) if not _save_policy_path_is_safe(autosave_path): return { &"ok": false, &"code": &"INVALID_AUTOSAVE_PATH", &"message": autosave_path, } configured[&"autosave_path"] = autosave_path if overrides.has(&"autosave_generations"): var generations := int(overrides[&"autosave_generations"]) if generations < 1 or generations > SaveService.MAX_AUTOSAVE_GENERATIONS: return { &"ok": false, &"code": &"INVALID_AUTOSAVE_GENERATIONS", &"message": str(generations), } configured[&"autosave_generations"] = generations _save_policy = configured return { &"ok": true, &"code": &"OK", &"policy": get_save_policy(), } func enable_production_save_policy() -> Dictionary: return configure_save_policy({ &"enabled": true, &"daily_checkpoint_enabled": true, &"autosave_path": DEFAULT_AUTOSAVE_PATH, }) func get_save_policy() -> Dictionary: return _save_policy.duplicate(true) func get_last_daily_checkpoint_day() -> int: return _last_daily_checkpoint_day func get_pending_checkpoint_count() -> int: return _pending_checkpoints.size() func get_latest_restore_safety_report() -> Dictionary: return latest_restore_safety_report.duplicate(true) func enable_production_operations() -> Dictionary: if not _initialized or _clock == null or _state == null: return _operations_api_failure(&"RUNTIME_NOT_INITIALIZED", "Runtime is not initialized.") if _operations_enabled: return _operations_api_failure(&"ALREADY_ENABLED", "Production operations are already enabled.") if _clock.absolute_minute != OPERATIONS_START_MINUTE \ or _clock.day != 1 or _clock.minute_of_day != OPERATIONS_START_MINUTE: return _operations_api_failure( &"INVALID_START_TIME", "Production operations must start at Day 1 06:00." ) if not bool(mvp_content_report.get(&"valid", false)): return _operations_api_failure(&"CONTENT_INVALID", "MVP content is unavailable.") if _staff_payroll_arrears_total(staff_service) != 0: return _operations_api_failure( &"INITIAL_ARREARS_UNSUPPORTED", "Production operations cannot infer pre-existing payroll arrears." ) if not (staff_service.create_snapshot().get(&"payroll_settlements", []) as Array).is_empty(): return _operations_api_failure( &"INITIAL_PAYROLL_HISTORY_UNSUPPORTED", "Production operations require a fresh Day 1 payroll history." ) if _guest_aggregate_has_reserved_operations_id(guest_service): return _operations_api_failure( &"RESERVED_GUEST_ID_PRESENT", "Production operations require the guest-auto namespace to be unused." ) var coordinator := OperationsCoordinator.new({ &"start_minute": OPERATIONS_COORDINATOR_ORIGIN_MINUTE, &"world_seed": world_seed, }) coordinator.configure_callbacks( Callable(self, "_operations_daily_boundary"), Callable(self, "_operations_arrival_boundary"), Callable(self, "_operations_morning_boundary"), Callable(self, "_operations_midnight_upkeep_boundary") ) var demand_configuration := _configure_default_operations_demand(coordinator) if not demand_configuration.is_success(): return _operations_api_failure( &"DEMAND_CONFIGURATION_FAILED", demand_configuration.message, demand_configuration.details ) var population := coordinator.set_population_count(guest_service.guest_count()) if not population.is_success(): return _operations_api_failure( &"INITIAL_POPULATION_INVALID", population.message, population.details ) var previous_coordinator := operations_coordinator var previous_ap := _personal_action_points var enable_external_before := _capture_operations_external_state() if enable_external_before.is_empty(): return _operations_api_failure( &"INITIAL_CAPTURE_FAILED", "Production Operations could not capture the initial Runtime state." ) operations_coordinator = coordinator _personal_action_points = 0 var advanced := coordinator.advance_to(OPERATIONS_START_MINUTE, true) if not advanced.is_success() \ or coordinator.current_minute != _clock.absolute_minute \ or coordinator.get_population_count() != guest_service.guest_count() \ or coordinator.get_arrears_total() != _operations_closed_arrears_total() \ or not _operations_notifications_are_committable() \ or not _commit_operations_domain_events() \ or not _commit_operations_notifications(): coordinator.discard_deferred_notifications() _restore_operations_external_state(enable_external_before) _discard_operations_domain_events() operations_coordinator = previous_coordinator _personal_action_points = previous_ap return _operations_api_failure( &"INITIAL_BOUNDARY_FAILED", advanced.message if not advanced.is_success() else "Initial Operations cross-links failed.", advanced.details ) _operations_enabled = true _operations_tick_blocked = false _operations_runtime_failure_latched = false _operations_runtime_failure_report.clear() _register_operations_tick_handler() _pause_for_waiting_foreground_event() _publish_snapshot(_clock.absolute_minute) return { &"ok": true, &"code": &"OK", &"enabled": true, &"current_minute": coordinator.current_minute, &"personal_action_points": _personal_action_points, } func acknowledge_operations_pause() -> Dictionary: if not _operations_enabled or operations_coordinator == null: return _operations_api_failure(&"OPERATIONS_DISABLED", "Production operations are disabled.") if operations_coordinator.is_terminal(): return _operations_api_failure(&"TERMINAL_STATE", "Bankruptcy cannot be acknowledged away.") if operations_coordinator.is_external_quarantined(): return _operations_api_failure( &"EXTERNAL_ROLLBACK_FAILED", "A verified snapshot restore is required." ) if _operations_deferred_runtime_minute < 0: return _operations_api_failure( &"NO_DEFERRED_RUNTIME_MINUTE", "Operations have no deferred Runtime phase suffix." ) if _operations_deferred_runtime_minute != _clock.absolute_minute: return _operations_api_failure( &"DEFERRED_MINUTE_MISMATCH", "The deferred Operations minute no longer matches GameClock." ) var had_runtime_failure := _operations_runtime_failure_latched var acknowledged := operations_coordinator.acknowledge_pause() if not acknowledged.is_success(): return _operations_api_failure(&"ACKNOWLEDGE_FAILED", acknowledged.message, acknowledged.details) var deferred_minute := _operations_deferred_runtime_minute var deferred := operations_coordinator.advance_to(deferred_minute, true) if not deferred.is_success(): var partial_commit_ok := _commit_operations_domain_events() \ and _commit_operations_notifications() if not partial_commit_ok: operations_coordinator.discard_deferred_notifications() _discard_operations_domain_events() _operations_runtime_failure_latched = true _operations_runtime_failure_report = deferred.to_dictionary() _operations_tick_blocked = true _clock.set_paused(true) return _operations_api_failure(&"RETRY_FAILED", deferred.message, deferred.details) if operations_coordinator.current_minute != deferred_minute \ or operations_coordinator.get_population_count() != guest_service.guest_count() \ or operations_coordinator.get_arrears_total() != _operations_closed_arrears_total() \ or not _operations_notifications_are_committable() \ or not _commit_operations_domain_events() \ or not _commit_operations_notifications(): operations_coordinator.discard_deferred_notifications() _discard_operations_domain_events() _operations_runtime_failure_latched = true _operations_runtime_failure_report = { &"code": &"OPERATIONS_CROSSLINK_MISMATCH", &"minute": deferred_minute, } _operations_tick_blocked = true _clock.set_paused(true) return _operations_api_failure( &"RETRY_CROSSLINK_FAILED", "Operations retry did not restore Runtime cross-links." ) _operations_runtime_failure_latched = false _operations_runtime_failure_report.clear() if operations_coordinator.is_terminal(): _operations_deferred_runtime_minute = -1 _operations_tick_blocked = true _clock.set_paused(true) _publish_snapshot(deferred_minute) return { &"ok": true, &"code": &"TERMINAL_STATE", &"had_runtime_failure": had_runtime_failure, &"suffix_replayed": false, } if operations_coordinator.pause_requested: _operations_tick_blocked = true _clock.set_paused(true) return _operations_api_failure( &"PAUSE_REMAINS", "Operations still require acknowledgement before Runtime phases can resume." ) _operations_deferred_runtime_minute = -1 _operations_tick_blocked = false _clock.set_paused(false) _run_operations_deferred_suffix(deferred_minute) _pause_for_waiting_foreground_event() if operations_coordinator.get_population_count() != guest_service.guest_count() \ or operations_coordinator.get_arrears_total() != _operations_closed_arrears_total(): _operations_runtime_failure_latched = true _operations_runtime_failure_report = { &"code": &"DEFERRED_SUFFIX_CROSSLINK_MISMATCH", &"minute": deferred_minute, } _operations_tick_blocked = true _clock.set_paused(true) return _operations_api_failure( &"SUFFIX_CROSSLINK_FAILED", "Deferred Runtime phases ended with invalid Operations cross-links." ) return { &"ok": true, &"code": &"OK", &"had_runtime_failure": had_runtime_failure, &"coordinator": acknowledged.to_dictionary(), &"suffix_replayed": true, &"deferred_runtime_minute": deferred_minute, } func _run_operations_deferred_suffix(absolute_minute: int) -> void: _advance_mana(absolute_minute) _advance_construction(absolute_minute) _advance_inventory_and_logistics(absolute_minute) _advance_facilities(absolute_minute) _advance_guest_vitals(absolute_minute) _advance_authority_incidents(absolute_minute) _advance_defense(absolute_minute) _advance_relationship_events(absolute_minute) _advance_guest_queues(absolute_minute) _advance_staff_ai(absolute_minute) if _active_command_minute >= 0: _operations_resume_publish_pending = true else: _publish_snapshot(absolute_minute) _process_save_checkpoints(absolute_minute) func get_operations_dashboard_report() -> Dictionary: return {&"enabled":_operations_enabled, &"arrears_total":_operations_total_arrears(), &"tick_blocked":_operations_tick_blocked, &"runtime_failure_latched":_operations_runtime_failure_latched, &"personal_action_points":_personal_action_points, &"deferred_runtime_minute":_operations_deferred_runtime_minute, &"coordinator":operations_coordinator.get_dashboard_summary() if operations_coordinator != null else {}} func get_operations_report() -> Dictionary: return { &"enabled": _operations_enabled, &"arrears_total": _operations_total_arrears(), &"tick_blocked": _operations_tick_blocked, &"runtime_failure_latched": _operations_runtime_failure_latched, &"runtime_failure_report": _operations_runtime_failure_report.duplicate(true), &"deferred_runtime_minute": _operations_deferred_runtime_minute, &"personal_action_points": _personal_action_points, &"coordinator": ( operations_coordinator.create_snapshot() if operations_coordinator != null else {} ), } func set_operations_test_boundary_hook(operation: StringName, callback: Callable) -> bool: # Test-only fault injection is invoked at PREPARE and, for arrivals, once at # AFTER_FIRST_MUTATION. The latter proves staged service notifications are # discarded when a real partial apply is rolled back. if operation not in [&"DAILY_CLOSE", &"ARRIVAL_PLAN", &"MORNING_BOUNDARY", &"MIDNIGHT_UPKEEP"]: return false if callback.is_valid(): _operations_test_boundary_hooks[operation] = callback else: _operations_test_boundary_hooks.erase(operation) return true func _run_operations_test_boundary_hook( operation: StringName, payload: Dictionary, phase: StringName, token: Variant ) -> Dictionary: var callback: Callable = _operations_test_boundary_hooks.get(operation, Callable()) if not callback.is_valid(): return {&"ok": true} var returned: Variant = callback.call(payload.duplicate(true), phase, token) if returned is bool: return {&"ok": bool(returned), &"reason": &"TEST_HOOK_REJECTED"} if returned is Dictionary: var result: Dictionary = returned if typeof(result.get(&"ok", null)) == TYPE_BOOL: return result.duplicate(true) return {&"ok": false, &"reason": &"TEST_HOOK_INVALID_RESULT"} func _register_operations_tick_handler() -> void: _clock.unregister_tick_handler( _clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.operations" ) _clock.register_tick_handler( _clock.TickPhase.TIME_BOUNDARIES, &"simulation_runtime.operations", Callable(self, "_advance_operations_time") ) func _configure_operations_callbacks() -> void: if operations_coordinator == null: return operations_coordinator.configure_callbacks( Callable(self, "_operations_daily_boundary"), Callable(self, "_operations_arrival_boundary"), Callable(self, "_operations_morning_boundary"), Callable(self, "_operations_midnight_upkeep_boundary") ) func _configure_default_operations_demand(coordinator: OperationsCoordinator) -> CommandResult: if coordinator == null or DEFAULT_OPERATIONS_DEMAND_PROFILES.size() != 8: return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, 0, "The canonical Operations demand profile set is unavailable." ) var allowed_classes: Dictionary = {} for class_id: StringName in mvp_content_catalog.get_guest_class_ids(): allowed_classes[class_id] = true var seen_classes: Dictionary = {} for raw_profile: Variant in DEFAULT_OPERATIONS_DEMAND_PROFILES: if not raw_profile is Dictionary: return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, 0, "A canonical Operations demand profile is malformed." ) var profile: Dictionary = raw_profile var guest_class := StringName(profile[&"guest_class"]) if not allowed_classes.has(guest_class) or seen_classes.has(guest_class): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, 0, "The canonical Operations demand classes are invalid." ) seen_classes[guest_class] = true var configured := coordinator.set_demand_input( StringName(profile[&"facility_tag"]), guest_class, StringName(profile[&"time_band"]), { &"base": 100, &"reputation": 0, &"unmet_need": 0, &"event": 0, &"recent_price_penalty": 0, &"wait_penalty": 0, } ) if not configured.is_success(): return configured if seen_classes.size() != allowed_classes.size(): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, 0, "Every MVP Guest class must have exactly one demand profile." ) return CommandResult.succeeded(0, {&"profile_count": seen_classes.size()}) func get_facility_upkeep_report(facility_id: StringName) -> Dictionary: var definition_id: StringName = &"" var facility := facility_service.get_instance(facility_id) if facility != null: definition_id = facility.definition_id elif construction_service != null: for built: Dictionary in construction_service.get_facilities(): if StringName(built[&"instance_id"]) == facility_id: definition_id = StringName(built[&"definition_id"]) break var definition := definition_registry.get_facility(definition_id) var balance := upkeep_settlement.get_facility_balance(facility_id) var units := facility_upkeep.get_unsettled_units(facility_id, upkeep_settlement.get_settled_period_count()) return { &"facility_id": facility_id, &"owned": definition != null, &"daily_rate": definition.maintenance_gold if definition != null else 0, &"unsettled_units": units + int(balance[&"fraction_units"]), &"gold_denominator": FacilityUpkeepAccrual.MINUTES_PER_DAY, &"arrears": balance[&"arrears"], } func _advance_facility_upkeep(minute: int) -> void: if _operations_deferred_runtime_minute >= 0: return var rates: Dictionary = {} for facility: FacilityInstance in facility_service.get_instances(): var definition := definition_registry.get_facility(facility.definition_id) if definition != null: rates[String(facility.instance_id)] = definition.maintenance_gold # Infrastructure modules are completed assets even when they have no service instance. for facility: Dictionary in construction_service.get_facilities(): var definition := definition_registry.get_facility(StringName(facility[&"definition_id"])) if definition != null: rates[String(facility[&"instance_id"])] = definition.maintenance_gold var advanced := facility_upkeep.advance_to(minute, rates) if not advanced[&"ok"]: _clock.set_paused(true) push_error("Facility upkeep accrual failed: %s" % advanced) func _upkeep_accrual_crosslinks_are_valid(accrual: FacilityUpkeepAccrual, settlement: FacilityUpkeepSettlement) -> bool: var periods: Array = accrual.get_report()[&"closed_periods"] var receipts: Array = settlement.get_report()[&"receipts"] if receipts.size() > periods.size(): return false for index in receipts.size(): if receipts[index][&"period"] != periods[index]: return false return true func _operations_closed_arrears_total() -> int: # Coordinator arrears describe its last financial close. Interim calendar # payments during a legacy 06:00 period must not rewrite that closed record. var last_close := 0 if operations_coordinator != null and operations_coordinator.get_open_financial_day() > 1: last_close = operations_coordinator.get_daily_close_minute(operations_coordinator.get_open_financial_day() - 1) return _staff_payroll_arrears_total(staff_service) + upkeep_settlement.get_arrears_through(last_close) func _operations_total_arrears() -> int: return _staff_payroll_arrears_total(staff_service) + upkeep_settlement.get_arrears_total() func _advance_operations_time(absolute_minute: int) -> void: if not _operations_enabled or operations_coordinator == null: return if _operations_deferred_runtime_minute >= 0: _operations_tick_blocked = true _clock.set_paused(true) return _operations_tick_blocked = false var advanced := operations_coordinator.advance_to(absolute_minute, true) if not advanced.is_success(): # Earlier boundaries in the same minute can already be validly committed # (for example demand before a failed arrival). Publish only those staged # effects and notifications; the failing callback rolled itself back. if not _commit_operations_domain_events() \ or not _commit_operations_notifications(): operations_coordinator.discard_deferred_notifications() _discard_operations_domain_events() _operations_runtime_failure_latched = true _operations_runtime_failure_report = advanced.to_dictionary() _operations_deferred_runtime_minute = absolute_minute _operations_tick_blocked = true _clock.set_paused(true) return if operations_coordinator.current_minute != absolute_minute \ or operations_coordinator.get_population_count() != guest_service.guest_count() \ or operations_coordinator.get_arrears_total() != _operations_closed_arrears_total() \ or not _operations_notifications_are_committable() \ or not _commit_operations_domain_events() \ or not _commit_operations_notifications(): operations_coordinator.discard_deferred_notifications() _discard_operations_domain_events() _operations_runtime_failure_latched = true _operations_runtime_failure_report = { &"code": &"OPERATIONS_CROSSLINK_MISMATCH", &"minute": absolute_minute, } _operations_deferred_runtime_minute = absolute_minute _operations_tick_blocked = true _clock.set_paused(true) return _operations_runtime_failure_latched = false _operations_runtime_failure_report.clear() if operations_coordinator.is_terminal(): _operations_deferred_runtime_minute = -1 _operations_tick_blocked = true _clock.set_paused(true) return if operations_coordinator.pause_requested: _operations_deferred_runtime_minute = absolute_minute _operations_tick_blocked = true _clock.set_paused(true) return _operations_deferred_runtime_minute = -1 _pause_for_waiting_foreground_event() func _operations_should_block_tick() -> bool: if not _operations_enabled or operations_coordinator == null: return false return _operations_deferred_runtime_minute >= 0 \ or _operations_runtime_failure_latched \ or operations_coordinator.pause_requested \ or operations_coordinator.is_terminal() \ or operations_coordinator.is_external_quarantined() func _pause_for_waiting_foreground_event() -> void: if event_service == null or _clock == null: return var foreground := event_service.get_foreground_instance() if foreground != null and foreground.state == EventInstance.State.WAITING_CHOICE: _clock.set_paused(true) func _capture_operations_external_state() -> Dictionary: # This is deliberately a composed transaction token, not a Runtime snapshot. # It contains every aggregate that an Operations callback can reach, while # excluding clock/coordinator state owned by the caller of the callback. if not _begin_operations_external_transaction(): return {} return _build_operations_external_state() func _build_operations_external_state() -> Dictionary: var committed_minute := _runtime_aggregate_committed_minute() if committed_minute < 0: _discard_operations_domain_events() return {} var rng_snapshots: Dictionary = {} for stream_id: StringName in _sorted_name_keys(_rng_streams): var stream := _rng_streams.get(stream_id) as DeterministicRng if stream == null: _discard_operations_domain_events() return {} rng_snapshots[stream_id] = stream.snapshot() return { # TIME_BOUNDARIES runs before the Runtime suffix. During a normal # Operations callback the Clock is already at X while minute-driven # aggregates are still committed through X - 1. Capture that committed # participant minute so rollback validates the state it actually saved. &"captured_minute": committed_minute, &"captured_clock_minute": _clock.absolute_minute, &"captured_authority_minute": authority_service.current_minute, &"captured_defense_minute": defense_service.current_minute, &"game_state": _state.create_snapshot(), &"command_queue": command_queue.create_snapshot(), &"next_command_id": _next_command_id, &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"inventory": inventory_service.create_snapshot(), &"kitchen_production": kitchen_production.create_snapshot(), &"auto_purchase_plans": auto_purchase_plans.create_snapshot(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"guests": guest_service.create_snapshot(), &"staff": staff_service.create_snapshot(), &"relationships": relationship_service.create_snapshot(), &"events": event_service.create_snapshot(), &"authority": authority_service.create_snapshot(), &"world": world_service.create_snapshot(), &"path": path_service.create_snapshot(), &"elevators": elevators.create_snapshot(), &"legacy_stairs": legacy_stairs.create_snapshot(), &"excavation": excavation_service.create_snapshot(), &"spatial_subslots": spatial_subslot_service.create_snapshot(), &"route_projection": route_subslot_path_adapter.create_snapshot(), &"facility_infrastructure": facility_infrastructure_projector.create_snapshot(), &"movement_credits": _movement_credits.duplicate(true), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), &"rng_streams": rng_snapshots, &"staffing_projection_enabled": _staffing_projection_enabled, &"progression": progression_service.create_snapshot(), &"hired_staff_candidate_ids": _hired_staff_candidate_ids.duplicate(true), &"personal_action_points": _personal_action_points, # The remaining aggregates are reachable through Event actions or signals. &"mana": mana_network_service.create_snapshot(), &"mana_reserve": mana_reserve.create_snapshot(), &"logistics_dispatcher": logistics_dispatcher.create_snapshot(), &"logistics_requests": _serialize_logistics_requests(), &"defense": defense_service.create_snapshot(), &"construction": construction_service.create_snapshot(), } func _operations_external_committed_minute() -> int: return _runtime_aggregate_committed_minute() func _runtime_aggregate_committed_minute() -> int: if facility_service == null or guest_service == null or inventory_service == null \ or staff_service == null or kitchen_production == null: return -1 var minute := facility_service.current_minute if minute < 0 or guest_service.current_minute != minute \ or inventory_service.current_minute != minute \ or staff_service.current_minute != minute \ or kitchen_production.current_minute != minute \ or elevators.current_minute != minute: return -1 return minute func _restore_operations_external_state(token: Variant) -> bool: if not token is Dictionary: _discard_operations_domain_events() return false var saved: Dictionary = token var required_dictionary_keys: Array[StringName] = [ &"facility_upkeep", &"upkeep_settlement", &"facility_cleanliness", &"game_state", &"command_queue", &"economy", &"inventory", &"kitchen_production", &"auto_purchase_plans", &"facilities", &"guests", &"staff", &"relationships", &"events", &"authority", &"world", &"path", &"elevators", &"legacy_stairs", &"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure", &"movement_credits", &"domain_events", &"id_generator", &"rng_streams", &"progression", &"hired_staff_candidate_ids", &"mana", &"mana_reserve", &"logistics_dispatcher", &"defense", &"construction", ] for key: StringName in required_dictionary_keys: if not saved.get(key, null) is Dictionary: _discard_operations_domain_events() return false if not saved.get(&"logistics_requests", null) is Array \ or typeof(saved.get(&"staffing_projection_enabled", null)) != TYPE_BOOL \ or typeof(saved.get(&"personal_action_points", null)) != TYPE_INT \ or typeof(saved.get(&"captured_minute", null)) != TYPE_INT \ or int(saved[&"captured_minute"]) < 0 \ or typeof(saved.get(&"captured_clock_minute", null)) != TYPE_INT \ or int(saved[&"captured_clock_minute"]) < int(saved[&"captured_minute"]) \ or typeof(saved.get(&"captured_authority_minute", null)) != TYPE_INT \ or int(saved[&"captured_authority_minute"]) < 0 \ or typeof(saved.get(&"captured_defense_minute", null)) != TYPE_INT \ or int(saved[&"captured_defense_minute"]) < 0 \ or typeof(saved.get(&"next_command_id", null)) != TYPE_INT \ or int(saved[&"next_command_id"]) <= 0: _discard_operations_domain_events() return false # Validation/PREPARE failure did not mutate a participant. Avoid restore APIs # here because GameState.restore_snapshot intentionally emits reset signals. if _operations_external_state_matches(saved): _discard_operations_domain_events() return true # Ask every participant to restore even when an earlier participant rejects # its snapshot. This avoids knowingly leaving a half-restored external world. var state_ok: bool = _state.restore_snapshot(saved[&"game_state"]) var command_queue_ok := command_queue.restore_snapshot(saved[&"command_queue"]) var economy_ok := economy_service.restore_snapshot(saved[&"economy"]) var upkeep_ok := facility_upkeep.restore_snapshot(saved[&"facility_upkeep"]) and upkeep_settlement.restore_snapshot(saved[&"upkeep_settlement"], economy_service) var inventory_ok := inventory_service.restore_snapshot(saved[&"inventory"]) var facilities_ok := facility_service.restore_snapshot(saved[&"facilities"]) facilities_ok = facility_cleanliness.restore_snapshot(saved.get(&"facility_cleanliness", {})) and facilities_ok var kitchen_ok := kitchen_production.restore_snapshot(saved[&"kitchen_production"]) var auto_purchase_ok := auto_purchase_plans.restore_snapshot(saved[&"auto_purchase_plans"], facility_service, economy_service, int(saved[&"captured_minute"])) var guests_ok := guest_service.restore_snapshot(saved[&"guests"]) var staff_ok := staff_service.restore_snapshot(saved[&"staff"]) var relationships_ok := relationship_service.restore_snapshot(saved[&"relationships"]) var events_ok := event_service.restore_snapshot(saved[&"events"]) var authority_ok := authority_service.restore_snapshot(saved[&"authority"]) var world_ok := world_service.restore_snapshot(saved[&"world"]) var spatial_ok := spatial_subslot_service.restore_snapshot(saved[&"spatial_subslots"]) var route_snapshot: Dictionary = saved[&"route_projection"] var projected_walkable: Variant = route_snapshot.get(&"projected_walkable_cells", null) var topology_ok := projected_walkable is Array \ and path_service.rebuild_graph(projected_walkable) var path_ok := topology_ok and path_service.restore_snapshot(saved[&"path"]) path_ok = elevators.restore_snapshot(saved[&"elevators"]) and path_ok path_ok = legacy_stairs.restore_snapshot(saved[&"legacy_stairs"]) and path_ok var route_ok := path_ok and route_subslot_path_adapter.restore_snapshot(route_snapshot) var infrastructure_ok := route_ok and facility_infrastructure_projector.restore_snapshot( saved[&"facility_infrastructure"] ) var domain_events_ok := domain_events.restore_snapshot(saved[&"domain_events"]) var ids_ok := id_generator.restore_snapshot(saved[&"id_generator"]) var progression_ok := progression_service.restore_snapshot(saved[&"progression"]) var mana_ok := mana_network_service.restore_snapshot(saved[&"mana"]) mana_ok = mana_reserve.restore_snapshot(saved[&"mana_reserve"]) and mana_ok var dispatcher_ok := logistics_dispatcher.restore_snapshot(saved[&"logistics_dispatcher"]) var defense_ok := defense_service.restore_snapshot(saved[&"defense"]) var construction_ok := construction_service.restore_snapshot(saved[&"construction"]) var excavation_ok := excavation_service.restore_snapshot(saved[&"excavation"]) var restored_requests: Variant = _deserialize_logistics_requests(saved[&"logistics_requests"]) var requests_ok := restored_requests is Dictionary if requests_ok: logistics_requests = restored_requests _movement_credits = (saved[&"movement_credits"] as Dictionary).duplicate(true) _staffing_projection_enabled = bool(saved[&"staffing_projection_enabled"]) _hired_staff_candidate_ids = ( saved[&"hired_staff_candidate_ids"] as Dictionary ).duplicate(true) _personal_action_points = int(saved[&"personal_action_points"]) _next_command_id = int(saved[&"next_command_id"]) var rng_ok := true var saved_rngs: Dictionary = saved[&"rng_streams"] if saved_rngs.size() != _rng_streams.size(): rng_ok = false for raw_stream_id: Variant in saved_rngs: var stream_id := StringName(raw_stream_id) var stream := _rng_streams.get(stream_id) as DeterministicRng if stream == null or not saved_rngs[raw_stream_id] is Dictionary \ or not stream.restore(saved_rngs[raw_stream_id]): rng_ok = false _discard_operations_domain_events() var participants_ok := upkeep_ok and state_ok and command_queue_ok and economy_ok and inventory_ok and kitchen_ok and auto_purchase_ok \ and facilities_ok and guests_ok and staff_ok and relationships_ok \ and events_ok and authority_ok and world_ok and spatial_ok and path_ok \ and route_ok and infrastructure_ok and excavation_ok \ and domain_events_ok and ids_ok \ and progression_ok and mana_ok and dispatcher_ok and defense_ok \ and construction_ok and requests_ok and rng_ok if not participants_ok: return false var validation_minute := int(saved[&"captured_minute"]) var clock_validation_minute := int(saved[&"captured_clock_minute"]) return _operations_external_state_matches(saved) \ and bool(economy_service.validate_ledger().get(&"ok", false)) \ and _validate_logistics_dispatcher_crosslinks( logistics_dispatcher, logistics_requests, clock_validation_minute ) \ and _validate_runtime_crosslinks( facility_service, guest_service, economy_service, inventory_service, staff_service, validation_minute, _staffing_projection_enabled ) \ and _validate_runtime_path_crosslinks( path_service, guest_service, staff_service, _movement_credits, clock_validation_minute ) \ and _validate_authority_economy_crosslinks( authority_service, economy_service, int(saved[&"captured_authority_minute"]) ) \ and _validate_defense_crosslinks( defense_service, staff_service, facility_service, int(saved[&"captured_defense_minute"]) ) \ and _validate_construction_facility_crosslinks( world_service, construction_service, facility_service, facility_infrastructure_projector ) \ and _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ) func _operations_external_state_matches(saved: Dictionary) -> bool: if not _strict_variants_equal(_state.create_snapshot(), saved[&"game_state"]) \ or not _strict_variants_equal(command_queue.create_snapshot(), saved[&"command_queue"]) \ or _next_command_id != int(saved[&"next_command_id"]) \ or not _strict_variants_equal(facility_upkeep.create_snapshot(), saved[&"facility_upkeep"]) \ or not _strict_variants_equal(upkeep_settlement.create_snapshot(), saved[&"upkeep_settlement"]) \ or not _strict_variants_equal(economy_service.create_snapshot(), saved[&"economy"]) \ or not _strict_variants_equal(inventory_service.create_snapshot(), saved[&"inventory"]) \ or not _strict_variants_equal(kitchen_production.create_snapshot(), saved[&"kitchen_production"]) \ or not _strict_variants_equal(auto_purchase_plans.create_snapshot(), saved[&"auto_purchase_plans"]) \ or not _strict_variants_equal(facility_service.create_snapshot(), saved[&"facilities"]) \ or not _strict_variants_equal(facility_cleanliness.create_snapshot(), saved.get(&"facility_cleanliness", {})) \ or not _strict_variants_equal(guest_service.create_snapshot(), saved[&"guests"]) \ or not _strict_variants_equal(staff_service.create_snapshot(), saved[&"staff"]) \ or not _strict_variants_equal(relationship_service.create_snapshot(), saved[&"relationships"]) \ or not _strict_variants_equal(event_service.create_snapshot(), saved[&"events"]) \ or not _strict_variants_equal(authority_service.create_snapshot(), saved[&"authority"]) \ or not _strict_variants_equal(world_service.create_snapshot(), saved[&"world"]) \ or not _strict_variants_equal(path_service.create_snapshot(), saved[&"path"]) \ or not _strict_variants_equal(elevators.create_snapshot(), saved[&"elevators"]) \ or not _strict_variants_equal(legacy_stairs.create_snapshot(), saved[&"legacy_stairs"]) \ or not _strict_variants_equal(excavation_service.create_snapshot(), saved[&"excavation"]) \ or not _strict_variants_equal(spatial_subslot_service.create_snapshot(), saved[&"spatial_subslots"]) \ or not _strict_variants_equal(route_subslot_path_adapter.create_snapshot(), saved[&"route_projection"]) \ or not _strict_variants_equal(facility_infrastructure_projector.create_snapshot(), saved[&"facility_infrastructure"]) \ or not _strict_variants_equal(_movement_credits, saved[&"movement_credits"]) \ or not _strict_variants_equal(domain_events.create_snapshot(), saved[&"domain_events"]) \ or not _strict_variants_equal(id_generator.create_snapshot(), saved[&"id_generator"]) \ or not _strict_variants_equal(progression_service.create_snapshot(), saved[&"progression"]) \ or not _strict_variants_equal(mana_network_service.create_snapshot(), saved[&"mana"]) \ or not _strict_variants_equal(mana_reserve.create_snapshot(), saved[&"mana_reserve"]) \ or not _strict_variants_equal(logistics_dispatcher.create_snapshot(), saved[&"logistics_dispatcher"]) \ or not _strict_variants_equal(_serialize_logistics_requests(), saved[&"logistics_requests"]) \ or not _strict_variants_equal(defense_service.create_snapshot(), saved[&"defense"]) \ or not _strict_variants_equal(construction_service.create_snapshot(), saved[&"construction"]) \ or _staffing_projection_enabled != bool(saved[&"staffing_projection_enabled"]) \ or not _strict_variants_equal(_hired_staff_candidate_ids, saved[&"hired_staff_candidate_ids"]) \ or _personal_action_points != int(saved[&"personal_action_points"]): return false var saved_rngs: Dictionary = saved[&"rng_streams"] if saved_rngs.size() != _rng_streams.size(): return false for raw_stream_id: Variant in saved_rngs: var stream := _rng_streams.get(StringName(raw_stream_id)) as DeterministicRng if stream == null or not _strict_variants_equal( stream.snapshot(), saved_rngs[raw_stream_id] ): return false return true func _begin_operations_external_transaction() -> bool: if _operations_transaction_active: # One Coordinator minute can contain daily, demand and arrival boundaries. # Keep their Runtime side effects in one notification/event transaction # until the Coordinator has committed the complete minute. return true _operations_transaction_active = true _operations_queued_domain_events.clear() _operations_queued_guest_spawn_notifications.clear() _operations_queued_economy_notifications.clear() return true func _commit_operations_domain_events() -> bool: if not _operations_transaction_active: return true var seen: Dictionary = {} for event: SimulationDomainEvent in _operations_queued_domain_events: if event == null or not event.is_valid() or seen.has(event.event_id) \ or domain_events.has_published(event.event_id): return false seen[event.event_id] = true var committed := domain_events.publish_batch(_operations_queued_domain_events) if not committed: return false _operations_queued_domain_events.clear() _operations_transaction_active = false return true func _operations_notifications_are_committable() -> bool: if operations_coordinator == null or guest_service == null: return false var seen_guest_ids: Dictionary = {} for guest_id: StringName in _operations_queued_guest_spawn_notifications: if guest_id.is_empty() or seen_guest_ids.has(guest_id) \ or not guest_service.has_guest(guest_id) \ or not guest_service.has_pending_guest_spawned_notification(guest_id): return false seen_guest_ids[guest_id] = true var seen_transaction_ids: Dictionary = {} for transaction_id: StringName in _operations_queued_economy_notifications: if transaction_id.is_empty() or seen_transaction_ids.has(transaction_id) \ or not economy_service.has_pending_transaction_notification(transaction_id): return false seen_transaction_ids[transaction_id] = true return true func _stage_operations_economy_notification(result: CommandResult) -> bool: if result == null or not result.is_success(): return false var transaction_id := StringName(result.details.get(&"transaction_id", &"")) if transaction_id.is_empty() \ or not economy_service.has_pending_transaction_notification(transaction_id) \ or _operations_queued_economy_notifications.has(transaction_id): return false _operations_queued_economy_notifications.append(transaction_id) # Preserve Runtime's authoritative GameState projection now; the matching raw # Economy signal is delivered to external observers only after commit. _on_ledger_transaction_posted( transaction_id, economy_service.get_transaction_entries(transaction_id) ) return true func _discard_operations_domain_events() -> void: for guest_id: StringName in _operations_queued_guest_spawn_notifications: guest_service.discard_guest_spawned_notification(guest_id) for transaction_id: StringName in _operations_queued_economy_notifications: economy_service.discard_transaction_notification(transaction_id) _operations_queued_guest_spawn_notifications.clear() _operations_flushing_guest_spawn_notifications.clear() _operations_queued_economy_notifications.clear() _operations_flushing_economy_notifications.clear() _operations_queued_domain_events.clear() _operations_transaction_active = false func _commit_operations_notifications() -> bool: if operations_coordinator == null: return false guest_service.set_external_mutation_blocked(true) var coordinator_notifications_ok := operations_coordinator.commit_deferred_notifications() var guest_notifications_ok := true if coordinator_notifications_ok: for guest_id: StringName in _operations_queued_guest_spawn_notifications: _operations_flushing_guest_spawn_notifications[guest_id] = true if not guest_service.commit_guest_spawned_notification(guest_id): guest_notifications_ok = false _operations_flushing_guest_spawn_notifications.erase(guest_id) else: guest_notifications_ok = false var economy_notifications_ok := coordinator_notifications_ok and guest_notifications_ok if economy_notifications_ok: for transaction_id: StringName in _operations_queued_economy_notifications: _operations_flushing_economy_notifications[transaction_id] = true if not economy_service.commit_transaction_notification(transaction_id): economy_notifications_ok = false _operations_flushing_economy_notifications.erase(transaction_id) guest_service.set_external_mutation_blocked(false) _operations_queued_guest_spawn_notifications.clear() _operations_flushing_guest_spawn_notifications.clear() _operations_queued_economy_notifications.clear() _operations_flushing_economy_notifications.clear() return coordinator_notifications_ok and guest_notifications_ok and economy_notifications_ok func _publish_domain_event(event: SimulationDomainEvent) -> bool: if _construction_completion_transaction_active: if event == null or not event.is_valid() or domain_events.has_published(event.event_id): return false for queued: SimulationDomainEvent in _construction_queued_domain_events: if queued.event_id == event.event_id: return false _construction_queued_domain_events.append(event.copy()) return true if not _operations_transaction_active: return domain_events.publish(event) if event == null or not event.is_valid() or domain_events.has_published(event.event_id): return false for queued: SimulationDomainEvent in _operations_queued_domain_events: if queued.event_id == event.event_id: return false _operations_queued_domain_events.append(event.copy()) return true func _operations_daily_boundary( payload: Dictionary, phase: StringName, token: Variant ) -> Dictionary: if phase == OperationsCoordinator.PHASE_CAPTURE: var captured := _capture_operations_external_state() return {&"ok": not captured.is_empty(), &"token": captured} if phase == OperationsCoordinator.PHASE_VALIDATE: return { &"ok": token is Dictionary and not token.is_empty() \ and _operations_daily_payload_is_valid(payload), &"token": token, } if phase == OperationsCoordinator.PHASE_APPLY: return _apply_operations_daily_close(payload, token) if phase == OperationsCoordinator.PHASE_ROLLBACK: return {&"ok": _restore_operations_external_state(token)} return {&"ok": false} func _operations_daily_payload_is_valid(payload: Dictionary) -> bool: for key: StringName in [ &"day", &"boundary_minute", &"period_start_minute", &"period_end_minute_exclusive", ]: if typeof(payload.get(key, null)) != TYPE_INT: return false var day := int(payload[&"day"]) var boundary_minute := int(payload[&"boundary_minute"]) return day > 0 \ and boundary_minute == _clock.absolute_minute \ and boundary_minute == operations_coordinator.get_daily_close_minute(day) \ and int(payload[&"period_start_minute"]) == operations_coordinator.get_financial_period_start(day) \ and int(payload[&"period_end_minute_exclusive"]) == boundary_minute func _settle_upkeep_through(boundary_minute: int) -> Dictionary: var upkeep_paid := 0 for period: Dictionary in facility_upkeep.get_closed_periods_from(upkeep_settlement.get_settled_period_count()): if int(period[&"period_end"]) > boundary_minute: continue var upkeep_result := upkeep_settlement.settle(period, economy_service) if not upkeep_result[&"ok"]: return {&"ok": false, &"reason": &"UPKEEP_SETTLEMENT_FAILED"} if not upkeep_result[&"replayed"]: for row: Dictionary in upkeep_result[&"receipt"][&"rows"]: upkeep_paid += int(row[&"paid"]) if not String(upkeep_result[&"transaction_id"]).is_empty() and not _stage_operations_economy_notification(CommandResult.succeeded(0, {&"transaction_id": upkeep_result[&"transaction_id"]})): return {&"ok": false, &"reason": &"UPKEEP_NOTIFICATION_STAGE_FAILED"} return {&"ok": true, &"paid_total": upkeep_paid} func _operations_midnight_upkeep_boundary(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary: if phase == OperationsCoordinator.PHASE_CAPTURE: var captured := _capture_operations_external_state() return {&"ok": not captured.is_empty(), &"token": captured} if phase == OperationsCoordinator.PHASE_ROLLBACK: return {&"ok": _restore_operations_external_state(token)} var minute: Variant = payload.get(&"boundary_minute") var valid: bool = token is Dictionary and not token.is_empty() and typeof(minute) == TYPE_INT \ and int(minute) == _clock.absolute_minute and int(minute) % 1440 == 0 if phase == OperationsCoordinator.PHASE_VALIDATE: return {&"ok": valid, &"token": token} if phase != OperationsCoordinator.PHASE_APPLY or not valid: return {&"ok": false} var prepared := _run_operations_test_boundary_hook(&"MIDNIGHT_UPKEEP", payload, OPERATIONS_TEST_PREPARE_PHASE, token) if not bool(prepared.get(&"ok", false)): return prepared var result := _settle_upkeep_through(int(minute)) if not bool(result[&"ok"]): return result var after := _run_operations_test_boundary_hook(&"MIDNIGHT_UPKEEP", payload, OPERATIONS_TEST_AFTER_FIRST_MUTATION_PHASE, token) if not bool(after.get(&"ok", false)): return after return {&"ok": upkeep_settlement.validate_ledger(economy_service) \ and int(_state.get_resource(&"gold")) == economy_service.get_balance(PLAYER_CASH_ACCOUNT)} func _apply_operations_daily_close(payload: Dictionary, token: Variant) -> Dictionary: if not token is Dictionary or token.is_empty() \ or not _operations_daily_payload_is_valid(payload): return {&"ok": false, &"reason": &"DAILY_CLOSE_PRECONDITION_FAILED"} var prepare_hook := _run_operations_test_boundary_hook( &"DAILY_CLOSE", payload, OPERATIONS_TEST_PREPARE_PHASE, token ) if not bool(prepare_hook.get(&"ok", false)): return prepare_hook var day := int(payload[&"day"]) var preview := staff_service.preview_daily_payroll(day) if not bool(preview.get(&"ok", false)) \ or not preview.get(&"rows", null) is Array: return {&"ok": false, &"reason": &"PAYROLL_PREVIEW_FAILED"} var available_gold := economy_service.get_balance(PLAYER_CASH_ACCOUNT) var remaining_gold := available_gold var payments: Dictionary = {} var expected_paid_total := 0 var previous_staff_id := "" for raw_row: Variant in preview[&"rows"]: if not raw_row is Dictionary: return {&"ok": false, &"reason": &"PAYROLL_PREVIEW_INVALID"} var row: Dictionary = raw_row var staff_id_value: Variant = row.get(&"staff_id", null) var total_due_value: Variant = row.get(&"total_due", null) if typeof(staff_id_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(staff_id_value).is_empty() \ or typeof(total_due_value) != TYPE_INT or int(total_due_value) < 0: return {&"ok": false, &"reason": &"PAYROLL_PREVIEW_INVALID"} var staff_text := String(staff_id_value) if not previous_staff_id.is_empty() and staff_text <= previous_staff_id: return {&"ok": false, &"reason": &"PAYROLL_ORDER_INVALID"} previous_staff_id = staff_text var payment := mini(int(total_due_value), remaining_gold) payments[StringName(staff_id_value)] = payment remaining_gold -= payment expected_paid_total += payment var settlement_id := StringName("payroll-day-%08d" % day) var settlement := staff_service.apply_daily_payroll_settlement( settlement_id, day, payments ) if not settlement.is_success() \ or int(settlement.details.get(&"paid_total", -1)) != expected_paid_total: return { &"ok": false, &"reason": &"PAYROLL_SETTLEMENT_FAILED", &"result": settlement.to_dictionary(), } if expected_paid_total > 0: var wage_payment := economy_service.transfer( PLAYER_CASH_ACCOUNT, WAGE_EXPENSE_ACCOUNT, expected_paid_total, int(payload[&"boundary_minute"]), &"WAGE", settlement_id, StringName("operations.wage.day.%08d" % day), &"operations.payroll.settlement", &"", false ) if not wage_payment.is_success(): return { &"ok": false, &"reason": &"WAGE_LEDGER_POST_FAILED", &"result": wage_payment.to_dictionary(), } if not _stage_operations_economy_notification(wage_payment): return { &"ok": false, &"reason": &"WAGE_NOTIFICATION_STAGE_FAILED", &"result": wage_payment.to_dictionary(), } var upkeep_result := _settle_upkeep_through(int(payload[&"boundary_minute"])) if not bool(upkeep_result[&"ok"]): return upkeep_result var upkeep_paid := int(upkeep_result[&"paid_total"]) var financials := _build_operations_daily_financials( int(payload[&"period_start_minute"]), int(payload[&"period_end_minute_exclusive"]), int(preview.get(&"new_wages_total", -1)), preview[&"rows"] ) if not bool(financials.get(&"ok", false)): return financials var arrears_total := int(settlement.details.get(&"unpaid_after_total", -1)) if arrears_total < 0 \ or arrears_total != _staff_payroll_arrears_total(staff_service) \ or economy_service.get_balance(PLAYER_CASH_ACCOUNT) != available_gold - expected_paid_total - upkeep_paid \ or int(_state.get_resource(&"gold")) != economy_service.get_balance(PLAYER_CASH_ACCOUNT) \ or not bool(economy_service.validate_ledger().get(&"ok", false)) \ or not bool(staff_service.validate_invariants().get(&"ok", false)): return {&"ok": false, &"reason": &"DAILY_CLOSE_POSTCONDITION_FAILED"} var settlement_report := settlement.details.duplicate(true) settlement_report[&"upkeep_arrears_total"] = upkeep_settlement.get_arrears_total() settlement_report[&"arrears_total"] = arrears_total + int(settlement_report[&"upkeep_arrears_total"]) return { &"ok": true, &"category_totals": financials[&"category_totals"], &"reconciliation_difference": financials[&"reconciliation_difference"], &"facility_profit_loss": financials[&"facility_profit_loss"], &"anomalies": financials[&"anomalies"], &"settlement": settlement_report, } func _build_operations_daily_financials( period_start: int, period_end_exclusive: int, new_wages_total: int, payroll_rows: Array ) -> Dictionary: if period_start < 0 or period_end_exclusive <= period_start or new_wages_total < 0: return {&"ok": false, &"reason": &"DAILY_FINANCIAL_PERIOD_INVALID"} if not _loan_principal_ledger_is_valid(economy_service): return {&"ok": false, &"reason": &"INVALID_LOAN_PRINCIPAL_LEDGER"} var category_totals := { &"revenue": 0, &"refunds": 0, &"cost_of_goods": 0, &"wages": new_wages_total, &"maintenance": 0, &"utilities": 0, &"taxes_and_interest": 0, &"fines": 0, &"construction_cash_flow": 0, } var facility_rows: Dictionary = {} var excluded_categories: Array[StringName] = [ &"EVENT_COST_RESERVED", &"EVENT_COST_RELEASED", &"INITIALIZATION", &"TRANSFER", &"GENERATION", &"DESTRUCTION", &"MIGRATION", &"SERVICE_CASH_SWEEP", &"WAGE", &"MAINTENANCE_PAYMENT", ] var category_mapping := { &"SERVICE_SALE": &"revenue", &"REFUND": &"refunds", &"CONSTRUCTION": &"construction_cash_flow", &"CONSTRUCTION_REFUND": &"construction_cash_flow", &"AUTHORITY_FINE": &"fines", &"OPERATING_EXPENSE": &"cost_of_goods", &"STAFF_SIGNING_COST": &"cost_of_goods", &"EVENT_ACTION": &"cost_of_goods", &"EVENT_COST_COMMITTED": &"cost_of_goods", &"DEFENSE_SKILL_COST": &"cost_of_goods", &"MAINTENANCE_ACCRUAL": &"maintenance", &"LOAN_PRINCIPAL_BORROWED": &"financing_principal_inflows", &"LOAN_PRINCIPAL_REPAID": &"financing_principal_outflows", } var operating_refund_total := 0 for entry: LedgerEntry in economy_service.get_entries(): if entry.absolute_minute < period_start \ or entry.absolute_minute >= period_end_exclusive: continue if entry.category in excluded_categories: continue if entry.category == &"OPERATING_REFUND": operating_refund_total += entry.amount continue if not category_mapping.has(entry.category): return { &"ok": false, &"reason": &"UNKNOWN_LEDGER_CATEGORY", &"category": entry.category, &"transaction_id": entry.transaction_id, } var category: StringName = category_mapping[entry.category] category_totals[category] = int(category_totals.get(category, 0)) + entry.amount if not entry.source_entity_id.is_empty() \ and (facility_service.has_instance(entry.source_entity_id) or entry.category == &"MAINTENANCE_ACCRUAL") \ and category not in [&"construction_cash_flow", &"financing_principal_inflows", &"financing_principal_outflows"]: if not facility_rows.has(entry.source_entity_id): facility_rows[entry.source_entity_id] = { &"revenue": 0, &"expenses": 0, &"net_profit": 0, } var facility_row: Dictionary = facility_rows[entry.source_entity_id] if category == &"revenue": facility_row[&"revenue"] = int(facility_row[&"revenue"]) + entry.amount else: facility_row[&"expenses"] = int(facility_row[&"expenses"]) + entry.amount var anomalies: Array[Dictionary] = [] var wage_allocation := _allocate_operations_wages_to_facilities( period_start, period_end_exclusive, new_wages_total, payroll_rows, facility_rows ) if not bool(wage_allocation.get(&"ok", false)): return wage_allocation anomalies.append_array(wage_allocation[&"anomalies"]) var operating_refund_offset := mini( operating_refund_total, int(category_totals[&"cost_of_goods"]) ) category_totals[&"cost_of_goods"] = int(category_totals[&"cost_of_goods"]) \ - operating_refund_offset var excess_operating_refund := operating_refund_total - operating_refund_offset if excess_operating_refund > 0: category_totals[&"revenue"] = int(category_totals[&"revenue"]) \ + excess_operating_refund anomalies.append({ &"code": &"OPERATING_REFUND_EXCEEDS_PERIOD_COST", &"refund_total": operating_refund_total, &"cost_offset": operating_refund_offset, &"excess_revenue": excess_operating_refund, }) for facility_id: StringName in _sorted_name_keys(facility_rows): var row: Dictionary = facility_rows[facility_id] row[&"net_profit"] = int(row[&"revenue"]) - int(row[&"expenses"]) var allocation_rows: Array[Dictionary] = [] for facility_id: StringName in _sorted_name_keys( wage_allocation[&"allocated_by_facility"] ): allocation_rows.append({ &"facility_id": String(facility_id), &"amount": int(wage_allocation[&"allocated_by_facility"][facility_id]), }) var unallocated_rows: Array[Dictionary] = [] for staff_id: StringName in _sorted_name_keys( wage_allocation[&"unallocated_by_staff"] ): unallocated_rows.append({ &"staff_id": String(staff_id), &"amount": int(wage_allocation[&"unallocated_by_staff"][staff_id]), }) var profit_loss_facility_ids: Array[String] = [] for facility_id: StringName in _sorted_name_keys(facility_rows): profit_loss_facility_ids.append(String(facility_id)) anomalies.append({ &"code": &"WAGE_ALLOCATION_EVIDENCE", &"version": 1, &"new_wages_total": new_wages_total, &"allocated_total": int(wage_allocation[&"allocated_total"]), &"unallocated_total": int(wage_allocation[&"unallocated_total"]), &"allocations": allocation_rows, &"unallocated_by_staff": unallocated_rows, &"facility_ids": profit_loss_facility_ids, }) var daily_summary := economy_service.get_daily_summary(period_start / 1_440 + 1) if daily_summary.is_empty(): return {&"ok": false, &"reason": &"LEDGER_PERIOD_SUMMARY_MISSING"} var difference := int(daily_summary.get(&"total_debit", 0)) \ - int(daily_summary.get(&"total_credit", 0)) return { &"ok": true, &"category_totals": category_totals, &"reconciliation_difference": difference, &"facility_profit_loss": facility_rows, &"anomalies": anomalies, } func _allocate_operations_wages_to_facilities( period_start: int, period_end_exclusive: int, new_wages_total: int, payroll_rows: Array, facility_rows: Dictionary ) -> Dictionary: var new_wages_by_staff: Dictionary = {} var observed_new_wages := 0 var previous_staff_text := "" for raw_row: Variant in payroll_rows: if not raw_row is Dictionary: return {&"ok": false, &"reason": &"PAYROLL_ALLOCATION_INPUT_INVALID"} var row: Dictionary = raw_row var staff_id_value: Variant = row.get(&"staff_id", null) var new_wages_value: Variant = row.get(&"new_wages", null) if typeof(staff_id_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(staff_id_value).is_empty() \ or typeof(new_wages_value) != TYPE_INT or int(new_wages_value) < 0: return {&"ok": false, &"reason": &"PAYROLL_ALLOCATION_INPUT_INVALID"} var staff_text := String(staff_id_value) if not previous_staff_text.is_empty() and staff_text <= previous_staff_text: return {&"ok": false, &"reason": &"PAYROLL_ALLOCATION_ORDER_INVALID"} previous_staff_text = staff_text new_wages_by_staff[StringName(staff_id_value)] = int(new_wages_value) observed_new_wages += int(new_wages_value) if observed_new_wages != new_wages_total: return {&"ok": false, &"reason": &"PAYROLL_ALLOCATION_TOTAL_MISMATCH"} var weights_by_staff: Dictionary = {} for shift: ShiftInstance in staff_service.get_shifts(): if shift == null or shift.cancelled or shift.assigned_facility_id.is_empty() \ or not new_wages_by_staff.has(shift.staff_id) \ or int(new_wages_by_staff[shift.staff_id]) <= 0 \ or not facility_service.has_instance(shift.assigned_facility_id): continue var overlap := maxi( mini(shift.end_at, period_end_exclusive) - maxi(shift.start_at, period_start), 0 ) if overlap <= 0: continue # Recorded duty is the strongest attribution fact. A future/unadvanced Shift # still uses its scheduled overlap so daily wages never disappear merely # because the Runtime was restored immediately before the close. var recorded_minutes := shift.worked_minutes + shift.overtime_minutes var weight := mini(recorded_minutes, overlap) if recorded_minutes > 0 else overlap if weight <= 0: continue if not weights_by_staff.has(shift.staff_id): weights_by_staff[shift.staff_id] = {} var facility_weights: Dictionary = weights_by_staff[shift.staff_id] facility_weights[shift.assigned_facility_id] = int( facility_weights.get(shift.assigned_facility_id, 0) ) + weight var anomalies: Array[Dictionary] = [] var allocated_total := 0 var allocated_by_facility: Dictionary = {} var unallocated_by_staff: Dictionary = {} for staff_id: StringName in _sorted_name_keys(new_wages_by_staff): var wage := int(new_wages_by_staff[staff_id]) if wage <= 0: continue var weights: Dictionary = weights_by_staff.get(staff_id, {}) if weights.is_empty(): unallocated_by_staff[staff_id] = wage anomalies.append({ &"code": &"UNALLOCATED_WAGES", &"staff_id": staff_id, &"amount": wage, }) continue var total_weight := 0 for facility_id: StringName in _sorted_name_keys(weights): total_weight += int(weights[facility_id]) if total_weight <= 0: return {&"ok": false, &"reason": &"PAYROLL_ALLOCATION_WEIGHT_INVALID"} var allocations: Dictionary = {} var floor_total := 0 var facility_ids := _sorted_name_keys(weights) for facility_id: StringName in facility_ids: var amount := wage * int(weights[facility_id]) / total_weight allocations[facility_id] = amount floor_total += amount var remainder := wage - floor_total for index in range(remainder): var facility_id: StringName = facility_ids[index] allocations[facility_id] = int(allocations[facility_id]) + 1 for facility_id: StringName in facility_ids: var amount := int(allocations[facility_id]) if amount <= 0: continue if not facility_rows.has(facility_id): facility_rows[facility_id] = { &"revenue": 0, &"expenses": 0, &"net_profit": 0, } var facility_row: Dictionary = facility_rows[facility_id] facility_row[&"expenses"] = int(facility_row[&"expenses"]) + amount allocated_by_facility[facility_id] = int( allocated_by_facility.get(facility_id, 0) ) + amount allocated_total += amount if allocated_total > new_wages_total: return {&"ok": false, &"reason": &"PAYROLL_ALLOCATION_EXCEEDS_TOTAL"} return { &"ok": true, &"allocated_total": allocated_total, &"unallocated_total": new_wages_total - allocated_total, &"allocated_by_facility": allocated_by_facility, &"unallocated_by_staff": unallocated_by_staff, &"anomalies": anomalies, } func _operations_arrival_boundary( payload: Dictionary, phase: StringName, token: Variant ) -> Dictionary: if phase == OperationsCoordinator.PHASE_CAPTURE: var captured := _capture_operations_external_state() var population_cap := ( int(operations_coordinator.get_config().get(&"population_cap", 0)) if operations_coordinator != null else 0 ) return { &"ok": not captured.is_empty(), &"token": captured, &"capacity_remaining": maxi(population_cap - guest_service.guest_count(), 0) if int(_state.get_resource(&"mana")) > 0 and weather.permits_new_arrivals() else 0, } if phase == OperationsCoordinator.PHASE_VALIDATE: return { &"ok": token is Dictionary and not token.is_empty() \ and _operations_arrival_payload_is_valid(payload), &"token": token, } if phase == OperationsCoordinator.PHASE_APPLY: return _apply_operations_arrivals(payload, token) if phase == OperationsCoordinator.PHASE_ROLLBACK: return {&"ok": _restore_operations_external_state(token)} return {&"ok": false} func _operations_arrival_payload_is_valid(payload: Dictionary) -> bool: var plans_value: Variant = payload.get(&"plans", null) var total_value: Variant = payload.get(&"total_count", null) var minute_value: Variant = payload.get(&"minute", null) if not plans_value is Array or typeof(total_value) != TYPE_INT \ or int(total_value) < 0 or typeof(minute_value) != TYPE_INT \ or int(minute_value) != _clock.absolute_minute: return false var allowed_classes: Dictionary = {} for class_id: StringName in mvp_content_catalog.get_guest_class_ids(): allowed_classes[class_id] = true var species_ids := mvp_content_catalog.get_species_ids() if allowed_classes.size() != 8 or species_ids.size() != 4: return false var seen_guest_ids: Dictionary = {} var seen_plan_ids: Dictionary = {} var counted := 0 for raw_plan: Variant in plans_value: if not raw_plan is Dictionary: return false var plan: Dictionary = raw_plan var plan_id_value: Variant = plan.get(&"plan_id", null) var guest_class_value: Variant = plan.get(&"guest_class", null) var facility_tag_value: Variant = plan.get(&"facility_tag", null) var time_band_value: Variant = plan.get(&"time_band", null) var plan_minute_value: Variant = plan.get(&"minute", null) var count_value: Variant = plan.get(&"count", null) var guest_ids_value: Variant = plan.get(&"guest_ids", null) if typeof(plan_id_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or not _operations_sequence_id_is_valid(String(plan_id_value), "arrival-plan-") \ or seen_plan_ids.has(StringName(plan_id_value)) \ or typeof(guest_class_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or not allowed_classes.has(StringName(guest_class_value)) \ or typeof(facility_tag_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(facility_tag_value).is_empty() \ or typeof(time_band_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(time_band_value) not in OperationsCoordinator.TIME_BANDS \ or typeof(plan_minute_value) != TYPE_INT \ or int(plan_minute_value) != int(minute_value) \ or typeof(count_value) != TYPE_INT or int(count_value) <= 0 \ or not guest_ids_value is Array \ or guest_ids_value.size() != int(count_value): return false seen_plan_ids[StringName(plan_id_value)] = true for raw_guest_id: Variant in guest_ids_value: if typeof(raw_guest_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var guest_id := StringName(raw_guest_id) var actor_id := _guest_path_actor_id(guest_id) if not _operations_sequence_id_is_valid(String(guest_id), "guest-auto-") \ or seen_guest_ids.has(guest_id) or guest_service.has_guest(guest_id) \ or not guest_service.get_persistent_record(guest_id).is_empty() \ or economy_service.has_account(_guest_wallet_account(guest_id)) \ or path_service.has_actor_projection(actor_id) \ or _movement_credits.has(actor_id): return false seen_guest_ids[guest_id] = true counted += 1 if counted != int(total_value): return false var population_cap := int(operations_coordinator.get_config().get(&"population_cap", 0)) return guest_service.guest_count() + counted <= population_cap \ and _operations_entrance_cell() != null func _operations_sequence_id_is_valid(value: String, prefix: String) -> bool: if not value.begins_with(prefix) or value.length() != prefix.length() + 8: return false var suffix := value.substr(prefix.length(), 8) if not suffix.is_valid_int(): return false var sequence := int(suffix) return sequence > 0 and value == "%s%08d" % [prefix, sequence] func _operations_entrance_cell() -> Variant: var bounds := world_service.get_bounds() for x in range(int(bounds.get(&"min_x", 0)), int(bounds.get(&"max_x", -1)) + 1): var cell := Vector3i(x, 0, WorldService.LAYER_FRONT) if not world_service.is_in_bounds(cell) \ or world_service.get_zone(cell) != WorldService.ZONE_BOUNDARY: continue var traffic := path_service.get_cell_traffic(cell) if traffic.is_empty() or bool(traffic.get(&"temporary_block", false)): continue return cell return null func _apply_operations_arrivals(payload: Dictionary, token: Variant) -> Dictionary: if not token is Dictionary or token.is_empty() \ or not _operations_arrival_payload_is_valid(payload): return {&"ok": false, &"reason": &"ARRIVAL_PRECONDITION_FAILED"} var prepare_hook := _run_operations_test_boundary_hook( &"ARRIVAL_PLAN", payload, OPERATIONS_TEST_PREPARE_PHASE, token ) if not bool(prepare_hook.get(&"ok", false)): return prepare_hook if int(progression_service.metrics.get(&"guest_lifetime", 0)) \ > ProgressionService.MAX_SAFE_INTEGER - int(payload[&"total_count"]): return {&"ok": false, &"reason": &"ARRIVAL_LIFETIME_LIMIT"} var entrance_value: Variant = _operations_entrance_cell() var spawn_rng := get_rng_stream(&"guest_spawn") var species_ids := mvp_content_catalog.get_species_ids() if entrance_value == null or spawn_rng == null or species_ids.size() != 4: return {&"ok": false, &"reason": &"ARRIVAL_CONTENT_UNAVAILABLE"} var entrance: Vector3i = entrance_value var accepted_count := 0 var arrivals: Array[Dictionary] = [] for raw_plan: Variant in payload[&"plans"]: var plan: Dictionary = raw_plan var guest_class := StringName(plan[&"guest_class"]) for raw_guest_id: Variant in plan[&"guest_ids"]: var guest_id := StringName(raw_guest_id) var species := species_ids[spawn_rng.randi_range(0, species_ids.size() - 1)] var definition_id := StringName("%s_%s" % [guest_class, species]) var definition := guest_service.get_definition(definition_id) if definition == null or not definition.adult_verified \ or definition.wealth_min < 0 or definition.wealth_max < definition.wealth_min: return {&"ok": false, &"reason": &"ARRIVAL_DEFINITION_INVALID"} var wallet_gold := spawn_rng.randi_range( definition.wealth_min, definition.wealth_max ) var command := SimulationCommand.new( 1, _clock.absolute_minute, _clock.absolute_minute, 0, &"spawn_guest", &"operations_coordinator", guest_id, { &"guest_id": guest_id, &"guest_type_id": definition_id, &"gold": wallet_gold, &"options": { &"current_cell": entrance, &"needs": { &"hunger": 0, &"sleep": 0, &"hygiene": 0, &"medical": 0, &"leisure": 0, &"intimacy": 0, }, &"planned_exit_at": _clock.absolute_minute + 720, }, }, StringName("operations.arrival.%s" % guest_id) ) var spawned := _execute_spawn_guest(command) var guest := guest_service.get_guest(guest_id) if not spawned.is_success() or guest == null \ or guest.guest_type_id != definition_id \ or guest.current_cell != entrance \ or guest.wallet_gold != wallet_gold \ or economy_service.get_balance(_guest_wallet_account(guest_id)) != wallet_gold: return { &"ok": false, &"reason": &"ARRIVAL_SPAWN_FAILED", &"guest_id": guest_id, &"result": spawned.to_dictionary(), } var arrival_row := { &"guest_id": guest_id, &"plan_id": StringName(plan[&"plan_id"]), &"guest_class": guest_class, &"species": species, &"definition_id": definition_id, &"adult_verified": true, &"wallet_gold": wallet_gold, &"entrance_cell": entrance, } if not _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"GUEST_AUTO_ARRIVED", _clock.absolute_minute, &"operations_coordinator", [guest_id, definition_id], arrival_row, 0 )): return {&"ok": false, &"reason": &"ARRIVAL_EVENT_FAILED"} arrivals.append(arrival_row) accepted_count += 1 if accepted_count == 1: var after_mutation_hook := _run_operations_test_boundary_hook( &"ARRIVAL_PLAN", payload, OPERATIONS_TEST_AFTER_FIRST_MUTATION_PHASE, token ) if not bool(after_mutation_hook.get(&"ok", false)): return after_mutation_hook var runtime_crosslinks_ok := _validate_runtime_crosslinks( facility_service, guest_service, economy_service, inventory_service, staff_service, _operations_external_committed_minute(), _staffing_projection_enabled ) var path_crosslinks_ok := _validate_runtime_path_crosslinks( path_service, guest_service, staff_service, _movement_credits, _clock.absolute_minute ) if accepted_count != int(payload[&"total_count"]) \ or not runtime_crosslinks_ok or not path_crosslinks_ok: return { &"ok": false, &"reason": &"ARRIVAL_POSTCONDITION_FAILED", &"accepted_count": accepted_count, &"expected_count": int(payload[&"total_count"]), &"runtime_crosslinks_ok": runtime_crosslinks_ok, &"path_crosslinks_ok": path_crosslinks_ok, } return { &"ok": true, &"accepted_count": accepted_count, } func _operations_morning_boundary( payload: Dictionary, phase: StringName, token: Variant ) -> Dictionary: if phase == OperationsCoordinator.PHASE_CAPTURE: var captured := _capture_operations_external_state() return {&"ok": not captured.is_empty(), &"token": captured} if phase == OperationsCoordinator.PHASE_VALIDATE: return { &"ok": token is Dictionary and not token.is_empty() \ and _operations_morning_payload_is_valid(payload), &"token": token, } if phase == OperationsCoordinator.PHASE_APPLY: return _apply_operations_morning(payload, token) if phase == OperationsCoordinator.PHASE_ROLLBACK: return {&"ok": _restore_operations_external_state(token)} return {&"ok": false} func _operations_morning_payload_is_valid(payload: Dictionary) -> bool: var day_value: Variant = payload.get(&"day", null) var minute_value: Variant = payload.get(&"boundary_minute", null) if typeof(day_value) != TYPE_INT or int(day_value) <= 0 \ or typeof(minute_value) != TYPE_INT: return false var minute := int(minute_value) return minute == _clock.absolute_minute \ and posmod(minute, SimulationKernel.MINUTES_PER_DAY) == OPERATIONS_START_MINUTE \ and int(day_value) == minute / SimulationKernel.MINUTES_PER_DAY + 1 func _apply_operations_morning(payload: Dictionary, token: Variant) -> Dictionary: if not token is Dictionary or token.is_empty() \ or not _operations_morning_payload_is_valid(payload): return {&"ok": false, &"reason": &"MORNING_PRECONDITION_FAILED"} var prepare_hook := _run_operations_test_boundary_hook( &"MORNING_BOUNDARY", payload, OPERATIONS_TEST_PREPARE_PHASE, token ) if not bool(prepare_hook.get(&"ok", false)): return prepare_hook var minute := int(payload[&"boundary_minute"]) var before_ap := _personal_action_points _personal_action_points = mini( _personal_action_points + 2, MAX_PERSONAL_ACTION_POINTS ) var authority_advance := authority_service.advance_time(minute) if not authority_advance.is_success(): return { &"ok": false, &"reason": &"MORNING_AUTHORITY_ADVANCE_FAILED", &"result": authority_advance.to_dictionary(), } var authority_evaluation := authority_service.evaluate_inspection(minute) if not authority_evaluation.is_success(): return { &"ok": false, &"reason": &"MORNING_AUTHORITY_EVALUATION_FAILED", &"result": authority_evaluation.to_dictionary(), } if not authority_evaluation.is_replay: _publish_authority_evaluation(authority_evaluation.details) _enqueue_pending_relationship_candidates() # start_next_event owns exactly one expiry pass and one actor audit pass. # Calling those helpers separately here would double-apply morning effects. var event_start := event_service.start_next_event( minute, _build_event_context(minute) ) var no_event_ready := ( not event_start.is_success() and event_start.message == "No eligible Event candidate is ready." ) var foreground_already_active := ( not event_start.is_success() and event_start.message == "A foreground Event is already active." ) if not event_start.is_success() and not no_event_ready and not foreground_already_active: return { &"ok": false, &"reason": &"MORNING_EVENT_START_FAILED", &"result": event_start.to_dictionary(), } if _personal_action_points < 0 \ or _personal_action_points > MAX_PERSONAL_ACTION_POINTS \ or authority_service.current_minute != minute \ or not bool(authority_service.validate_invariants().get(&"ok", false)) \ or not bool(event_service.validate_invariants().get(&"ok", false)) \ or not bool(relationship_service.validate_invariants().get(&"ok", false)): return {&"ok": false, &"reason": &"MORNING_POSTCONDITION_FAILED"} return { &"ok": true, &"event_payload": { &"personal_action_points_before": before_ap, &"personal_action_points_after": _personal_action_points, &"authority_advance": authority_advance.to_dictionary(), &"authority_evaluation": authority_evaluation.to_dictionary(), &"event_start": event_start.to_dictionary(), &"no_event_ready": no_event_ready, &"foreground_already_active": foreground_already_active, }, } func _create_operations_runtime_snapshot() -> Dictionary: return { &"wrapper_version": OPERATIONS_RUNTIME_WRAPPER_VERSION, &"enabled": _operations_enabled, &"personal_action_points": _personal_action_points, &"runtime_failure_latched": _operations_runtime_failure_latched, &"runtime_failure_report": _operations_runtime_failure_report.duplicate(true), &"deferred_runtime_minute": _operations_deferred_runtime_minute, &"coordinator": ( operations_coordinator.create_snapshot() if _operations_enabled and operations_coordinator != null else {} ), } func _parse_operations_runtime_snapshot( raw_wrapper: Variant, runtime_schema_version: int, restored_minute: int, restored_guests: GuestService, restored_staff: StaffService, restored_economy: EconomyService, restored_world_seed: int, restored_clock_paused: Variant, restored_upkeep_settlement: FacilityUpkeepSettlement ) -> Dictionary: var disabled := { &"ok": true, &"enabled": false, &"personal_action_points": 0, &"runtime_failure_latched": false, &"runtime_failure_report": {}, &"deferred_runtime_minute": -1, &"coordinator": null, } if runtime_schema_version < OPERATIONS_RUNTIME_SNAPSHOT_VERSION: return disabled if typeof(restored_clock_paused) != TYPE_BOOL \ or not restored_economy.has_account(WAGE_EXPENSE_ACCOUNT): return {&"ok": false} if not raw_wrapper is Dictionary: return {&"ok": false} var wrapper: Dictionary = raw_wrapper var expected_keys: Array[StringName] = [ &"wrapper_version", &"enabled", &"personal_action_points", &"runtime_failure_latched", &"runtime_failure_report", &"deferred_runtime_minute", &"coordinator", ] if not _dictionary_has_exact_canonical_keys(wrapper, expected_keys): return {&"ok": false} if typeof(wrapper[&"wrapper_version"]) != TYPE_INT \ or int(wrapper[&"wrapper_version"]) != OPERATIONS_RUNTIME_WRAPPER_VERSION \ or typeof(wrapper[&"enabled"]) != TYPE_BOOL \ or typeof(wrapper[&"personal_action_points"]) != TYPE_INT \ or typeof(wrapper[&"runtime_failure_latched"]) != TYPE_BOOL \ or not wrapper[&"runtime_failure_report"] is Dictionary \ or typeof(wrapper[&"deferred_runtime_minute"]) != TYPE_INT \ or not wrapper[&"coordinator"] is Dictionary: return {&"ok": false} var enabled := bool(wrapper[&"enabled"]) var action_points := int(wrapper[&"personal_action_points"]) var failure_latched := bool(wrapper[&"runtime_failure_latched"]) var failure_report: Dictionary = wrapper[&"runtime_failure_report"] var deferred_runtime_minute := int(wrapper[&"deferred_runtime_minute"]) var coordinator_snapshot: Dictionary = wrapper[&"coordinator"] if action_points < 0 or action_points > MAX_PERSONAL_ACTION_POINTS \ or failure_latched != not failure_report.is_empty() \ or (failure_latched \ and not _operations_failure_report_is_valid(failure_report, restored_minute)): return {&"ok": false} if not enabled: if action_points != 0 or failure_latched or deferred_runtime_minute != -1 \ or not coordinator_snapshot.is_empty(): return {&"ok": false} return disabled var restored_coordinator := OperationsCoordinator.new() if not restored_coordinator.restore_snapshot(coordinator_snapshot): return {&"ok": false} var coordinator_current_matches := restored_coordinator.current_minute == restored_minute var pending_boundary_minute := int( coordinator_snapshot.get(&"pending_boundary_minute", -1) ) var deferred_boundary_minute := int( coordinator_snapshot.get(&"deferred_boundary_minute", -1) ) var normal_shape := deferred_runtime_minute == -1 \ and not failure_latched and not restored_coordinator.pause_requested \ and not restored_coordinator.is_terminal() \ and coordinator_current_matches var failure_shape := deferred_runtime_minute == restored_minute \ and failure_latched and bool(restored_clock_paused) \ and restored_coordinator.current_minute == restored_minute - 1 \ and pending_boundary_minute == restored_minute var pause_shape := deferred_runtime_minute == restored_minute \ and not failure_latched and bool(restored_clock_paused) \ and restored_coordinator.pause_requested and coordinator_current_matches \ and deferred_boundary_minute == restored_minute var terminal_shape := deferred_runtime_minute == -1 \ and not failure_latched and restored_coordinator.is_terminal() \ and coordinator_current_matches and bool(restored_clock_paused) if not bool(restored_coordinator.validate_invariants().get(&"ok", false)) \ or restored_coordinator.get_origin_minute() != OPERATIONS_COORDINATOR_ORIGIN_MINUTE \ or not (normal_shape or failure_shape or pause_shape or terminal_shape) \ or restored_coordinator.get_population_count() != restored_guests.guest_count() \ or restored_coordinator.get_arrears_total() != (_staff_payroll_arrears_total(restored_staff) + restored_upkeep_settlement.get_arrears_through(restored_coordinator.get_daily_close_minute(restored_coordinator.get_open_financial_day() - 1))) \ or not _operations_payroll_crosslinks_are_valid( restored_coordinator, restored_staff, restored_economy, restored_upkeep_settlement ) \ or not _operations_coordinator_configuration_is_canonical( restored_coordinator, restored_world_seed ) \ or not _operations_arrival_guest_crosslinks_are_valid( restored_coordinator, restored_guests ) \ or (restored_coordinator.is_external_quarantined() \ and not (failure_shape or pause_shape)): return {&"ok": false} return { &"ok": true, &"enabled": true, &"personal_action_points": action_points, &"runtime_failure_latched": failure_latched, &"runtime_failure_report": failure_report.duplicate(true), &"deferred_runtime_minute": deferred_runtime_minute, &"coordinator": restored_coordinator, } func _progression_guest_lifetime_matches( progression: ProgressionService, guests: GuestService ) -> bool: if progression == null or guests == null: return false var guest_snapshot := guests.create_snapshot() var persistent_value: Variant = guest_snapshot.get(&"persistent_records", null) var metrics_value: Variant = progression.create_snapshot().get(&"metrics", null) if not persistent_value is Array or not metrics_value is Dictionary: return false var lifetime_value: Variant = (metrics_value as Dictionary).get(&"guest_lifetime", null) return typeof(lifetime_value) == TYPE_INT \ and int(lifetime_value) == guests.guest_count() + persistent_value.size() func _operations_arrival_guest_crosslinks_are_valid( coordinator: OperationsCoordinator, guests: GuestService ) -> bool: if coordinator == null or guests == null: return false var definitions_by_guest: Dictionary = {} for guest: GuestRuntime in guests.get_guests(): definitions_by_guest[guest.guest_id] = guest.guest_type_id var guest_snapshot := guests.create_snapshot() var persistent_value: Variant = guest_snapshot.get(&"persistent_records", null) if not persistent_value is Array: return false for raw_record: Variant in persistent_value: if not raw_record is Dictionary: return false var guest_id := StringName((raw_record as Dictionary).get(&"guest_id", &"")) var definition_id := StringName( (raw_record as Dictionary).get(&"guest_type_id", &"") ) if guest_id.is_empty() or definition_id.is_empty() \ or definitions_by_guest.has(guest_id): return false definitions_by_guest[guest_id] = definition_id var history_ids: Dictionary = {} var expected_sequence := 1 for report: Dictionary in coordinator.get_arrival_reports(): var plans_value: Variant = report.get(&"plans", null) if not plans_value is Array: return false for raw_plan: Variant in plans_value: if not raw_plan is Dictionary: return false var plan: Dictionary = raw_plan var guest_class := StringName(plan.get(&"guest_class", &"")) var ids_value: Variant = plan.get(&"guest_ids", null) if guest_class.is_empty() or not ids_value is Array: return false for raw_guest_id: Variant in ids_value: if typeof(raw_guest_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var guest_id := StringName(raw_guest_id) if guest_id != StringName("guest-auto-%08d" % expected_sequence) \ or history_ids.has(guest_id) \ or not definitions_by_guest.has(guest_id): return false var definition_id := String(definitions_by_guest[guest_id]) if not definition_id.begins_with("%s_" % String(guest_class)): return false history_ids[guest_id] = true expected_sequence += 1 for raw_guest_id: Variant in definitions_by_guest: var guest_id := StringName(raw_guest_id) if String(guest_id).begins_with("guest-auto-") \ and not history_ids.has(guest_id): return false return true func _guest_aggregate_has_reserved_operations_id(guests: GuestService) -> bool: if guests == null: return true for guest: GuestRuntime in guests.get_guests(): if String(guest.guest_id).begins_with("guest-auto-"): return true var records: Variant = guests.create_snapshot().get(&"persistent_records", null) if not records is Array: return true for raw_record: Variant in records: if not raw_record is Dictionary \ or String((raw_record as Dictionary).get(&"guest_id", "")).begins_with( "guest-auto-" ): return true return false func _operations_payroll_crosslinks_are_valid( coordinator: OperationsCoordinator, staff: StaffService, economy: EconomyService, upkeep: FacilityUpkeepSettlement ) -> bool: if coordinator == null or staff == null or economy == null: return false var staff_snapshot := staff.create_snapshot() var settlement_values: Variant = staff_snapshot.get(&"payroll_settlements", null) if not settlement_values is Array: return false var summaries := coordinator.get_day_summaries() if summaries.size() != settlement_values.size(): return false var expected_wage_transactions: Dictionary = {} var settlements_by_day: Dictionary = {} for raw_settlement: Variant in settlement_values: if not raw_settlement is Dictionary: return false var serialized: Dictionary = raw_settlement var day_value: Variant = serialized.get(&"day", null) var settlement_id_value: Variant = serialized.get(&"settlement_id", null) if typeof(day_value) != TYPE_INT \ or typeof(settlement_id_value) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var day := int(day_value) var settlement_id := StringName(settlement_id_value) if day <= 0 or settlement_id != StringName("payroll-day-%08d" % day) \ or settlements_by_day.has(day): return false var staff_record := staff.get_payroll_settlement(settlement_id) if staff_record.is_empty(): return false settlements_by_day[day] = staff_record var paid_total := int(staff_record.get(&"paid_total", -1)) if paid_total < 0: return false var idempotency_key := StringName("operations.wage.day.%08d" % day) var transaction_id := economy.get_transaction_id_for_idempotency_key(idempotency_key) if paid_total == 0: if not transaction_id.is_empty(): return false continue if transaction_id.is_empty() or expected_wage_transactions.has(transaction_id): return false var entries := economy.get_transaction_entries(transaction_id) if entries.size() != 1: return false var entry: LedgerEntry = entries[0] if entry.absolute_minute != coordinator.get_daily_close_minute(day) \ or entry.debit_account != WAGE_EXPENSE_ACCOUNT \ or entry.credit_account != PLAYER_CASH_ACCOUNT \ or entry.amount != paid_total or entry.category != &"WAGE" \ or entry.source_entity_id != settlement_id \ or entry.memo_key != &"operations.payroll.settlement" \ or not entry.related_transaction_id.is_empty(): return false expected_wage_transactions[transaction_id] = true for summary: Dictionary in summaries: var day := int(summary.get(&"day", -1)) if not settlements_by_day.has(day): return false var summary_settlement_value: Variant = summary.get(&"settlement", null) if not summary_settlement_value is Dictionary: return false var summary_settlement: Dictionary = summary_settlement_value.duplicate(true) var staff_record: Dictionary = settlements_by_day[day] var expected_categories: Variant = _operations_expected_category_totals( economy, coordinator.get_financial_period_start(day), coordinator.get_daily_close_minute(day), int(staff_record.get(&"new_wages_total", -1)) ) if expected_categories == null \ or not _strict_variants_equal( _operations_snapshot_comparison_value( summary.get(&"category_totals", {}) ), _operations_snapshot_comparison_value(expected_categories) ) \ or not _operations_facility_wage_summary_is_valid( summary, staff_record, staff, economy ): return false var upkeep_debt := 0 for receipt: Dictionary in upkeep.get_report()[&"receipts"]: if int(receipt[&"period"][&"period_end"]) <= coordinator.get_daily_close_minute(day): upkeep_debt = 0 for row: Dictionary in receipt[&"rows"]: upkeep_debt += int(row[&"arrears"]) if summary_settlement.has(&"upkeep_arrears_total"): if typeof(summary_settlement[&"upkeep_arrears_total"]) != TYPE_INT or int(summary_settlement[&"upkeep_arrears_total"]) != upkeep_debt: return false summary_settlement.erase(&"upkeep_arrears_total") elif upkeep_debt != 0: return false if int(summary_settlement.get(&"arrears_total", -1)) \ != int(staff_record.get(&"unpaid_after_total", -2)) + upkeep_debt \ or summary_settlement.get(&"replayed", null) != false: return false summary_settlement.erase(&"arrears_total") summary_settlement.erase(&"replayed") # OperationsCoordinator persists history in a transport-safe form, so # StringName identifiers become String values after a round-trip. Compare # the redundant Staff/Coordinator evidence in that same canonical form; # numeric and structural types remain strict. if not _strict_variants_equal( _operations_snapshot_comparison_value(summary_settlement), _operations_snapshot_comparison_value(staff_record) ): return false for entry: LedgerEntry in economy.get_entries(): if entry.category == &"WAGE" \ and not expected_wage_transactions.has(entry.transaction_id): return false return true ## Existing liabilities only. This projection does not issue financing or infer terms. func get_loan_principal_report() -> Dictionary: if not _initialized or economy_service == null or _state == null: return {&"available": false, &"loans": []} var cash := economy_service.get_balance(PLAYER_CASH_ACCOUNT) var pending: Dictionary = {} for command: SimulationCommand in command_queue.get_pending_commands(): if command.command_type != &"repay_loan_principal": continue var id: Variant = command.payload.get(&"loan_id", null) if typeof(id) not in [TYPE_STRING, TYPE_STRING_NAME]: continue var key := StringName(id) if not pending.has(key): pending[key] = [] pending[key].append(command.command_id) var loans: Array[Dictionary] = [] for account: Dictionary in economy_service.get_accounts(): var id := String(account[&"account_id"]) if not id.begins_with("LOAN_LIABILITY:"): continue var loan := StringName(id.trim_prefix("LOAN_LIABILITY:")) loans.append({&"loan_id": loan, &"outstanding_principal": -economy_service.get_balance(StringName(id)), &"pending_command_ids": (pending.get(loan, []) as Array).duplicate()}) loans.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return String(left[&"loan_id"]) < String(right[&"loan_id"])) var valid: bool = _state.get_resource(&"gold") == cash and (loans.is_empty() or _loan_principal_ledger_is_valid(economy_service)) var ended: bool = _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal() return {&"available": true, &"ledger_valid": valid, &"terminal": ended, &"cash": cash, &"next_command_id": _next_command_id, &"loans": loans} ## Atomic principal payment against an already attested loan liability. ## Loan issuance, rates, due dates and bank eligibility belong to the bank ## producer; this API does not create or approve any financing contract. func repay_loan_principal( loan_id: StringName, amount: Variant, idempotency_key: StringName ) -> CommandResult: if not _initialized or economy_service == null or _state == null: return CommandResult.failed(CommandResult.Code.EXECUTOR_FAILED, 0, "Simulation runtime is not initialized.") if _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "Repayment is unavailable after the game ends.") if loan_id.is_empty() or idempotency_key.is_empty() or typeof(amount) != TYPE_INT \ or amount <= 0 or amount > OperationsCoordinator.MAX_FINANCIAL_AMOUNT: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, 0, "A loan id, positive integer principal and idempotency key are required.") if not economy_service.get_transaction_id_for_idempotency_key(idempotency_key).is_empty(): return CommandResult.failed(CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY, 0, "The principal payment key has already been posted.") if not _loan_principal_ledger_is_valid(economy_service): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "The principal ledger is inconsistent.") var liability := StringName("LOAN_LIABILITY:" + String(loan_id)) if not economy_service.has_account(liability): return CommandResult.failed(CommandResult.Code.NOT_FOUND, 0, "The loan liability does not exist.") var remaining := -economy_service.get_balance(liability) var cash := economy_service.get_balance(PLAYER_CASH_ACCOUNT) if amount > remaining or amount > cash or _state.get_resource(&"gold") != cash: return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "Principal payment exceeds the outstanding principal or available Gold, or its mirror is inconsistent.") # The single balanced posting is atomic. Defer receipts until the Gold # mirror is synchronized, also respecting any enclosing effect scope. economy_service.begin_notification_scope() var result := economy_service.transfer( PLAYER_CASH_ACCOUNT, liability, amount, _runtime_event_minute(), &"LOAN_PRINCIPAL_REPAID", loan_id, idempotency_key ) if result.is_success(): _state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT)) result.details[&"loan_id"] = loan_id result.details[&"paid_principal"] = amount result.details[&"outstanding_principal"] = remaining - amount economy_service.end_notification_scope() return result func _loan_principal_ledger_is_valid(economy: EconomyService) -> bool: if economy == null: return false var outstanding: Dictionary = {} for entry: LedgerEntry in economy.get_entries(): if entry.category not in [&"LOAN_PRINCIPAL_BORROWED", &"LOAN_PRINCIPAL_REPAID"]: continue if entry.source_entity_id.is_empty(): return false var liability := StringName("LOAN_LIABILITY:" + String(entry.source_entity_id)) if not economy.has_account(liability) or not economy.account_allows_negative(liability): return false var remaining := int(outstanding.get(liability, 0)) if entry.category == &"LOAN_PRINCIPAL_BORROWED": if entry.debit_account != PLAYER_CASH_ACCOUNT or entry.credit_account != liability \ or entry.amount > OperationsCoordinator.MAX_FINANCIAL_AMOUNT - remaining: return false remaining += entry.amount else: if entry.debit_account != liability or entry.credit_account != PLAYER_CASH_ACCOUNT \ or entry.amount > remaining: return false remaining -= entry.amount outstanding[liability] = remaining for account: Dictionary in economy.get_accounts(): var id: StringName = account[&"account_id"] if String(id).begins_with("LOAN_LIABILITY:"): if not economy.account_allows_negative(id) or economy.get_balance(id) != -int(outstanding.get(id, 0)): return false return true func _operations_expected_category_totals( economy: EconomyService, period_start: int, period_end_exclusive: int, new_wages_total: int ) -> Variant: if economy == null or period_start < 0 or period_end_exclusive <= period_start \ or new_wages_total < 0: return null if not _loan_principal_ledger_is_valid(economy): return null var totals := { &"revenue": 0, &"refunds": 0, &"cost_of_goods": 0, &"wages": new_wages_total, &"maintenance": 0, &"utilities": 0, &"taxes_and_interest": 0, &"fines": 0, &"construction_cash_flow": 0, } var excluded: Array[StringName] = [ &"EVENT_COST_RESERVED", &"EVENT_COST_RELEASED", &"INITIALIZATION", &"TRANSFER", &"GENERATION", &"DESTRUCTION", &"MIGRATION", &"SERVICE_CASH_SWEEP", &"WAGE", &"MAINTENANCE_PAYMENT", ] var mapping := { &"SERVICE_SALE": &"revenue", &"REFUND": &"refunds", &"CONSTRUCTION": &"construction_cash_flow", &"CONSTRUCTION_REFUND": &"construction_cash_flow", &"AUTHORITY_FINE": &"fines", &"OPERATING_EXPENSE": &"cost_of_goods", &"STAFF_SIGNING_COST": &"cost_of_goods", &"EVENT_ACTION": &"cost_of_goods", &"EVENT_COST_COMMITTED": &"cost_of_goods", &"DEFENSE_SKILL_COST": &"cost_of_goods", &"MAINTENANCE_ACCRUAL": &"maintenance", &"LOAN_PRINCIPAL_BORROWED": &"financing_principal_inflows", &"LOAN_PRINCIPAL_REPAID": &"financing_principal_outflows", } var operating_refunds := 0 for entry: LedgerEntry in economy.get_entries(): if entry.absolute_minute < period_start \ or entry.absolute_minute >= period_end_exclusive: continue if entry.category in excluded: continue if entry.category == &"OPERATING_REFUND": operating_refunds += entry.amount continue if not mapping.has(entry.category): return null var category: StringName = mapping[entry.category] totals[category] = int(totals.get(category, 0)) + entry.amount var offset := mini(operating_refunds, int(totals[&"cost_of_goods"])) totals[&"cost_of_goods"] = int(totals[&"cost_of_goods"]) - offset totals[&"revenue"] = int(totals[&"revenue"]) + operating_refunds - offset return totals func _operations_facility_wage_summary_is_valid( summary: Dictionary, staff_record: Dictionary, _staff: StaffService, economy: EconomyService ) -> bool: var facilities_value: Variant = summary.get(&"facility_profit_loss", null) var anomalies_value: Variant = summary.get(&"anomalies", null) if not facilities_value is Dictionary or not anomalies_value is Array: return false var evidence: Dictionary = {} var observed_unallocated: Dictionary = {} for raw_anomaly: Variant in anomalies_value: if not raw_anomaly is Dictionary: return false var anomaly: Dictionary = raw_anomaly match StringName(anomaly.get(&"code", &"")): &"WAGE_ALLOCATION_EVIDENCE": if not evidence.is_empty() or not _dictionary_has_exact_canonical_keys( anomaly, [&"code", &"version", &"new_wages_total", &"allocated_total", &"unallocated_total", &"allocations", &"unallocated_by_staff", &"facility_ids"] ): return false evidence = anomaly &"UNALLOCATED_WAGES": if not _dictionary_has_exact_canonical_keys( anomaly, [&"code", &"staff_id", &"amount"] ): return false var staff_id := StringName(anomaly.get(&"staff_id", &"")) var amount_value: Variant = anomaly.get(&"amount", null) if staff_id.is_empty() or typeof(amount_value) != TYPE_INT \ or int(amount_value) <= 0 or observed_unallocated.has(staff_id): return false observed_unallocated[staff_id] = int(amount_value) &"OPERATING_REFUND_EXCEEDS_PERIOD_COST": if not _dictionary_has_exact_canonical_keys( anomaly, [&"code", &"refund_total", &"cost_offset", &"excess_revenue"] ): return false _: return false if evidence.is_empty() or int(evidence.get(&"version", -1)) != 1 \ or typeof(evidence.get(&"new_wages_total", null)) != TYPE_INT \ or int(evidence[&"new_wages_total"]) \ != int(staff_record.get(&"new_wages_total", -1)) \ or typeof(evidence.get(&"allocated_total", null)) != TYPE_INT \ or typeof(evidence.get(&"unallocated_total", null)) != TYPE_INT \ or not evidence.get(&"allocations", null) is Array \ or not evidence.get(&"unallocated_by_staff", null) is Array \ or not evidence.get(&"facility_ids", null) is Array: return false var allocations: Dictionary = {} var previous_facility := "" var allocated_total := 0 for raw_row: Variant in evidence[&"allocations"]: if not raw_row is Dictionary or not _dictionary_has_exact_canonical_keys( raw_row, [&"facility_id", &"amount"] ): return false var facility_id := StringName((raw_row as Dictionary).get(&"facility_id", &"")) var amount_value: Variant = (raw_row as Dictionary).get(&"amount", null) if facility_id.is_empty() or typeof(amount_value) != TYPE_INT \ or int(amount_value) <= 0 or allocations.has(facility_id) \ or (not previous_facility.is_empty() \ and String(facility_id) <= previous_facility): return false allocations[facility_id] = int(amount_value) allocated_total += int(amount_value) previous_facility = String(facility_id) var unallocated: Dictionary = {} var previous_staff := "" var unallocated_total := 0 for raw_row: Variant in evidence[&"unallocated_by_staff"]: if not raw_row is Dictionary or not _dictionary_has_exact_canonical_keys( raw_row, [&"staff_id", &"amount"] ): return false var staff_id := StringName((raw_row as Dictionary).get(&"staff_id", &"")) var amount_value: Variant = (raw_row as Dictionary).get(&"amount", null) if staff_id.is_empty() or typeof(amount_value) != TYPE_INT \ or int(amount_value) <= 0 or unallocated.has(staff_id) \ or (not previous_staff.is_empty() and String(staff_id) <= previous_staff): return false unallocated[staff_id] = int(amount_value) unallocated_total += int(amount_value) previous_staff = String(staff_id) var expected_facility_ids: Dictionary = {} var previous_id := "" for raw_id: Variant in evidence[&"facility_ids"]: if typeof(raw_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var facility_id := StringName(raw_id) if facility_id.is_empty() or expected_facility_ids.has(facility_id) \ or (not previous_id.is_empty() and String(facility_id) <= previous_id): return false expected_facility_ids[facility_id] = true previous_id = String(facility_id) var new_wages_total := int(evidence[&"new_wages_total"]) if allocated_total != int(evidence[&"allocated_total"]) \ or unallocated_total != int(evidence[&"unallocated_total"]) \ or allocated_total + unallocated_total != new_wages_total \ or not _strict_variants_equal(observed_unallocated, unallocated): return false for facility_id: StringName in allocations: if not expected_facility_ids.has(facility_id): return false var facility_rows: Dictionary = facilities_value if facility_rows.size() != expected_facility_ids.size(): return false for raw_id: Variant in facility_rows: if not expected_facility_ids.has(StringName(raw_id)): return false var period_start := int(summary.get(&"period_start_minute", -1)) var period_end := int(summary.get(&"period_end_minute_exclusive", -1)) if period_start < 0 or period_end <= period_start: return false var base_rows: Dictionary = {} for facility_id: StringName in expected_facility_ids: base_rows[facility_id] = {&"revenue": 0, &"expenses": 0} var excluded: Array[StringName] = [ &"EVENT_COST_RESERVED", &"EVENT_COST_RELEASED", &"INITIALIZATION", &"TRANSFER", &"GENERATION", &"DESTRUCTION", &"MIGRATION", &"SERVICE_CASH_SWEEP", &"WAGE", &"MAINTENANCE_PAYMENT", &"OPERATING_REFUND", ] var mapping := { &"SERVICE_SALE": &"revenue", &"REFUND": &"expenses", &"AUTHORITY_FINE": &"expenses", &"OPERATING_EXPENSE": &"expenses", &"STAFF_SIGNING_COST": &"expenses", &"EVENT_ACTION": &"expenses", &"EVENT_COST_COMMITTED": &"expenses", &"DEFENSE_SKILL_COST": &"expenses", &"MAINTENANCE_ACCRUAL": &"expenses", } for entry: LedgerEntry in economy.get_entries(): if entry.absolute_minute < period_start or entry.absolute_minute >= period_end \ or entry.category in excluded \ or entry.category in [&"CONSTRUCTION", &"CONSTRUCTION_REFUND"] \ or not expected_facility_ids.has(entry.source_entity_id): continue if not mapping.has(entry.category): return false var base: Dictionary = base_rows[entry.source_entity_id] var key: StringName = mapping[entry.category] base[key] = int(base[key]) + entry.amount for facility_id: StringName in expected_facility_ids: var row_value: Variant = facility_rows.get(facility_id, null) if not row_value is Dictionary: return false var row: Dictionary = row_value var base: Dictionary = base_rows[facility_id] var expected_revenue := int(base[&"revenue"]) var expected_expenses := int(base[&"expenses"]) \ + int(allocations.get(facility_id, 0)) if int(row.get(&"revenue", -1)) != expected_revenue \ or int(row.get(&"expenses", -1)) != expected_expenses \ or int(row.get(&"net_profit", 0)) != expected_revenue - expected_expenses: return false return true func _operations_facility_wage_summary_from_live_shifts_is_valid( summary: Dictionary, staff_record: Dictionary, staff: StaffService, economy: EconomyService ) -> bool: var facilities_value: Variant = summary.get(&"facility_profit_loss", null) var rows_value: Variant = staff_record.get(&"rows", null) var anomalies_value: Variant = summary.get(&"anomalies", null) if not facilities_value is Dictionary or not rows_value is Array \ or not anomalies_value is Array: return false var facility_rows: Dictionary = facilities_value var period_start := int(summary.get(&"period_start_minute", -1)) var period_end := int(summary.get(&"period_end_minute_exclusive", -1)) if period_start < 0 or period_end <= period_start: return false var wages_by_staff: Dictionary = {} for raw_row: Variant in rows_value: if not raw_row is Dictionary: return false var row: Dictionary = raw_row var staff_id := StringName(row.get(&"staff_id", &"")) var wage_value: Variant = row.get(&"new_wages", null) if staff_id.is_empty() or typeof(wage_value) != TYPE_INT \ or int(wage_value) < 0 or wages_by_staff.has(staff_id): return false wages_by_staff[staff_id] = int(wage_value) var weights_by_staff: Dictionary = {} for shift: ShiftInstance in staff.get_shifts(): if shift == null or shift.cancelled or shift.assigned_facility_id.is_empty() \ or not wages_by_staff.has(shift.staff_id) \ or int(wages_by_staff[shift.staff_id]) <= 0: continue var overlap := maxi( mini(shift.end_at, period_end) - maxi(shift.start_at, period_start), 0 ) if overlap <= 0: continue var recorded := shift.worked_minutes + shift.overtime_minutes var weight := mini(recorded, overlap) if recorded > 0 else overlap if weight <= 0: continue if not weights_by_staff.has(shift.staff_id): weights_by_staff[shift.staff_id] = {} var weights: Dictionary = weights_by_staff[shift.staff_id] weights[shift.assigned_facility_id] = int( weights.get(shift.assigned_facility_id, 0) ) + weight var expected_allocations: Dictionary = {} var expected_unallocated: Dictionary = {} for staff_id: StringName in _sorted_name_keys(wages_by_staff): var wage := int(wages_by_staff[staff_id]) if wage <= 0: continue var weights: Dictionary = weights_by_staff.get(staff_id, {}) if weights.is_empty(): expected_unallocated[staff_id] = wage continue var facility_ids := _sorted_name_keys(weights) var total_weight := 0 for facility_id: StringName in facility_ids: total_weight += int(weights[facility_id]) if total_weight <= 0: return false var allocated := 0 for facility_id: StringName in facility_ids: var amount := wage * int(weights[facility_id]) / total_weight expected_allocations[facility_id] = int( expected_allocations.get(facility_id, 0) ) + amount allocated += amount var remainder := wage - allocated for index in range(remainder): var facility_id: StringName = facility_ids[index] expected_allocations[facility_id] = int(expected_allocations[facility_id]) + 1 var base_rows: Dictionary = {} var excluded: Array[StringName] = [ &"EVENT_COST_RESERVED", &"EVENT_COST_RELEASED", &"INITIALIZATION", &"TRANSFER", &"GENERATION", &"DESTRUCTION", &"MIGRATION", &"SERVICE_CASH_SWEEP", &"WAGE", &"MAINTENANCE_PAYMENT", &"OPERATING_REFUND", ] var mapping := { &"SERVICE_SALE": &"revenue", &"REFUND": &"expense", &"AUTHORITY_FINE": &"expense", &"OPERATING_EXPENSE": &"expense", &"STAFF_SIGNING_COST": &"expense", &"EVENT_ACTION": &"expense", &"EVENT_COST_COMMITTED": &"expense", &"DEFENSE_SKILL_COST": &"expense", } for entry: LedgerEntry in economy.get_entries(): if entry.absolute_minute < period_start or entry.absolute_minute >= period_end \ or entry.category in excluded \ or entry.category in [&"CONSTRUCTION", &"CONSTRUCTION_REFUND"] \ or entry.source_entity_id.is_empty() \ or not facility_rows.has(entry.source_entity_id): continue if not mapping.has(entry.category): return false if not base_rows.has(entry.source_entity_id): base_rows[entry.source_entity_id] = {&"revenue": 0, &"expenses": 0} var base: Dictionary = base_rows[entry.source_entity_id] var key: StringName = mapping[entry.category] if key == &"revenue": base[&"revenue"] = int(base[&"revenue"]) + entry.amount else: base[&"expenses"] = int(base[&"expenses"]) + entry.amount for raw_facility_id: Variant in facility_rows: var facility_id := StringName(raw_facility_id) var row_value: Variant = facility_rows[raw_facility_id] if facility_id.is_empty() or not row_value is Dictionary: return false var row: Dictionary = row_value var base: Dictionary = base_rows.get( facility_id, {&"revenue": 0, &"expenses": 0} ) if int(row.get(&"revenue", -1)) != int(base[&"revenue"]) \ or int(row.get(&"expenses", -1)) \ != int(base[&"expenses"]) + int(expected_allocations.get(facility_id, 0)): return false for facility_id: StringName in expected_allocations: if not facility_rows.has(facility_id): return false var observed_unallocated: Dictionary = {} for raw_anomaly: Variant in anomalies_value: if not raw_anomaly is Dictionary: return false var anomaly: Dictionary = raw_anomaly if StringName(anomaly.get(&"code", &"")) != &"UNALLOCATED_WAGES": continue var staff_id := StringName(anomaly.get(&"staff_id", &"")) var amount_value: Variant = anomaly.get(&"amount", null) if staff_id.is_empty() or typeof(amount_value) != TYPE_INT \ or int(amount_value) <= 0 or observed_unallocated.has(staff_id): return false observed_unallocated[staff_id] = int(amount_value) return _strict_variants_equal(observed_unallocated, expected_unallocated) func _operations_coordinator_configuration_is_canonical( coordinator: OperationsCoordinator, expected_world_seed: int ) -> bool: if coordinator == null: return false var expected := OperationsCoordinator.new({ &"start_minute": OPERATIONS_COORDINATOR_ORIGIN_MINUTE, &"world_seed": expected_world_seed, }) if not _strict_variants_equal(coordinator.get_config(), expected.get_config()): return false var profiles := coordinator.get_demand_profiles() if profiles.size() != DEFAULT_OPERATIONS_DEMAND_PROFILES.size(): return false var expected_identities: Dictionary = {} for raw_profile: Variant in DEFAULT_OPERATIONS_DEMAND_PROFILES: var profile: Dictionary = raw_profile var key := "%s|%s|%s" % [ profile[&"facility_tag"], profile[&"guest_class"], profile[&"time_band"], ] expected_identities[key] = true var seen: Dictionary = {} var expected_components := { &"base": 100, &"reputation": 0, &"unmet_need": 0, &"event": 0, &"recent_price_penalty": 0, &"wait_penalty": 0, } for profile: Dictionary in profiles: var key := "%s|%s|%s" % [ profile.get(&"facility_tag", &""), profile.get(&"guest_class", &""), profile.get(&"time_band", &""), ] if not expected_identities.has(key) or seen.has(key) \ or not _strict_variants_equal( profile.get(&"components", {}), expected_components ): return false seen[key] = true return seen.size() == expected_identities.size() func _staff_payroll_arrears_total(service: StaffService) -> int: var total := 0 for row: Dictionary in service.get_payroll_obligations(): var unpaid: Variant = row.get(&"unpaid_wages", null) if typeof(unpaid) != TYPE_INT or int(unpaid) < 0: return -1 total += int(unpaid) return total func _operations_failure_report_is_valid(report: Dictionary, minute: int) -> bool: if report.size() == 2 \ and _dictionary_has_exact_canonical_keys(report, [&"code", &"minute"]): var simple_code: Variant = report.get(&"code", null) var simple_minute: Variant = report.get(&"minute", null) return typeof(simple_code) in [TYPE_STRING, TYPE_STRING_NAME] \ and not String(simple_code).is_empty() \ and typeof(simple_minute) == TYPE_INT and int(simple_minute) == minute var result_keys: Array[StringName] = [ &"code", &"command_id", &"target_id", &"message", &"resolution_hints", &"details", &"is_replay", ] if not _dictionary_has_exact_canonical_keys(report, result_keys): return false var code_value: Variant = report.get(&"code", null) var command_id_value: Variant = report.get(&"command_id", null) var target_value: Variant = report.get(&"target_id", null) var message_value: Variant = report.get(&"message", null) var hints_value: Variant = report.get(&"resolution_hints", null) var details_value: Variant = report.get(&"details", null) if typeof(code_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or String(code_value).is_empty() or String(code_value) == "OK" \ or typeof(command_id_value) != TYPE_INT or int(command_id_value) < 0 \ or typeof(target_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or typeof(message_value) != TYPE_STRING \ or not hints_value is Array or hints_value.size() > 32 \ or not details_value is Dictionary \ or typeof(report.get(&"is_replay", null)) != TYPE_BOOL: return false for raw_hint: Variant in hints_value: if typeof(raw_hint) != TYPE_STRING or String(raw_hint).length() > 4_096: return false return JSON.stringify(details_value).length() <= 262_144 func _dictionary_has_exact_canonical_keys( source: Dictionary, expected_keys: Array[StringName] ) -> bool: if source.size() != expected_keys.size(): return false var seen: Dictionary = {} for raw_key: Variant in source: if typeof(raw_key) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var key := StringName(raw_key) if not expected_keys.has(key) or seen.has(key): return false seen[key] = true return seen.size() == expected_keys.size() func _operations_api_failure( code: StringName, message: String, details: Dictionary = {} ) -> Dictionary: return { &"ok": false, &"code": code, &"message": message, &"details": details.duplicate(true), } func register_save_migration_step( from_version: int, migration_id: StringName, migrator: Callable ) -> Dictionary: return save_migration_registry.register_step(from_version, migration_id, migrator) func queue_checkpoint( save_path := "", metadata := {}, use_autosave := false, reason: StringName = &"manual" ) -> Dictionary: if not _initialized: return { &"ok": false, &"code": &"RUNTIME_NOT_INITIALIZED", &"message": "Simulation runtime is not initialized.", } var resolved_path := str(save_path) if resolved_path.is_empty(): resolved_path = str(_save_policy[&"autosave_path"]) var request_id := _next_checkpoint_request_id _next_checkpoint_request_id += 1 _pending_checkpoints.append({ &"request_id": request_id, &"save_path": resolved_path, &"metadata": metadata.duplicate(true) if metadata is Dictionary else {}, &"use_autosave": bool(use_autosave), &"reason": reason if not reason.is_empty() else &"manual", }) return { &"ok": true, &"code": &"CHECKPOINT_QUEUED", &"request_id": request_id, &"save_path": resolved_path, &"queued_for_tick_boundary": true, &"queued_at_absolute_minute": _clock.absolute_minute, } func queue_manual_checkpoint(save_path: String, metadata := {}) -> Dictionary: return queue_checkpoint(save_path, metadata, false, &"manual") func save_to_path(save_path: String, metadata := {}) -> Dictionary: return _perform_checkpoint_save(save_path, metadata, false, &"manual", 0) func inspect_recovery_candidates( save_path := "", generation_count := -1 ) -> Dictionary: var resolved_path := str(save_path) if resolved_path.is_empty(): resolved_path = str(_save_policy[&"autosave_path"]) var resolved_generations := generation_count if resolved_generations < 0: resolved_generations = int(_save_policy[&"autosave_generations"]) var started_at_us := Time.get_ticks_usec() var result := SaveService.inspect_recovery_candidates( resolved_path, resolved_generations ) var runtime_duration_us := maxi(int(Time.get_ticks_usec() - started_at_us), 0) _record_runtime_performance(&"load", runtime_duration_us) return _with_operation_context(result, { &"operation": &"inspect_recovery", &"save_path": resolved_path, &"runtime_duration_us": runtime_duration_us, }) func load_from_path(save_path: String) -> Dictionary: var started_at_us := Time.get_ticks_usec() var previous_paused := _clock.paused _clock.set_paused(true) var result := SaveService.load_snapshot(save_path) if not result[&"ok"] and result[&"code"] == &"UNSUPPORTED_FORMAT" \ and bool(_save_policy.get(&"auto_migrate", true)): var migration := SaveService.migrate_save( save_path, save_migration_registry, SaveService.FORMAT_VERSION ) _record_runtime_performance(&"save", int(migration.get(&"duration_us", 0))) if migration[&"ok"]: result = SaveService.load_snapshot(save_path) else: result = _with_operation_context(migration, { &"operation": &"load", &"migration_attempted": true, }) return _finish_load_operation( result, save_path, previous_paused, started_at_us, &"load", &"SAVE_LOADED", &"SAVE_LOAD_FAILED" ) func load_with_recovery( save_path := "", generation_count := -1 ) -> Dictionary: var resolved_path := str(save_path) if resolved_path.is_empty(): resolved_path = str(_save_policy[&"autosave_path"]) var resolved_generations := generation_count if resolved_generations < 0: resolved_generations = int(_save_policy[&"autosave_generations"]) var started_at_us := Time.get_ticks_usec() var previous_paused := _clock.paused # Recovery is a two-stage decision. SaveService establishes package, # checksum, format, and generic snapshot validity; Runtime then tries every # package-loadable candidate in the same stable order against authoritative # domain and cross-link rules. Failed domain attempts never reach the commit # boundary in _restore_snapshot_internal. var live_snapshot_before := create_snapshot() _clock.set_paused(true) var package_inventory := SaveService.inspect_recovery_candidates( resolved_path, resolved_generations ) var result := _attempt_domain_valid_recovery( resolved_path, package_inventory ) return _finish_recovery_operation( result, resolved_path, previous_paused, started_at_us, live_snapshot_before ) func _attempt_domain_valid_recovery( save_path: String, package_inventory: Dictionary ) -> Dictionary: var package_report := package_inventory.duplicate(true) # The selected package snapshot is an implementation detail of the package # inspection pass. Every actual attempt is loaded again through the explicit, # read-only candidate API so the final selection cannot accidentally reuse a # different candidate's payload. package_report.erase(&"snapshot") var package_recovery_report: Dictionary = {} var package_recovery_value: Variant = package_inventory.get(&"recovery_report", {}) if package_recovery_value is Dictionary: package_recovery_report = package_recovery_value.duplicate(true) var raw_candidates_value: Variant = package_inventory.get(&"candidates", []) var candidates: Array[Dictionary] = [] if raw_candidates_value is Array: for raw_candidate: Variant in raw_candidates_value: if not raw_candidate is Dictionary: continue var candidate: Dictionary = raw_candidate.duplicate(true) var package_loadable := bool(candidate.get(&"valid", false)) candidate[&"package_loadable"] = package_loadable candidate[&"package_status"] = &"VALID" if package_loadable else &"REJECTED" candidate[&"package_rejected"] = not package_loadable candidate[&"package_rejection_code"] = ( &"" if package_loadable else StringName(candidate.get(&"code", &"PACKAGE_REJECTED")) ) candidate[&"domain_status"] = &"NOT_ATTEMPTED" candidate[&"domain_attempted"] = false candidate[&"domain_valid"] = false candidate[&"domain_rejected"] = false candidate[&"domain_rejection_code"] = &"" candidate[&"failed_attempt_live_state_preserved"] = true candidate[&"selected"] = false candidates.append(candidate) if not bool(package_inventory.get(&"ok", false)) \ and not _has_package_loadable_recovery_candidate(candidates): return _recovery_failure_result( StringName(package_inventory.get(&"code", &"NO_VALID_RECOVERY_CANDIDATE")), str(package_inventory.get(&"message", save_path)), candidates, package_report, package_recovery_report, &"NO_PACKAGE_VALID_RECOVERY_CANDIDATE" ) for candidate_index in range(candidates.size()): var candidate: Dictionary = candidates[candidate_index] if not bool(candidate[&"package_loadable"]): continue var candidate_id := StringName(candidate.get(&"id", &"")) var generation := int(candidate.get(&"generation", -1)) var loaded := SaveService.load_recovery_candidate( save_path, candidate_id, generation ) if not bool(loaded.get(&"ok", false)): # A candidate may disappear or become corrupt between inventory and # its explicit load. Treat that race as a package rejection and keep # walking the already-established priority order. candidate[&"package_status"] = &"REJECTED" candidate[&"package_rejected"] = true candidate[&"package_load_succeeded"] = false candidate[&"package_rejection_code"] = StringName( loaded.get(&"code", &"PACKAGE_LOAD_FAILED") ) candidate[&"package_rejection_message"] = str(loaded.get(&"message", "")) candidates[candidate_index] = candidate continue candidate[&"package_load_succeeded"] = true candidate[&"domain_attempted"] = true var package_attempt_report: Dictionary = {} var loaded_report_value: Variant = loaded.get(&"recovery_report", {}) if loaded_report_value is Dictionary: package_attempt_report = loaded_report_value.duplicate(true) var attempt_live_guard := create_snapshot() var attempt_pause_guard := _clock.paused if not _restore_snapshot_internal(loaded[&"snapshot"], true): candidate[&"domain_status"] = &"REJECTED" candidate[&"domain_rejected"] = true candidate[&"domain_rejection_code"] = &"DOMAIN_OR_CROSSLINK_VALIDATION_FAILED" candidate[&"failed_attempt_live_state_preserved"] = ( create_snapshot() == attempt_live_guard and _clock.paused == attempt_pause_guard ) candidates[candidate_index] = candidate continue candidate[&"domain_status"] = &"VALID" candidate[&"domain_valid"] = true candidate[&"selected"] = true candidates[candidate_index] = candidate var result := loaded.duplicate(true) result[&"ok"] = true result[&"code"] = &"OK" result[&"selection_rule"] = package_inventory.get( &"selection_rule", &"FORMAL_BACKUP_MIGRATION_AUTOSAVE_NEWEST_FIRST" ) result[&"selected"] = candidate.duplicate(true) result[&"candidates"] = candidates.duplicate(true) result[&"candidate_attempts"] = candidates.duplicate(true) result[&"package_candidate_report"] = package_report result[&"package_recovery_report"] = package_attempt_report result[&"restore_safety_report"] = latest_restore_safety_report.duplicate(true) result[&"recovery_report"] = latest_restore_safety_report.duplicate(true) result[&"recovery_report"][&"restore_committed"] = true result[&"recovery_report"][&"selected_candidate_id"] = candidate_id result[&"recovery_report"][&"package_rejections"] = _recovery_package_rejections(candidates) result[&"recovery_report"][&"domain_rejections"] = _recovery_domain_rejections(candidates) return result return _recovery_failure_result( &"RESTORE_FAILED", save_path, candidates, package_report, package_recovery_report, &"NO_DOMAIN_VALID_RECOVERY_CANDIDATE" ) func _finish_recovery_operation( loaded_result: Dictionary, save_path: String, previous_paused: bool, started_at_us: int, live_snapshot_before: Dictionary ) -> Dictionary: var result := loaded_result.duplicate(true) if not bool(result.get(&"ok", false)): _clock.set_paused(previous_paused) var runtime_duration_us := maxi(int(Time.get_ticks_usec() - started_at_us), 0) _record_runtime_performance(&"load", runtime_duration_us) var live_state_preserved := true if not bool(result.get(&"ok", false)): live_state_preserved = create_snapshot() == live_snapshot_before result = _with_operation_context(result, { &"operation": &"recovery", &"save_path": save_path, &"runtime_duration_us": runtime_duration_us, &"loaded_clock_paused": _clock.paused if result.get(&"ok", false) else previous_paused, &"preload_pause_restored": true if result.get(&"ok", false) else _clock.paused == previous_paused, &"live_state_preserved": live_state_preserved, }) if bool(result.get(&"ok", false)): _complete_save_operation(result, &"SAVE_RECOVERY_LOADED") else: # A failed recovery is an observational operation. Publishing a persisted # DomainEvent here would itself violate the promised all-live-state atomicity. latest_save_operation_result = _save_event_payload(result) save_operation_completed.emit(result.duplicate(true)) return result func _has_package_loadable_recovery_candidate(candidates: Array[Dictionary]) -> bool: for candidate: Dictionary in candidates: if bool(candidate.get(&"package_loadable", false)): return true return false func _recovery_package_rejections(candidates: Array[Dictionary]) -> Array[Dictionary]: var rejected: Array[Dictionary] = [] for candidate: Dictionary in candidates: if not bool(candidate.get(&"package_rejected", false)): continue rejected.append({ &"id": StringName(candidate.get(&"id", &"")), &"code": StringName(candidate.get(&"package_rejection_code", &"PACKAGE_REJECTED")), }) return rejected func _recovery_domain_rejections(candidates: Array[Dictionary]) -> Array[Dictionary]: var rejected: Array[Dictionary] = [] for candidate: Dictionary in candidates: if not bool(candidate.get(&"domain_rejected", false)): continue rejected.append({ &"id": StringName(candidate.get(&"id", &"")), &"code": StringName(candidate.get( &"domain_rejection_code", &"DOMAIN_OR_CROSSLINK_VALIDATION_FAILED" )), }) return rejected func _recovery_failure_result( code: StringName, message: String, candidates: Array[Dictionary], package_report: Dictionary, package_recovery_report: Dictionary, rejection_reason: StringName ) -> Dictionary: var restore_safety_report := { &"report_version": 2, &"recovery_mode": true, &"repair_policy": &"WHITELISTED_AUTHORITATIVE_ONLY", &"automatic_repairs_applied": false, &"restore_committed": false, &"rejection_reason": rejection_reason, &"package_rejections": _recovery_package_rejections(candidates), &"domain_rejections": _recovery_domain_rejections(candidates), &"forbidden_repairs_applied": false, } return { &"ok": false, &"code": code, &"message": message, &"selection_rule": package_report.get( &"selection_rule", &"FORMAL_BACKUP_MIGRATION_AUTOSAVE_NEWEST_FIRST" ), &"selected": {}, &"candidates": candidates.duplicate(true), &"candidate_attempts": candidates.duplicate(true), &"package_candidate_report": package_report.duplicate(true), &"package_recovery_report": package_recovery_report.duplicate(true), &"restore_safety_report": restore_safety_report.duplicate(true), &"recovery_report": restore_safety_report, } func load_recovery_candidate( save_path: String, candidate_id: StringName, generation := -1 ) -> Dictionary: var started_at_us := Time.get_ticks_usec() var previous_paused := _clock.paused _clock.set_paused(true) var result := SaveService.load_recovery_candidate(save_path, candidate_id, generation) return _finish_load_operation( result, save_path, previous_paused, started_at_us, &"recovery_candidate", &"SAVE_RECOVERY_CANDIDATE_LOADED", &"SAVE_RECOVERY_FAILED" ) func migrate_save_path( save_path: String, target_version := SaveService.FORMAT_VERSION ) -> Dictionary: var started_at_us := Time.get_ticks_usec() var previous_paused := _clock.paused _clock.set_paused(true) var result := SaveService.migrate_save(save_path, save_migration_registry, target_version) _clock.set_paused(previous_paused) var runtime_duration_us := maxi(int(Time.get_ticks_usec() - started_at_us), 0) _record_runtime_performance(&"save", runtime_duration_us) result = _with_operation_context(result, { &"operation": &"migration", &"save_path": save_path, &"runtime_duration_us": runtime_duration_us, &"clock_pause_restored": _clock.paused == previous_paused, }) _complete_save_operation( result, &"SAVE_MIGRATION_SUCCEEDED" if result[&"ok"] else &"SAVE_MIGRATION_FAILED" ) return result func _process_save_checkpoints(absolute_minute: int) -> void: if _operations_tick_blocked: return if bool(_save_policy.get(&"enabled", false)) \ and bool(_save_policy.get(&"daily_checkpoint_enabled", true)) \ and posmod(absolute_minute, SimulationKernel.MINUTES_PER_DAY) == SimulationKernel.START_ABSOLUTE_MINUTE: var checkpoint_day := absolute_minute / SimulationKernel.MINUTES_PER_DAY + 1 if checkpoint_day != _last_daily_checkpoint_day: var previous_checkpoint_day := _last_daily_checkpoint_day _last_daily_checkpoint_day = checkpoint_day var daily_result := _perform_checkpoint_save( str(_save_policy[&"autosave_path"]), { &"display_name": "Daily Autosave", &"checkpoint_day": checkpoint_day, &"checkpoint_absolute_minute": absolute_minute, }, true, &"daily_0600", 0 ) if not daily_result[&"ok"]: _last_daily_checkpoint_day = previous_checkpoint_day if _pending_checkpoints.is_empty(): return var requests := _pending_checkpoints.duplicate(true) _pending_checkpoints.clear() for request_value: Variant in requests: if not request_value is Dictionary: continue var request: Dictionary = request_value _perform_checkpoint_save( str(request[&"save_path"]), request[&"metadata"], bool(request[&"use_autosave"]), StringName(request[&"reason"]), int(request[&"request_id"]) ) func _perform_checkpoint_save( save_path: String, metadata: Variant, use_autosave: bool, reason: StringName, request_id: int ) -> Dictionary: if not _initialized or _clock == null: return { &"ok": false, &"code": &"RUNTIME_NOT_INITIALIZED", &"message": "Simulation runtime is not initialized.", } var started_at_us := Time.get_ticks_usec() # A rejected Path projection is not a complete Runtime save. Refuse before # pausing or touching formal/tmp/backup files, and keep the refusal observable # without publishing a persisted event into the live snapshot. if path_service.create_snapshot().is_empty(): var refused: Dictionary = { &"ok": false, &"code": &"INVALID_SNAPSHOT", &"message": "Incomplete Path snapshot; original save preserved.", &"operation": &"autosave" if use_autosave else &"save", &"save_path": save_path, &"reason": reason, &"request_id": request_id, &"checkpoint_absolute_minute": _clock.absolute_minute, &"runtime_duration_us": maxi(int(Time.get_ticks_usec() - started_at_us), 0), &"clock_pause_restored": true, &"formal_save_preserved": true, &"live_state_preserved": true, } latest_save_operation_result = _save_event_payload(refused) save_operation_completed.emit(refused.duplicate(true)) return refused var previous_paused := _clock.paused _clock.set_paused(true) var snapshot := create_snapshot() # Persistence pauses I/O, not game semantics. The package must retain the # state that should resume after load rather than the temporary I/O pause. snapshot[&"clock"][&"paused"] = previous_paused var effective_metadata: Dictionary = metadata.duplicate(true) if metadata is Dictionary else {} var presentation: Dictionary = {} checkpoint_presentation_requested.emit(presentation) if not effective_metadata.has(&"interface_selection") and presentation.get(&"interface_selection") is Dictionary: effective_metadata[&"interface_selection"] = presentation[&"interface_selection"].duplicate(true) effective_metadata[&"checkpoint_reason"] = reason var result := ( SaveService.save_autosave( save_path, snapshot, effective_metadata, int(_save_policy[&"autosave_generations"]) ) if use_autosave else SaveService.save_snapshot(save_path, snapshot, effective_metadata) ) _clock.set_paused(previous_paused) var runtime_duration_us := maxi(int(Time.get_ticks_usec() - started_at_us), 0) _record_runtime_performance(&"save", runtime_duration_us) result = _with_operation_context(result, { &"operation": &"autosave" if use_autosave else &"save", &"save_path": save_path, &"reason": reason, &"request_id": request_id, &"checkpoint_absolute_minute": int(snapshot[&"clock"][&"absolute_minute"]), &"runtime_duration_us": runtime_duration_us, &"clock_pause_restored": _clock.paused == previous_paused, }) var event_type := ( &"AUTOSAVE_CHECKPOINT_SUCCEEDED" if use_autosave and result[&"ok"] else &"AUTOSAVE_CHECKPOINT_FAILED" if use_autosave else &"SAVE_CHECKPOINT_SUCCEEDED" if result[&"ok"] else &"SAVE_CHECKPOINT_FAILED" ) _complete_save_operation(result, event_type) return result func _finish_load_operation( loaded_result: Dictionary, save_path: String, previous_paused: bool, started_at_us: int, operation: StringName, success_event_type: StringName, failure_event_type: StringName ) -> Dictionary: var result := loaded_result.duplicate(true) var recovery_mode := operation in [&"recovery", &"recovery_candidate"] var package_recovery_report: Variant = result.get(&"recovery_report", null) if result[&"ok"] and not _restore_snapshot_internal(result[&"snapshot"], recovery_mode): result[&"ok"] = false result[&"source_code"] = result.get(&"code", &"OK") result[&"code"] = &"RESTORE_FAILED" result[&"message"] = save_path result[&"recovery_report"] = { &"report_version": 1, &"recovery_mode": recovery_mode, &"repair_policy": &"WHITELISTED_AUTHORITATIVE_ONLY", &"automatic_repairs_applied": false, &"restore_committed": false, &"rejection_reason": &"DOMAIN_OR_CROSSLINK_VALIDATION_FAILED", &"forbidden_repairs_applied": false, } elif result[&"ok"]: result[&"restore_safety_report"] = latest_restore_safety_report.duplicate(true) if recovery_mode: result[&"recovery_report"] = latest_restore_safety_report.duplicate(true) result[&"recovery_report"][&"restore_committed"] = true if package_recovery_report is Dictionary: result[&"package_recovery_report"] = package_recovery_report.duplicate(true) if not result[&"ok"]: _clock.set_paused(previous_paused) var runtime_duration_us := maxi(int(Time.get_ticks_usec() - started_at_us), 0) _record_runtime_performance(&"load", runtime_duration_us) result = _with_operation_context(result, { &"operation": operation, &"save_path": save_path, &"runtime_duration_us": runtime_duration_us, &"loaded_clock_paused": _clock.paused if result[&"ok"] else previous_paused, &"preload_pause_restored": true if result[&"ok"] else _clock.paused == previous_paused, }) _complete_save_operation( result, success_event_type if result[&"ok"] else failure_event_type ) return result func _complete_save_operation(result: Dictionary, event_type: StringName) -> void: latest_save_operation_result = _save_event_payload(result) _publish_actor_event( event_type, &"save_service", [], latest_save_operation_result ) save_operation_completed.emit(result.duplicate(true)) func _save_event_payload(result: Dictionary) -> Dictionary: var payload := { &"ok": bool(result.get(&"ok", false)), &"code": StringName(result.get(&"code", &"UNKNOWN")), &"message": str(result.get(&"message", "")), &"operation": StringName(result.get(&"operation", &"unknown")), &"save_path": str(result.get(&"save_path", result.get(&"path", ""))), &"reason": StringName(result.get(&"reason", &"")), &"request_id": int(result.get(&"request_id", 0)), &"checkpoint_absolute_minute": int(result.get(&"checkpoint_absolute_minute", _clock.absolute_minute)), &"duration_us": int(result.get(&"duration_us", 0)), &"runtime_duration_us": int(result.get(&"runtime_duration_us", 0)), } var selected_value: Variant = result.get(&"selected", null) if selected_value is Dictionary: payload[&"selected_candidate_id"] = StringName(selected_value.get(&"id", &"")) payload[&"selected_candidate_kind"] = StringName(selected_value.get(&"kind", &"")) payload[&"selected_generation"] = int(selected_value.get(&"generation", -1)) var manifest_value: Variant = result.get(&"manifest", null) if manifest_value is Dictionary: payload[&"save_id"] = str(manifest_value.get("save_id", "")) payload[&"saved_absolute_minute"] = int(manifest_value.get("absolute_minute", -1)) return payload func _with_operation_context(result: Dictionary, context: Dictionary) -> Dictionary: var enriched := result.duplicate(true) for key: Variant in context: enriched[key] = context[key] return enriched func _record_runtime_performance(metric_id: StringName, duration_us: int) -> void: if _clock != null and _clock.performance_budget != null: _clock.performance_budget.record_sample(metric_id, maxi(duration_us, 0)) func _release_recovery_task_items( inventory: InventoryService, reservation_ids: Array[StringName], task: StaffTask, absolute_minute: int ) -> Dictionary: var before := inventory.create_snapshot() for reservation_id: StringName in reservation_ids: var result := inventory.release_reservation( reservation_id, StringName("recovery.staff.task.release.%s.%s" % [task.task_id, reservation_id]), absolute_minute ) if not bool(result.get(&"ok", false)): inventory.restore_snapshot(before) return result return {&"ok": true} func _merge_content_filter_profiles(current: Dictionary, saved: Dictionary) -> Dictionary: var disabled_by_name: Dictionary = {} for raw_tag: Variant in current.get(&"disabled_content_tags", []): var tag := StringName(raw_tag) if not tag.is_empty(): disabled_by_name[tag] = true for raw_tag: Variant in saved.get(&"disabled_content_tags", []): var tag := StringName(raw_tag) if not tag.is_empty(): disabled_by_name[tag] = true var disabled_tags: Array[StringName] = [] for raw_tag: Variant in disabled_by_name: disabled_tags.append(StringName(raw_tag)) disabled_tags.sort_custom( func(left: StringName, right: StringName) -> bool: return String(left) < String(right) ) return { &"filter_mode": maxi( int(current.get(&"filter_mode", EventDefinition.FilterBehavior.NONE)), int(saved.get(&"filter_mode", EventDefinition.FilterBehavior.NONE)) ) as EventDefinition.FilterBehavior, &"maximum_content_rating": mini( int(current.get(&"maximum_content_rating", EventDefinition.ContentRating.GENERAL)), int(saved.get(&"maximum_content_rating", EventDefinition.ContentRating.GENERAL)) ) as EventDefinition.ContentRating, &"disabled_content_tags": disabled_tags, } func _content_filter_restore_report( current: Dictionary, saved: Dictionary, effective: Dictionary ) -> Dictionary: var current_tags: Array = current.get(&"disabled_content_tags", []) var saved_tags: Array = saved.get(&"disabled_content_tags", []) var saved_was_weaker := ( int(saved.get(&"filter_mode", 0)) < int(current.get(&"filter_mode", 0)) or int(saved.get(&"maximum_content_rating", 0)) > int(current.get(&"maximum_content_rating", 0)) ) for raw_tag: Variant in current_tags: if not saved_tags.has(StringName(raw_tag)) and not saved_tags.has(String(raw_tag)): saved_was_weaker = true break return { &"saved_profile_was_weaker": saved_was_weaker, &"strict_profile_preserved": true, &"current_before_load": _content_filter_profile_payload(current), &"saved_profile": _content_filter_profile_payload(saved), &"effective_profile": _content_filter_profile_payload(effective), } func _content_filter_profile_payload(profile: Dictionary) -> Dictionary: var mode := clampi( int(profile.get(&"filter_mode", EventDefinition.FilterBehavior.NONE)), EventDefinition.FilterBehavior.NONE, EventDefinition.FilterBehavior.DISABLE ) var rating := clampi( int(profile.get(&"maximum_content_rating", EventDefinition.ContentRating.GENERAL)), EventDefinition.ContentRating.GENERAL, EventDefinition.ContentRating.RESTRICTED ) var tags: Array[String] = [] for raw_tag: Variant in profile.get(&"disabled_content_tags", []): tags.append(String(raw_tag)) tags.sort() return { &"filter_mode": EventDefinition.FilterBehavior.keys()[mode], &"maximum_content_rating": EventDefinition.ContentRating.keys()[rating], &"disabled_content_tags": tags, } func _save_policy_path_is_safe(save_path: String) -> bool: if not save_path.begins_with("user://") or save_path == "user://": return false var relative := save_path.trim_prefix("user://").replace("\\", "/") if relative.is_empty() or relative.is_absolute_path(): return false for segment: String in relative.split("/", false): if segment.is_empty() or segment == "." or segment == "..": return false return true func _runtime_command_history_crosslinks_are_valid( queue_snapshot: Dictionary, next_command_id: int, events: DomainEventStream ) -> bool: if next_command_id <= 0 or events == null \ or typeof(queue_snapshot.get("version", null)) != TYPE_INT \ or int(queue_snapshot["version"]) != CommandQueue.SNAPSHOT_VERSION \ or not queue_snapshot.get("accepted", null) is Array: return false var seen_ids: Dictionary = {} var processed_by_id: Dictionary = {} var command_idempotency: Dictionary = {} for collection_key: String in ["pending", "in_flight", "processed"]: var rows_value: Variant = queue_snapshot.get(collection_key, null) if not rows_value is Array: return false if collection_key == "in_flight" and not rows_value.is_empty(): # Runtime commands are synchronously completed in APPLY_COMMANDS and # have no resumable in-flight execution path. return false for row_value: Variant in rows_value: if not row_value is Dictionary: return false var row: Dictionary = row_value var command_id_value: Variant = row.get("command_id", null) if typeof(command_id_value) != TYPE_INT or int(command_id_value) <= 0 \ or seen_ids.has(int(command_id_value)): return false var command_id := int(command_id_value) seen_ids[command_id] = true if collection_key == "processed": processed_by_id[command_id] = row else: var raw_key: Variant = row.get("idempotency_key", "") if typeof(raw_key) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var key := StringName(raw_key) if not key.is_empty(): command_idempotency[command_id] = key if seen_ids.size() != next_command_id - 1: return false for command_id in range(1, next_command_id): if not seen_ids.has(command_id): return false var accepted_by_id: Dictionary = {} var previous_accepted_id := 0 for raw_metadata: Variant in queue_snapshot["accepted"]: if not raw_metadata is Dictionary: return false var metadata: Dictionary = raw_metadata var command_id_value: Variant = metadata.get("command_id", null) var key_value: Variant = metadata.get("idempotency_key", null) if metadata.size() != 2 or typeof(command_id_value) != TYPE_INT \ or int(command_id_value) <= previous_accepted_id \ or typeof(key_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or not seen_ids.has(int(command_id_value)): return false var command_id := int(command_id_value) if command_idempotency.has(command_id) \ and StringName(command_idempotency[command_id]) != StringName(key_value): return false accepted_by_id[command_id] = StringName(key_value) command_idempotency[command_id] = StringName(key_value) previous_accepted_id = command_id if accepted_by_id.size() != seen_ids.size(): return false var idempotency_value: Variant = queue_snapshot.get("idempotency", null) if not idempotency_value is Dictionary: return false var mapped_ids: Dictionary = {} for raw_key: Variant in idempotency_value: if typeof(raw_key) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(raw_key).is_empty() \ or typeof(idempotency_value[raw_key]) != TYPE_INT: return false var command_id := int(idempotency_value[raw_key]) if not seen_ids.has(command_id) or mapped_ids.has(command_id): return false mapped_ids[command_id] = StringName(raw_key) for command_id: int in command_idempotency: var accepted_key := StringName(command_idempotency[command_id]) if accepted_key.is_empty(): if mapped_ids.has(command_id): return false elif mapped_ids.get(command_id, &"") != accepted_key: return false var completion_by_id: Dictionary = {} for event: SimulationDomainEvent in events.recent_events(): if event.event_type not in [&"COMMAND_COMPLETED", &"COMMAND_FAILED"]: continue var command_id := event.causation_command_id if command_id <= 0 or not processed_by_id.has(command_id) \ or completion_by_id.has(command_id) \ or event.source_service != &"simulation_runtime" \ or not _strict_variants_equal(event.payload, processed_by_id[command_id]): return false var result_code := String((processed_by_id[command_id] as Dictionary).get("code", "")) if (result_code == "OK") != (event.event_type == &"COMMAND_COMPLETED"): return false completion_by_id[command_id] = true # Completion events share a bounded stream with all other DomainEvents. Any # retained completion must match exactly; absence alone is not corruption # because unrelated events can legitimately evict it. return true func _legacy_operations_history_is_absent(runtime_data: Dictionary) -> bool: # v1-v4 had no Operations wrapper or payroll settlement semantics. Reject a # relabelled v5 payload instead of silently discarding production history. if runtime_data.has(&"operations") or runtime_data.has("operations"): return false var staff_value: Variant = runtime_data.get(&"staff", null) if staff_value is Dictionary: var settlements: Variant = (staff_value as Dictionary).get(&"payroll_settlements", []) if settlements is Array and not settlements.is_empty(): return false var economy_value: Variant = runtime_data.get(&"economy", null) if economy_value is Dictionary: var entries: Variant = (economy_value as Dictionary).get(&"entries", []) if entries is Array: for raw_entry: Variant in entries: if raw_entry is Dictionary \ and StringName((raw_entry as Dictionary).get(&"category", "")) == &"WAGE": return false return true func _legacy_infrastructure_history_is_absent(runtime_data: Dictionary) -> bool: # Infrastructure aggregates first exist in Runtime v6. A lower version with # any look-alike field or retained v6-only history is a relabelled payload, # not a migratable legacy save. for key: StringName in [ &"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure", ]: if runtime_data.has(key) or runtime_data.has(String(key)): return false var stream_value: Variant = runtime_data.get(&"domain_events", null) if stream_value is Dictionary: var recent_events: Variant = (stream_value as Dictionary).get("recent_events", []) if recent_events is Array: for raw_event: Variant in recent_events: if raw_event is Dictionary and StringName( (raw_event as Dictionary).get("event_type", "") ) in [&"FACILITY_INFRASTRUCTURE_PROJECTED", &"EXCAVATION_COMPLETED"]: return false var queue_value: Variant = runtime_data.get(&"command_queue", null) if queue_value is Dictionary: for queue_key: String in ["pending", "in_flight"]: var commands: Variant = (queue_value as Dictionary).get(queue_key, []) if not commands is Array: continue for raw_command: Variant in commands: if raw_command is Dictionary and StringName( (raw_command as Dictionary).get("command_type", "") ) in [&"start_excavation", &"pause_excavation", &"resume_excavation"]: return false return true func _runtime_v5_outer_snapshot_shape_is_valid(snapshot: Dictionary) -> bool: if not _dictionary_has_exact_canonical_keys( snapshot, [&"schema_version", &"clock", &"game_state", &"systems"] ) or typeof(snapshot.get(&"schema_version", null)) != TYPE_INT \ or int(snapshot[&"schema_version"]) != SimulationSnapshot.SCHEMA_VERSION: return false var systems_value: Variant = snapshot.get(&"systems", null) var clock_value: Variant = snapshot.get(&"clock", null) var state_value: Variant = snapshot.get(&"game_state", null) if not systems_value is Dictionary or not clock_value is Dictionary \ or not state_value is Dictionary: return false var systems: Dictionary = systems_value var clock_data: Dictionary = clock_value var state_data: Dictionary = state_value if not _dictionary_has_exact_canonical_keys(systems, [&"runtime"]) \ or not systems[&"runtime"] is Dictionary \ or not _dictionary_has_exact_canonical_keys( clock_data, [&"absolute_minute", &"speed", &"paused", &"snapshot_version"] ) \ or typeof(clock_data.get(&"absolute_minute", null)) != TYPE_INT \ or int(clock_data[&"absolute_minute"]) < 0 \ or typeof(clock_data.get(&"speed", null)) != TYPE_INT \ or int(clock_data[&"speed"]) not in [1, 2, 3, 4] \ or typeof(clock_data.get(&"paused", null)) != TYPE_BOOL \ or typeof(clock_data.get(&"snapshot_version", null)) != TYPE_INT \ or int(clock_data[&"snapshot_version"]) < 0: return false if not _dictionary_has_exact_canonical_keys( state_data, [&"resources", &"world_tree_rank", &"active_layer", &"revision"] ) or not state_data[&"resources"] is Dictionary \ or typeof(state_data.get(&"world_tree_rank", null)) != TYPE_INT \ or int(state_data[&"world_tree_rank"]) < 1 \ or int(state_data[&"world_tree_rank"]) > 7 \ or typeof(state_data.get(&"active_layer", null)) != TYPE_INT \ or int(state_data[&"active_layer"]) not in [0, 1] \ or typeof(state_data.get(&"revision", null)) != TYPE_INT \ or int(state_data[&"revision"]) < 0: return false var resources: Dictionary = state_data[&"resources"] var seen_resources: Dictionary = {} for raw_resource_id: Variant in resources: if typeof(raw_resource_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var resource_id := StringName(raw_resource_id) if resource_id.is_empty() or seen_resources.has(resource_id) \ or typeof(resources[raw_resource_id]) != TYPE_INT \ or int(resources[raw_resource_id]) < 0: return false seen_resources[resource_id] = true for required_resource: StringName in [&"gold", &"mana", &"materials", &"faith"]: if not seen_resources.has(required_resource): return false return true func _connect_path_performance_signal() -> void: if path_service == null or not path_service.has_signal(&"path_query_measured"): return var callback := Callable(self, "_on_path_query_measured") if not path_service.is_connected(&"path_query_measured", callback): path_service.connect(&"path_query_measured", callback) func _on_path_query_measured(duration_us: int) -> void: _record_runtime_performance(&"path_query", duration_us) func _initialize_rng_streams() -> void: _rng_streams.clear() for stream_id in RNG_STREAM_IDS: _rng_streams[stream_id] = DeterministicRng.from_root_seed(world_seed, stream_id) func _connect_construction_signals() -> void: if construction_service == null: return var completed_callback := Callable(self, "_on_facility_completed") var demolished_callback := Callable(self, "_on_facility_demolished") if not construction_service.facility_completed.is_connected(completed_callback): construction_service.facility_completed.connect(completed_callback) if not construction_service.facility_demolished.is_connected(demolished_callback): construction_service.facility_demolished.connect(demolished_callback) func _initialize_infrastructure_services() -> bool: spatial_subslot_service = SpatialSubslotService.new() route_subslot_path_adapter = RouteSubslotPathAdapter.new() if not route_subslot_path_adapter.configure( spatial_subslot_service, path_service, world_service.get_walkable_cells() ): return false facility_infrastructure_projector = FacilityInfrastructureProjector.new() if not facility_infrastructure_projector.configure( route_subslot_path_adapter, spatial_subslot_service, path_service ): return false excavation_service = ExcavationService.new(world_service, construction_resource_state) excavation_service.current_minute = _clock.absolute_minute var excavation_callback := Callable(self, "_on_excavation_completed") if not excavation_service.excavation_completed.is_connected(excavation_callback): excavation_service.excavation_completed.connect(excavation_callback) return true func rebuild_path_graph_from_world() -> bool: if path_service == null or world_service == null: return false var walkable_cells: Array[Vector3i] = world_service.get_walkable_cells() var projected_walkable_cells: Array[Vector3i] = walkable_cells.duplicate() if route_subslot_path_adapter != null \ and route_subslot_path_adapter.is_configured() \ and spatial_subslot_service != null: var seen_projected: Dictionary = {} projected_walkable_cells.clear() for cell: Vector3i in walkable_cells: if not seen_projected.has(cell): seen_projected[cell] = true projected_walkable_cells.append(cell) for cell: Vector3i in spatial_subslot_service.get_route_cells(): if not seen_projected.has(cell): seen_projected[cell] = true projected_walkable_cells.append(cell) var current_path_snapshot: Dictionary = path_service.create_snapshot() var raw_actor_cells: Variant = current_path_snapshot.get(&"actor_cells", null) var has_runtime_actor_state: bool = ( not _movement_credits.is_empty() or (raw_actor_cells is Array and not raw_actor_cells.is_empty()) ) # PathService correctly owns its own graph, but Runtime owns the cross-system # Actor/Path projection contract. Reject a topology change that would evict # a live Guest/Staff projection instead of allowing PathService to prune it. if has_runtime_actor_state: if _clock == null or not _validate_runtime_path_crosslinks( path_service, guest_service, staff_service, _movement_credits, _clock.absolute_minute ): return false var walkable_set: Dictionary = {} for cell: Vector3i in projected_walkable_cells: walkable_set[cell] = true var expected_cells: Dictionary = _expected_runtime_actor_cells( guest_service, staff_service ) for actor_id: StringName in _sorted_name_keys(expected_cells): if not walkable_set.has(expected_cells[actor_id]): return false if route_subslot_path_adapter != null \ and route_subslot_path_adapter.is_configured(): var synchronized := route_subslot_path_adapter.synchronize_projection( StringName( "runtime.base_sync.%08d" % (route_subslot_path_adapter.projection_revision + 1) ), walkable_cells ) return synchronized.is_success() and _prune_legacy_stairs_and_movement() return path_service.rebuild_graph(walkable_cells) and _prune_legacy_stairs_and_movement() func _initialize_world(rank: int) -> void: var bounds := _bounds_for_rank(rank) world_service.reset(bounds) _seed_boundary_cells(bounds) rebuild_path_graph_from_world() path_service.add_connector( &"starter.staff_door", Vector3i(0, 0, WorldService.LAYER_FRONT), Vector3i(0, 0, WorldService.LAYER_BACK), PathService.DEFAULT_CONNECTOR_COST, true, &"staff" ) func _initialize_mana_network() -> void: mana_network_service.register_source(STARTER_MANA_SOURCE, STARTER_MANA_OUTPUT) mana_network_service.register_connector(STARTER_MANA_BUS) mana_network_service.connect_nodes(STARTER_MANA_SOURCE, STARTER_MANA_BUS) func _initialize_economy() -> void: economy_service = EconomyService.new({ PLAYER_CASH_ACCOUNT: {&"allow_negative": false}, CONSTRUCTION_EXPENSE_ACCOUNT: {&"allow_negative": false}, OPERATING_EXPENSE_ACCOUNT: {&"allow_negative": false}, AUTHORITY_FINE_ACCOUNT: {&"allow_negative": false}, WAGE_EXPENSE_ACCOUNT: {&"allow_negative": false}, }) economy_service.initialize_account( PLAYER_CASH_ACCOUNT, int(_state.get_resource(&"gold")), _clock.absolute_minute, &"runtime.initial_player_cash" ) facility_cleanliness = FacilityCleanliness.new() cleaning_policy = CleaningPolicy.new() cleaning_policy.load_file() weather = WeatherTimeline.new() weather.initialize(&"clear", _clock.absolute_minute) facility_upkeep = FacilityUpkeepAccrual.new() upkeep_settlement = FacilityUpkeepSettlement.new() FacilityUpkeepSettlement.prepare_accounts(economy_service) facility_upkeep.start_at(_clock.absolute_minute) mana_reserve = ManaReserveCycle.new() mana_reserve.start_at(_clock.absolute_minute) construction_resource_state = _create_resource_adapter( economy_service, CONSTRUCTION_EXPENSE_ACCOUNT, &"CONSTRUCTION", &"CONSTRUCTION_REFUND", &"construction_service" ) operating_resource_state = _create_resource_adapter( economy_service, OPERATING_EXPENSE_ACCOUNT, &"OPERATING_EXPENSE", &"OPERATING_REFUND", &"simulation_runtime" ) _connect_economy_signals() func _create_resource_adapter( economy: EconomyService, counter_account: StringName, expense_category: StringName, refund_category: StringName, source_entity_id: StringName ) -> LedgerBackedResourceState: return LedgerBackedResourceState.new( _state, economy, counter_account, expense_category, refund_category, source_entity_id, func() -> int: return _runtime_event_minute() ) func _connect_economy_signals() -> void: if economy_service == null: return economy_service.transaction_posted.connect(Callable(self, "_on_ledger_transaction_posted")) func _initialize_s2_operations() -> void: inventory_service = InventoryService.new() inventory_service.current_minute = _clock.absolute_minute logistics_dispatcher = LogisticsDispatcher.new() logistics_requests = {} facility_service = FacilityService.new() facility_service.current_minute = _clock.absolute_minute _configure_facility_boundaries() _connect_facility_signals() auto_purchase_plans = AutoPurchasePlansType.new() kitchen_production = KitchenProductionService.new() kitchen_production.configure(inventory_service, facility_service, definition_registry, _clock.absolute_minute) func _initialize_s3_actors() -> void: guest_service = GuestService.new() guest_service.current_minute = _clock.absolute_minute guest_service.create_definition(&"traveler", { &"tags": [&"general", &"traveler"], &"wealth_min": 120, &"wealth_max": 450, &"base_patience": 45, &"risk_tolerance": 50, &"preference_weights": {&"dining": 8, &"lodging": 7, &"social": 3}, &"group_size_weights": {1: 70, 2: 25, 3: 5}, &"legal_sensitivity": 65, &"quality_sensitivity": 50, &"need_rates": {&"hunger": 1, &"sleep": 1, &"hygiene": 0, &"medical": 0, &"leisure": 1, &"intimacy": 0}, &"credit_limit_gold": 50, &"minimum_quality": 25, }) guest_service.create_definition(&"merchant", { &"tags": [&"general", &"merchant", &"frugal"], &"wealth_min": 300, &"wealth_max": 1_200, &"base_patience": 35, &"risk_tolerance": 35, &"preference_weights": {&"lodging": 9, &"quiet": 7, &"service": 4}, &"group_size_weights": {1: 60, 2: 40}, &"legal_sensitivity": 80, &"quality_sensitivity": 70, &"need_rates": {&"hunger": 1, &"sleep": 1, &"hygiene": 0, &"medical": 0, &"leisure": 1, &"intimacy": 0}, &"credit_limit_gold": 200, &"minimum_quality": 40, }) guest_service.create_definition(&"adventurer", { &"tags": [&"adventurer", &"general", &"spendthrift"], &"wealth_min": 180, &"wealth_max": 800, &"base_patience": 30, &"risk_tolerance": 85, &"preference_weights": {&"dining": 6, &"social": 8, &"service": 2}, &"group_size_weights": {1: 45, 2: 35, 3: 15, 4: 5}, &"legal_sensitivity": 35, &"quality_sensitivity": 35, &"adult_verified": true, &"need_rates": {&"hunger": 2, &"sleep": 1, &"hygiene": 0, &"medical": 0, &"leisure": 1, &"intimacy": 0}, &"credit_limit_gold": 100, &"minimum_quality": 20, }) if bool(mvp_content_report.get(&"valid", false)): for definition: GuestDefinition in mvp_content_catalog.build_all_guest_definitions(): if not guest_service.register_definition(definition): mvp_content_report = mvp_content_report.duplicate(true) mvp_content_report[&"valid"] = false mvp_content_report[&"runtime_registration_error"] = guest_service.last_error break staff_service = StaffService.new() staff_service.current_minute = _clock.absolute_minute _configure_staff_boundaries() _connect_guest_signals() _connect_staff_signals() func _configure_staff_boundaries() -> void: if staff_service == null: return staff_service.configure_arrival_driver(Callable(self, "_handle_rescue_arrival")) staff_service.configure_work_progress_driver(Callable(self, "_automatic_staff_work_units")) staff_service.configure_callbacks( Callable(self, "_reserve_staff_task_items"), Callable(self, "_release_staff_task_items"), Callable(self, "_estimate_staff_task_travel") ) func _connect_guest_signals() -> void: if guest_service == null: return guest_service.guest_spawned.connect(Callable(self, "_on_guest_spawned")) guest_service.guest_state_changed.connect(Callable(self, "_on_guest_state_changed")) guest_service.guest_queue_abandoned.connect(Callable(self, "_on_guest_queue_abandoned")) guest_service.guest_recovery_started.connect(Callable(self, "_on_guest_recovery_started")) guest_service.guest_despawned.connect(Callable(self, "_on_guest_despawned")) func _connect_staff_signals() -> void: if staff_service == null: return staff_service.staff_registered.connect(Callable(self, "_on_staff_registered")) staff_service.staff_removed.connect(Callable(self, "_on_staff_removed")) staff_service.shift_registered.connect(Callable(self, "_on_shift_registered")) staff_service.shift_state_changed.connect(Callable(self, "_on_shift_state_changed")) staff_service.task_registered.connect(Callable(self, "_on_staff_task_registered")) staff_service.task_assigned.connect(Callable(self, "_on_staff_task_assigned")) staff_service.task_reopened.connect(Callable(self, "_on_staff_task_reopened")) staff_service.task_completed.connect(Callable(self, "_on_staff_task_completed")) func _initialize_s4_relationship_events() -> void: relationship_service = RelationshipService.new() event_service = EventService.new() event_service.create_definition(&"relationship.threshold", { &"category": &"RELATIONSHIP", &"priority": 80, &"foreground": true, &"expires_after": 1_440, &"transaction_policy": &"ROLLBACK_ALL", &"missing_actor_policy": &"CANCEL", &"content_rating": &"GENERAL", &"choices": [ { &"choice_id": &"acknowledge", &"text_key": &"event.relationship.threshold.acknowledge", &"actions": [{ &"action_id": &"record_acknowledgement", &"type": &"SET_FLAG", &"payload": {&"flag_id": &"relationship_threshold_acknowledged"}, }], }, { &"choice_id": &"defer", &"text_key": &"event.relationship.threshold.defer", &"actions": [], }, ], }) _configure_relationship_event_boundaries() _connect_relationship_signals() _connect_event_signals() func _configure_relationship_event_boundaries() -> void: if relationship_service == null or event_service == null: return relationship_service.configure_original_identity_validator( func(payload: Dictionary) -> bool: return _original_relationship_identities_valid(payload, staff_service, mvp_content_catalog) ) relationship_service.configure_candidate_callback( Callable(self, "_on_relationship_threshold_candidate") ) event_service.configure_relationship_service(relationship_service) event_service.configure_candidate_eligibility_guard(Callable(self, "_event_candidate_placement_permitted")) event_service.configure_callbacks( Callable(self, "_event_actor_exists"), Callable(self, "_execute_event_action_boundary"), Callable(self, "_reserve_event_cost"), Callable(self, "_release_event_cost"), Callable(self, "_commit_event_cost"), Callable(self, "_event_entity_exists"), Callable(self, "_event_path_exists") ) ## Original 6.2/6.4: Tomari's placement prevents floor trouble. This does not ## choose a joining date, probability, defense score, or narrative content. func is_floor_trouble_protected(floor_y: int) -> bool: var tomari := staff_service.get_staff(&"heroine_tomari") if tomari == null or tomari.employment_kind != StaffRuntime.EmploymentKind.UNIQUE: return false var assignment := staff_service.get_work_assignment(tomari.staff_id) var facility := facility_service.get_instance(StringName(assignment.get(&"facility_id", &""))) return facility != null and facility.origin.y == floor_y func _event_candidate_placement_permitted(definition: EventDefinition, context: Dictionary) -> bool: if definition.category != &"TROUBLE": return true # A business trouble identifies its existing target facility. Missing or # conflicting location data cannot silently bypass placement protection. var target: Variant = context.get(&"facility_id", definition.location_rule.get(&"facility_id", null)) if typeof(target) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var declared: Variant = definition.location_rule.get(&"facility_id", target) if typeof(declared) not in [TYPE_STRING, TYPE_STRING_NAME] or StringName(declared) != StringName(target): return false var facility := facility_service.get_instance(StringName(target)) return facility != null and not is_floor_trouble_protected(facility.origin.y) func _connect_relationship_signals() -> void: if relationship_service == null: return relationship_service.relationship_registered.connect(Callable(self, "_on_relationship_registered")) relationship_service.relationship_value_changed.connect(Callable(self, "_on_relationship_value_changed")) relationship_service.shield_changed.connect(Callable(self, "_on_relationship_shield_changed")) relationship_service.route_lock_changed.connect(Callable(self, "_on_relationship_route_lock_changed")) func _connect_event_signals() -> void: if event_service == null: return event_service.event_enqueued.connect(Callable(self, "_on_event_enqueued")) event_service.event_started.connect(Callable(self, "_on_event_started")) event_service.event_finished.connect(Callable(self, "_on_event_finished")) event_service.event_cancelled.connect(Callable(self, "_on_event_cancelled")) event_service.choice_resolved.connect(Callable(self, "_on_event_choice_resolved")) event_service.effect_transaction_started.connect(Callable(self, "_on_event_effect_transaction_started")) event_service.effect_transaction_finished.connect(Callable(self, "_on_event_effect_transaction_finished")) func _initialize_s5_authority() -> void: authority_service = AuthorityService.new() authority_service.current_minute = _clock.absolute_minute _configure_authority_boundaries() _connect_authority_signals() func _configure_authority_boundaries() -> void: if authority_service == null: return authority_service.configure_callbacks( Callable(self, "_authority_line_of_sight"), Callable(self, "_authority_path_query"), Callable(self, "_apply_authority_resolution_boundary"), Callable(self, "_roll_authority_inspection") ) func _connect_authority_signals() -> void: if authority_service == null: return authority_service.heat_changed.connect(Callable(self, "_on_authority_heat_changed")) authority_service.incident_created.connect(Callable(self, "_on_authority_incident_created")) authority_service.incident_detector_added.connect(Callable(self, "_on_authority_incident_detector_added")) authority_service.evidence_registered.connect(Callable(self, "_on_authority_evidence_registered")) authority_service.evidence_discovered.connect(Callable(self, "_on_authority_evidence_discovered")) authority_service.inspection_scheduled.connect(Callable(self, "_on_authority_inspection_scheduled")) authority_service.inspection_state_changed.connect(Callable(self, "_on_authority_inspection_state_changed")) authority_service.resolution_applied.connect(Callable(self, "_on_authority_resolution_applied")) func _initialize_s5_defense() -> void: _reset_defense_round_attempts() defense_service = DefenseService.new() defense_service.current_minute = _clock.absolute_minute _configure_defense_boundaries() _connect_defense_signals() func _defense_operation_minute() -> int: return _active_command_minute if _active_command_minute >= 0 else _clock.absolute_minute func _runtime_event_minute() -> int: return _active_command_minute if _active_command_minute >= 0 else _clock.absolute_minute func _configure_defense_boundaries() -> void: if defense_service == null: return defense_service.configure_callbacks( Callable(self, "_roll_defense_round"), Callable(self, "_apply_defense_skill_boundary"), Callable(self, "_apply_defense_outcome_boundary"), Callable(self, "_apply_defense_rng_boundary") ) func _connect_defense_signals() -> void: if defense_service == null: return var bindings := [ [&"raid_created", Callable(self, "_on_defense_raid_created")], [&"raid_state_changed", Callable(self, "_on_defense_raid_state_changed")], [&"active_skill_applied", Callable(self, "_on_defense_active_skill_applied")], [&"round_resolved", Callable(self, "_on_defense_round_resolved")], [&"raid_resolved", Callable(self, "_on_defense_raid_resolved")], ] for binding: Array in bindings: var signal_name: StringName = binding[0] var callback: Callable = binding[1] if not defense_service.is_connected(signal_name, callback): defense_service.connect(signal_name, callback) func _detach_defense_service(service: DefenseService) -> void: if service == null: return # A caller may retain a RefCounted aggregate across Runtime initialize/load. # Retiring it must sever both transaction callbacks and event projection so # stale objects cannot consume the new Runtime's RNG/resources or emit ghost # DomainEvents after an otherwise atomic restore. service.configure_callbacks() var bindings := [ [&"raid_created", Callable(self, "_on_defense_raid_created")], [&"raid_state_changed", Callable(self, "_on_defense_raid_state_changed")], [&"active_skill_applied", Callable(self, "_on_defense_active_skill_applied")], [&"round_resolved", Callable(self, "_on_defense_round_resolved")], [&"raid_resolved", Callable(self, "_on_defense_raid_resolved")], ] for binding: Array in bindings: var signal_name: StringName = binding[0] var callback: Callable = binding[1] if service.is_connected(signal_name, callback): service.disconnect(signal_name, callback) func _configure_facility_boundaries() -> void: if facility_service == null: return facility_service.configure_callbacks( Callable(self, "_validate_service_start"), Callable(self, "_complete_service_transaction") ) func _connect_facility_signals() -> void: if facility_service == null: return facility_service.instance_registered.connect(Callable(self, "_on_cleanliness_facility_registered")) facility_service.instance_unregistered.connect(Callable(self, "_on_cleanliness_facility_unregistered")) facility_service.instance_unregistered.connect(Callable(self, "_on_peeping_target_removed")) facility_service.facility_state_changed.connect(Callable(self, "_on_facility_state_changed")) facility_service.service_started.connect(Callable(self, "_on_service_started")) facility_service.service_interrupted.connect(Callable(self, "_on_service_interrupted")) facility_service.service_completed.connect(Callable(self, "_on_service_completed")) facility_service.service_failed.connect(Callable(self, "_on_service_failed")) func _seed_boundary_cells(bounds: Dictionary) -> void: for x in range(int(bounds[&"min_x"]), int(bounds[&"max_x"]) + 1): for layer in [WorldService.LAYER_FRONT, WorldService.LAYER_BACK]: var cell := Vector3i(x, 0, layer) world_service.set_walkable(cell, true) world_service.set_support(cell, 0, true) world_service.consume_dirty_cells() func _expand_world_to_rank(rank: int) -> bool: var bounds := _bounds_for_rank(rank) if not world_service.expand_bounds(bounds): return false _seed_boundary_cells(bounds) return rebuild_path_graph_from_world() func _bounds_for_rank(rank: int) -> Dictionary: var source: Variant = ProgressionService.RANK_BOUNDS.get(clampi(rank, 1, 7), {}) return source.duplicate(true) if source is Dictionary else {} func _sync_progression_live_metrics() -> bool: return progression_service.update_metrics({ &"active_above_ground_facilities": _count_active_facilities(facility_service, false), &"active_underground_facilities": _count_active_facilities(facility_service, true), &"asset_gold": economy_service.get_balance(PLAYER_CASH_ACCOUNT), }) func _count_active_facilities(service: FacilityService, underground: bool) -> int: var count := 0 for instance: FacilityInstance in service.get_instances(): if instance.state not in [FacilityInstance.State.OPEN, FacilityInstance.State.DEGRADED]: continue if (instance.origin.y < 0) == underground: count += 1 return count func _capture_rank_external_state() -> Dictionary: return { &"world": world_service.create_snapshot(), &"path": path_service.create_snapshot(), &"elevators": elevators.create_snapshot(), &"legacy_stairs": legacy_stairs.create_snapshot(), &"excavation": excavation_service.create_snapshot(), &"spatial_subslots": spatial_subslot_service.create_snapshot(), &"route_projection": route_subslot_path_adapter.create_snapshot(), &"facility_infrastructure": facility_infrastructure_projector.create_snapshot(), &"game_state": _state.create_snapshot(), &"movement_credits": _movement_credits.duplicate(true), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), } func _apply_rank_external_state(target_rank: int, expected_bounds: Dictionary) -> bool: if target_rank != int(_state.world_tree_rank) + 1 \ or not _strict_variants_equal(_bounds_for_rank(target_rank), expected_bounds): return false if not _expand_world_to_rank(target_rank): return false if not _state.set_world_tree_rank(target_rank): return false return ( int(_state.world_tree_rank) == target_rank and _strict_variants_equal(world_service.get_bounds(), expected_bounds) and _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ) and _validate_runtime_path_crosslinks( path_service, guest_service, staff_service, _movement_credits, _clock.absolute_minute ) ) func _rollback_rank_external_state(snapshot: Dictionary) -> bool: if not snapshot.get(&"world", null) is Dictionary \ or not snapshot.get(&"path", null) is Dictionary \ or not snapshot.get(&"excavation", null) is Dictionary \ or not snapshot.get(&"spatial_subslots", null) is Dictionary \ or not snapshot.get(&"route_projection", null) is Dictionary \ or not snapshot.get(&"facility_infrastructure", null) is Dictionary \ or not snapshot.get(&"game_state", null) is Dictionary \ or not snapshot.get(&"movement_credits", null) is Dictionary \ or not snapshot.get(&"domain_events", null) is Dictionary \ or not snapshot.get(&"id_generator", null) is Dictionary: return false var restored := world_service.restore_snapshot(snapshot[&"world"]) restored = spatial_subslot_service.restore_snapshot(snapshot[&"spatial_subslots"]) and restored var route_snapshot: Dictionary = snapshot[&"route_projection"] var projected_walkable: Variant = route_snapshot.get(&"projected_walkable_cells", null) if not projected_walkable is Array: return false restored = path_service.rebuild_graph(projected_walkable) and restored restored = path_service.restore_snapshot(snapshot[&"path"]) and restored restored = elevators.restore_snapshot(snapshot[&"elevators"]) and restored restored = legacy_stairs.restore_snapshot(snapshot[&"legacy_stairs"]) and restored restored = route_subslot_path_adapter.restore_snapshot(route_snapshot) and restored restored = facility_infrastructure_projector.restore_snapshot( snapshot[&"facility_infrastructure"] ) and restored restored = excavation_service.restore_snapshot(snapshot[&"excavation"]) and restored restored = _state.restore_snapshot(snapshot[&"game_state"]) and restored _movement_credits = (snapshot[&"movement_credits"] as Dictionary).duplicate(true) restored = domain_events.restore_snapshot(snapshot[&"domain_events"]) and restored restored = id_generator.restore_snapshot(snapshot[&"id_generator"]) and restored if not restored: return false return ( _strict_variants_equal(world_service.create_snapshot(), snapshot[&"world"]) and _strict_variants_equal(path_service.create_snapshot(), snapshot[&"path"]) and _strict_variants_equal(elevators.create_snapshot(), snapshot[&"elevators"]) and _strict_variants_equal(legacy_stairs.create_snapshot(), snapshot[&"legacy_stairs"]) and _strict_variants_equal( excavation_service.create_snapshot(), snapshot[&"excavation"] ) and _strict_variants_equal( spatial_subslot_service.create_snapshot(), snapshot[&"spatial_subslots"] ) and _strict_variants_equal( route_subslot_path_adapter.create_snapshot(), snapshot[&"route_projection"] ) and _strict_variants_equal( facility_infrastructure_projector.create_snapshot(), snapshot[&"facility_infrastructure"] ) and _strict_variants_equal(_state.create_snapshot(), snapshot[&"game_state"]) and _strict_variants_equal(_movement_credits, snapshot[&"movement_credits"]) and _strict_variants_equal(domain_events.create_snapshot(), snapshot[&"domain_events"]) and _strict_variants_equal(id_generator.create_snapshot(), snapshot[&"id_generator"]) and _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ) and _validate_runtime_path_crosslinks( path_service, guest_service, staff_service, _movement_credits, _clock.absolute_minute ) ) func _process_commands(absolute_minute: int) -> bool: var previous_command_minute := _active_command_minute _active_command_minute = absolute_minute var results := command_queue.execute_ready(absolute_minute, Callable(self, "_execute_command")) _active_command_minute = previous_command_minute _synchronize_operations_population() for result in results: var entity_ids: Array[StringName] = [] if not result.target_id.is_empty(): entity_ids.append(result.target_id) var event := SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"COMMAND_COMPLETED" if result.is_success() else &"COMMAND_FAILED", absolute_minute, &"simulation_runtime", entity_ids, result.to_dictionary(), result.command_id ) _publish_domain_event(event) command_completed.emit(result) if _operations_resume_publish_pending: _operations_resume_publish_pending = false _publish_snapshot(absolute_minute) _process_save_checkpoints(absolute_minute) return true return false func _process_tick_commands(current_absolute_minute: int) -> void: if _operations_enabled: _operations_tick_blocked = _operations_should_block_tick() if _operations_tick_blocked: _clock.set_paused(true) _clock.request_current_tick_abort() return _process_commands(current_absolute_minute + 1) _synchronize_operations_population() func _synchronize_operations_population() -> bool: if not _operations_enabled or operations_coordinator == null \ or _operations_transaction_active: return true var active_count := guest_service.guest_count() if operations_coordinator.get_population_count() == active_count: return true var synchronized := operations_coordinator.set_population_count(active_count) if synchronized.is_success(): return true _operations_runtime_failure_latched = true _operations_runtime_failure_report = synchronized.to_dictionary() _operations_tick_blocked = true _clock.set_paused(true) return false func _execute_command(command: SimulationCommand) -> CommandResult: if not _executors.has(command.command_type): return CommandResult.failed( CommandResult.Code.INVALID_COMMAND, command.command_id, "No executor is registered for command type '%s'." % command.command_type ) if command.command_type in DEFENSE_COMMAND_TYPES: var advanced := defense_service.advance_time(_defense_operation_minute()) if not advanced.is_success(): advanced.command_id = command.command_id return advanced var executor: Callable = _executors[command.command_type] var returned: Variant = executor.call(command) if returned is CommandResult: return returned return CommandResult.failed( CommandResult.Code.EXECUTOR_FAILED, command.command_id, "Command executor returned an invalid result." ) func _execute_change_resources(command: SimulationCommand) -> CommandResult: var deltas: Variant = command.payload.get(&"deltas", command.payload.get("deltas", null)) if not deltas is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "deltas must be a Dictionary.") var state_before: Dictionary = _state.create_snapshot() var economy_before := economy_service.create_snapshot() var progression_before := progression_service.create_snapshot() var events_before := domain_events.create_snapshot() var ids_before := id_generator.create_snapshot() if not _apply_command_resource_deltas(command, deltas): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Resource balance would become negative.") var faith_delta := 0 for raw_resource_id: Variant in deltas: if StringName(raw_resource_id) == &"faith": faith_delta += int(deltas[raw_resource_id]) if faith_delta > 0: var next_faith_lifetime := int(progression_service.metrics[&"faith_lifetime"]) + faith_delta if not progression_service.update_metrics({&"faith_lifetime": next_faith_lifetime}): economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) progression_service.restore_snapshot(progression_before) domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(ids_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Progression lifetime Faith could not be updated atomically." ) return CommandResult.succeeded(command.command_id, { &"revision": _state.revision, &"player_cash": economy_service.get_balance(PLAYER_CASH_ACCOUNT), &"faith_lifetime": int(progression_service.metrics[&"faith_lifetime"]), }) # Repay an existing attested liability through the ordinary command queue. # No rate, issuance, eligibility or due-date values are supplied here. func _execute_repay_loan_principal(command: SimulationCommand) -> CommandResult: var loan_id: Variant = command.payload.get(&"loan_id", null) if typeof(loan_id) not in [TYPE_STRING, TYPE_STRING_NAME] or String(loan_id).is_empty(): return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "loan_id must be a nonempty String or StringName.") var result := repay_loan_principal( StringName(loan_id), command.payload.get(&"amount", null), command.idempotency_key ) result.command_id = command.command_id result.target_id = StringName(loan_id) return result func _execute_spend_resources(command: SimulationCommand) -> CommandResult: var cost: Variant = command.payload.get(&"cost", command.payload.get("cost", null)) if not cost is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "cost must be a Dictionary.") if not operating_resource_state.spend(cost): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Resources are insufficient.") return CommandResult.succeeded(command.command_id, { &"revision": _state.revision, &"player_cash": economy_service.get_balance(PLAYER_CASH_ACCOUNT), }) func _apply_command_resource_deltas(command: SimulationCommand, deltas: Dictionary) -> bool: var normalized: Dictionary = {} for raw_name: Variant in deltas: var resource_name := StringName(raw_name) normalized[resource_name] = int(normalized.get(resource_name, 0)) + int(deltas[raw_name]) for resource_name: StringName in normalized: var current := economy_service.get_balance(PLAYER_CASH_ACCOUNT) \ if resource_name == &"gold" else int(_state.get_resource(resource_name)) if current + int(normalized[resource_name]) < 0: return false var before_state: Dictionary = _state.create_snapshot() var before_economy: Dictionary = economy_service.create_snapshot() var non_gold := normalized.duplicate(true) var gold_delta := int(non_gold.get(&"gold", 0)) non_gold.erase(&"gold") if not non_gold.is_empty() and not _state.apply_resource_deltas(non_gold): return false if gold_delta != 0: var ledger_result: CommandResult var ledger_idempotency := StringName("runtime.resource.%08d" % command.command_id) if gold_delta > 0: ledger_result = economy_service.transfer( EconomyService.SYSTEM_SOURCE, PLAYER_CASH_ACCOUNT, gold_delta, _runtime_event_minute(), &"GENERATION", command.actor_id, ledger_idempotency ) else: ledger_result = economy_service.transfer( PLAYER_CASH_ACCOUNT, EconomyService.SYSTEM_SINK, -gold_delta, _runtime_event_minute(), &"DESTRUCTION", command.actor_id, ledger_idempotency ) if not ledger_result.is_success(): _state.restore_snapshot(before_state) economy_service.restore_snapshot(before_economy) return false _state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT)) return true func _execute_add_inventory_stack(command: SimulationCommand) -> CommandResult: var owner_id := StringName(command.payload.get(&"owner_inventory_id", command.payload.get("owner_inventory_id", ""))) var item_id := StringName(command.payload.get(&"item_id", command.payload.get("item_id", ""))) var quantity := int(command.payload.get(&"quantity", command.payload.get("quantity", 0))) var result := inventory_service.add_stack( owner_id, item_id, quantity, int(command.payload.get(&"quality", command.payload.get("quality", InventoryService.DEFAULT_QUALITY))), int(command.payload.get(&"expires_at", command.payload.get("expires_at", -1))), _runtime_event_minute(), int(command.payload.get(&"unit_weight", command.payload.get("unit_weight", InventoryService.DEFAULT_UNIT_WEIGHT))), StringName(command.payload.get(&"stack_id", command.payload.get("stack_id", ""))) ) return _command_result_from_dictionary(command.command_id, result, "Inventory stack could not be added.") func _execute_create_logistics_request(command: SimulationCommand) -> CommandResult: var source_id := StringName(command.payload.get(&"source_inventory_id", command.payload.get("source_inventory_id", ""))) var destination_id := StringName(command.payload.get(&"destination_inventory_id", command.payload.get("destination_inventory_id", ""))) var raw_requirements: Variant = command.payload.get(&"item_requirements", command.payload.get("item_requirements", null)) if source_id.is_empty() or destination_id.is_empty() or source_id == destination_id \ or not raw_requirements is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Source, destination, and item requirements are required.") var requirements: Dictionary = {} for raw_item_id: Variant in raw_requirements: var item_id := StringName(raw_item_id) var quantity := int(raw_requirements[raw_item_id]) if item_id.is_empty() or quantity <= 0: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Logistics requirements must be positive.") requirements[item_id] = quantity if requirements.is_empty(): return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "At least one item is required.") var inventory_before := inventory_service.create_snapshot() var id_before := id_generator.create_snapshot() var request_id := StringName(command.payload.get(&"request_id", command.payload.get("request_id", ""))) if request_id.is_empty(): request_id = id_generator.next_id(&"logistics_request") if logistics_requests.has(request_id): id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.DUPLICATE_COMMAND_ID, command.command_id, "Logistics request id already exists.") var reservation_ids: Array[StringName] = [] var stack_ids: Array[StringName] = [] var total_weight := 0 var deadline := int(command.payload.get(&"deadline", command.payload.get("deadline", -1))) for item_id: StringName in _sorted_name_keys(requirements): var reserve_result := inventory_service.reserve_item( source_id, item_id, int(requirements[item_id]), request_id, &"logistics", _runtime_event_minute(), deadline, StringName("logistics.reserve.%s.%s" % [request_id, item_id]) ) if not bool(reserve_result.get(&"ok", false)): inventory_service.restore_snapshot(inventory_before) id_generator.restore_snapshot(id_before) return _command_result_from_dictionary(command.command_id, reserve_result, "Logistics stock could not be reserved.") for reservation_id: StringName in reserve_result.get(&"reservation_ids", []): reservation_ids.append(reservation_id) var reservation := inventory_service.get_reservation(reservation_id) var stack := inventory_service.get_stack(reservation.stack_id) stack_ids.append(reservation.stack_id) total_weight += reservation.quantity * int(stack.get(&"unit_weight", 1)) var request := LogisticsRequest.new().configure( request_id, source_id, destination_id, requirements, int(command.payload.get(&"priority", command.payload.get("priority", 50))), _runtime_event_minute(), deadline, total_weight ) if not request.mark_reserved(reservation_ids, stack_ids, _runtime_event_minute()): inventory_service.restore_snapshot(inventory_before) id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Logistics request could not enter RESERVED state.") logistics_requests[request_id] = request return CommandResult.succeeded(command.command_id, { &"request_id": request_id, &"reservation_ids": reservation_ids, &"total_weight": total_weight, }) func _execute_dispatch_logistics(command: SimulationCommand) -> CommandResult: var carriers: Variant = command.payload.get(&"carriers", command.payload.get("carriers", null)) if not carriers is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "carriers must be a Dictionary.") var manual_requests: Array[LogisticsRequest] = [] for request: LogisticsRequest in _sorted_logistics_requests(): if not String(request.request_id).begins_with(SupplyLogisticsBridge.REQUEST_PREFIX): manual_requests.append(request) var assignments := logistics_dispatcher.dispatch(manual_requests, carriers, _runtime_event_minute()) return CommandResult.succeeded(command.command_id, { &"assignments": assignments, &"report": logistics_dispatcher.last_report.duplicate(true), }) func _execute_advance_logistics_request(command: SimulationCommand) -> CommandResult: var request_id := StringName(command.payload.get(&"request_id", command.payload.get("request_id", ""))) if command.command_id > 0 and String(request_id).begins_with(SupplyLogisticsBridge.REQUEST_PREFIX): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Supply deliveries advance only after the porter arrives.", {&"reason": &"SUPPLY_DRIVER_REQUIRED"}) if not logistics_requests.has(request_id): return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Logistics request was not found.") var request: LogisticsRequest = logistics_requests[request_id] var request_before := request.create_snapshot() var inventory_before_command := inventory_service.create_snapshot() var dispatcher_before := logistics_dispatcher.create_snapshot() match request.state: LogisticsRequest.State.ASSIGNED: if not request.transition_to(LogisticsRequest.State.PICKING_UP, "", _runtime_event_minute()): return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Logistics pickup could not begin.") LogisticsRequest.State.PICKING_UP: var carrier_id := request.assigned_carrier_id var pickup := inventory_service.transfer_reserved( request.reservation_ids, StringName("cargo:%s" % carrier_id), StringName("logistics.pickup.%s" % request_id), _runtime_event_minute() ) if not bool(pickup.get(&"ok", false)): request.mark_blocked(String(pickup.get(&"code", &"PICKUP_FAILED")), _runtime_event_minute()) var released := logistics_dispatcher.release_assignment_for_request( request_id, carrier_id, request.total_weight ) if not released: inventory_service.restore_snapshot(inventory_before_command) logistics_dispatcher.restore_snapshot(dispatcher_before) logistics_requests[request_id] = LogisticsRequest.from_snapshot(request_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Logistics pickup failure could not release request ownership." ) return _command_result_from_dictionary(command.command_id, pickup, "Logistics pickup failed.") request.transition_to(LogisticsRequest.State.DELIVERING, "", _runtime_event_minute()) LogisticsRequest.State.DELIVERING: var carrier_id := request.assigned_carrier_id var cargo_id := StringName("cargo:%s" % carrier_id) var inventory_before := inventory_service.create_snapshot() for item_id: StringName in _sorted_name_keys(request.item_requirements): var delivery := inventory_service.transfer_available( cargo_id, request.destination_inventory_id, item_id, int(request.item_requirements[item_id]), StringName("logistics.delivery.%s.%s" % [request_id, item_id]), _runtime_event_minute() ) if not bool(delivery.get(&"ok", false)): inventory_service.restore_snapshot(inventory_before) request.mark_blocked(String(delivery.get(&"code", &"DELIVERY_FAILED")), _runtime_event_minute()) var released := logistics_dispatcher.release_assignment_for_request( request_id, carrier_id, request.total_weight ) if not released: inventory_service.restore_snapshot(inventory_before_command) logistics_dispatcher.restore_snapshot(dispatcher_before) logistics_requests[request_id] = LogisticsRequest.from_snapshot(request_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Logistics delivery failure could not release request ownership." ) return _command_result_from_dictionary(command.command_id, delivery, "Logistics delivery failed.") request.transition_to(LogisticsRequest.State.COMPLETED, "", _runtime_event_minute()) var released := logistics_dispatcher.release_assignment_for_request( request_id, carrier_id, request.total_weight ) if not released: inventory_service.restore_snapshot(inventory_before_command) logistics_dispatcher.restore_snapshot(dispatcher_before) logistics_requests[request_id] = LogisticsRequest.from_snapshot(request_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Completed Logistics delivery could not release request ownership." ) _: return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Logistics request cannot advance from its current state.") return CommandResult.succeeded(command.command_id, { &"request_id": request_id, &"state": LogisticsRequest.State.keys()[request.state], }) func _execute_register_guest_wallet(command: SimulationCommand) -> CommandResult: var guest_id := StringName(command.payload.get(&"guest_id", command.payload.get("guest_id", ""))) var amount := int(command.payload.get(&"gold", command.payload.get("gold", 0))) if guest_id.is_empty() or amount < 0: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "A guest id and non-negative Gold are required.") var account_id := _guest_wallet_account(guest_id) if economy_service.has_account(account_id): return CommandResult.failed(CommandResult.Code.DUPLICATE_COMMAND_ID, command.command_id, "Guest wallet already exists.") var before := economy_service.create_snapshot() if not economy_service.register_account(account_id): return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Guest wallet account could not be registered.") if amount > 0: var initialized := economy_service.initialize_account( account_id, amount, _runtime_event_minute(), StringName("guest.wallet.%s" % guest_id) ) if not initialized.is_success(): economy_service.restore_snapshot(before) return initialized return CommandResult.succeeded(command.command_id, {&"account_id": account_id, &"balance": amount}) func _execute_spawn_guest(command: SimulationCommand) -> CommandResult: if _operations_enabled and operations_coordinator != null: if operations_coordinator.is_terminal() \ or operations_coordinator.is_external_quarantined(): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Operations currently block new Guest arrivals." ) if guest_service.guest_count() >= int( operations_coordinator.get_config().get(&"population_cap", 0) ): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Operations Guest population capacity is full." ) var guest_id := StringName(command.payload.get(&"guest_id", command.payload.get("guest_id", ""))) var id_before := id_generator.create_snapshot() if guest_id.is_empty(): guest_id = id_generator.next_id(&"guest") if String(guest_id).begins_with("guest-auto-") \ and not (_operations_transaction_active \ and operations_coordinator != null \ and command.actor_id == &"operations_coordinator"): id_generator.restore_snapshot(id_before) return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "guest-auto IDs are reserved for the OperationsCoordinator." ) var guest_type_id := StringName(command.payload.get(&"guest_type_id", command.payload.get("guest_type_id", "traveler"))) var definition := guest_service.get_definition(guest_type_id) if definition == null: id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Guest definition was not found.") if guest_service.has_guest(guest_id): id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.DUPLICATE_COMMAND_ID, command.command_id, "Guest already exists.") var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) var options: Dictionary = raw_options.duplicate(true) if raw_options is Dictionary else {} var initial_cell_value: Variant = _parse_world_cell( options.get(&"current_cell", options.get("current_cell", Vector3i.ZERO)) ) if initial_cell_value == null: id_generator.restore_snapshot(id_before) return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Guest current_cell must be Vector3i or [x, y, layer]." ) var initial_cell: Vector3i = initial_cell_value var initial_traffic := path_service.get_cell_traffic(initial_cell) var path_actor_id := _guest_path_actor_id(guest_id) if initial_traffic.is_empty() or bool(initial_traffic.get(&"temporary_block", false)) \ or path_service.has_actor_projection(path_actor_id): id_generator.restore_snapshot(id_before) return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Guest current_cell must be an unblocked walkable cell with a free Path actor id." ) options[&"current_cell"] = initial_cell var requested_gold := int(command.payload.get( &"gold", command.payload.get("gold", options.get(&"wallet_gold", options.get("wallet_gold", definition.wealth_min))) )) if requested_gold < 0: id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Guest Gold cannot be negative.") var defer_spawn_notification := _operations_transaction_active \ and operations_coordinator != null \ and command.actor_id == &"operations_coordinator" var account_id := _guest_wallet_account(guest_id) var economy_before := economy_service.create_snapshot() var guest_before := guest_service.create_snapshot() var path_before := path_service.create_snapshot() var movement_credits_before := _movement_credits.duplicate(true) var events_before := domain_events.create_snapshot() var progression_before := progression_service.create_snapshot() if economy_service.has_account(account_id): requested_gold = economy_service.get_balance(account_id) else: if not economy_service.register_account(account_id): id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Guest wallet account could not be registered.") if requested_gold > 0: var initialized := economy_service.initialize_account( account_id, requested_gold, _runtime_event_minute(), StringName("guest.wallet.%s" % guest_id), not defer_spawn_notification ) if not initialized.is_success(): economy_service.restore_snapshot(economy_before) id_generator.restore_snapshot(id_before) initialized.command_id = command.command_id return initialized if defer_spawn_notification \ and not _stage_operations_economy_notification(initialized): economy_service.restore_snapshot(economy_before) id_generator.restore_snapshot(id_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Guest wallet notification could not be staged." ) options[&"wallet_gold"] = requested_gold var guest := guest_service.spawn_guest( guest_id, guest_type_id, _runtime_event_minute(), options, not defer_spawn_notification ) if guest != null and defer_spawn_notification: # Runtime's Path projection and DomainEvent are part of the external # transaction, while the observable GuestService signal is held until the # Coordinator minute and DomainEvent batch have both committed. _on_guest_spawned(guest) if guest == null or not path_service.has_actor_projection(path_actor_id) \ or path_service.get_actor_projected_cell(path_actor_id) != guest.current_cell \ or not _movement_credits.has(path_actor_id): economy_service.restore_snapshot(economy_before) guest_service.restore_snapshot(guest_before) path_service.restore_snapshot(path_before) _movement_credits = movement_credits_before domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(id_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, guest_service.last_error if guest == null else "Guest Path projection failed." ) var next_guest_lifetime := int(progression_service.metrics[&"guest_lifetime"]) + 1 if not progression_service.update_metrics({&"guest_lifetime": next_guest_lifetime}): economy_service.restore_snapshot(economy_before) guest_service.restore_snapshot(guest_before) path_service.restore_snapshot(path_before) _movement_credits = movement_credits_before domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(id_before) progression_service.restore_snapshot(progression_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Progression lifetime Guest count could not be updated atomically." ) if defer_spawn_notification: _operations_queued_guest_spawn_notifications.append(guest_id) return CommandResult.succeeded(command.command_id, { &"guest_id": guest_id, &"guest_type_id": guest_type_id, &"wallet_account": account_id, &"wallet_gold": requested_gold, &"guest_lifetime": next_guest_lifetime, }) func _execute_transition_guest(command: SimulationCommand) -> CommandResult: var guest_id := StringName(command.payload.get(&"guest_id", command.payload.get("guest_id", ""))) var state_index := _guest_state_index(command.payload.get(&"state", command.payload.get("state", -1))) if guest_id.is_empty() or state_index < 0: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Guest id and valid state are required.") var guest := guest_service.get_guest(guest_id) if guest == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Guest was not found.") if not guest.queue_request_id.is_empty() or not guest.current_service_id.is_empty(): return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "A queued or serviced Guest must transition through the owning Facility transaction." ) var result := guest_service.transition_guest( guest_id, state_index, _runtime_event_minute(), StringName(command.payload.get(&"reason", command.payload.get("reason", "PLAYER_COMMAND"))) ) return _command_result_from_dictionary(command.command_id, result, "Guest transition failed.") func _execute_decide_guest(command: SimulationCommand) -> CommandResult: var guest_id := StringName(command.payload.get(&"guest_id", command.payload.get("guest_id", ""))) var guest := guest_service.get_guest(guest_id) if guest == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Guest was not found.") var wallet_account := _guest_wallet_account(guest_id) if economy_service.has_account(wallet_account): guest.wallet_gold = economy_service.get_balance(wallet_account) var raw_candidates: Variant = command.payload.get(&"candidates", command.payload.get("candidates", [])) var candidates: Array = raw_candidates.duplicate(true) if raw_candidates is Array else [] if candidates.is_empty(): candidates = _project_guest_candidates(guest_id) var raw_context: Variant = command.payload.get(&"context", command.payload.get("context", {})) var context: Dictionary = raw_context.duplicate(true) if raw_context is Dictionary else {} context[&"absolute_minute"] = _runtime_event_minute() var result := _decide_guest_with_log(guest_id, candidates, _runtime_event_minute(), context) return _command_result_from_dictionary(command.command_id, result, "Guest decision failed.") func _execute_register_staff(command: SimulationCommand) -> CommandResult: var staff_id := StringName(command.payload.get(&"staff_id", command.payload.get("staff_id", ""))) var role_id := StringName(command.payload.get(&"role_id", command.payload.get("role_id", ""))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if staff_id.is_empty() or role_id.is_empty() or not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Staff id, role id and options are required.") if _is_mvp_staff_candidate_id(staff_id) or mvp_content_catalog.get_unique_character_ids().has(staff_id): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Catalog identities must be created through their validated recruitment flow." ) var options: Dictionary = raw_options.duplicate(true) var initial_cell_value: Variant = _parse_world_cell( options.get(&"current_cell", options.get("current_cell", Vector3i.ZERO)) ) if initial_cell_value == null: return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Staff current_cell must be Vector3i or [x, y, layer]." ) var initial_cell: Vector3i = initial_cell_value var initial_traffic := path_service.get_cell_traffic(initial_cell) var path_actor_id := _staff_path_actor_id(staff_id) if initial_traffic.is_empty() or bool(initial_traffic.get(&"temporary_block", false)) \ or path_service.has_actor_projection(path_actor_id): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Staff current_cell must be an unblocked walkable cell with a free Path actor id." ) options[&"current_cell"] = initial_cell var staff_before := staff_service.create_snapshot() var path_before := path_service.create_snapshot() var credits_before := _movement_credits.duplicate(true) var events_before := domain_events.create_snapshot() var ids_before := id_generator.create_snapshot() options[&"employment_effective_minute"] = _runtime_event_minute() var result := staff_service.create_staff(staff_id, role_id, options) if result.is_success() and ( not path_service.has_actor_projection(path_actor_id) or path_service.get_actor_projected_cell(path_actor_id) != initial_cell or not _movement_credits.has(path_actor_id) ): staff_service.restore_snapshot(staff_before) path_service.restore_snapshot(path_before) _movement_credits = credits_before domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(ids_before) result = CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Staff Path projection failed." ) result.command_id = command.command_id return result # Original 6.1 plus the approved Anri staff-screen choice. Other unique # characters still have no approved joining timing. Zero signing cost and both # canonical wage fields must be actual integers; payload aliases cannot qualify. func _is_approved_free_unique_hire(candidate_id: StringName, canonical: Dictionary) -> bool: if candidate_id != &"heroine_anri" or not mvp_content_catalog.is_valid(): return false var staff_id_value: Variant = canonical.get(&"staff_id", null) var role_value: Variant = canonical.get(&"role_id", null) var options_value: Variant = canonical.get(&"options", null) if typeof(role_value) not in [TYPE_STRING, TYPE_STRING_NAME] or StringName(role_value) != &"innkeeper": return false if typeof(staff_id_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(staff_id_value) != candidate_id or not options_value is Dictionary: return false var options: Dictionary = options_value var employment_value: Variant = options.get(&"employment_kind", null) if typeof(employment_value) not in [TYPE_STRING, TYPE_STRING_NAME] \ or StringName(employment_value) != &"UNIQUE" \ or typeof(canonical.get(&"adult_verified", null)) != TYPE_BOOL \ or not bool(canonical[&"adult_verified"]): return false for value: Variant in [canonical.get(&"signing_cost", null), canonical.get(&"daily_wage", null), options.get(&"daily_wage", null)]: if typeof(value) != TYPE_INT or int(value) != 0: return false return true func _execute_hire_staff_candidate(command: SimulationCommand) -> CommandResult: # Ordinary contracts retain the investment gate and its existing error # priority. Only Anri has approved free recruitment; classify that exception # from validated canonical data, never caller-supplied costs or employment. var operations_gate := _operations_construction_investment_gate(command.command_id) var raw_candidate_id: Variant = command.payload.get( &"candidate_id", command.payload.get("candidate_id", null) ) if operations_gate != null and ( typeof(raw_candidate_id) not in [TYPE_STRING, TYPE_STRING_NAME] or StringName(raw_candidate_id) != &"heroine_anri" ): return operations_gate if typeof(raw_candidate_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "A catalog Staff candidate id is required." ) var candidate_id := StringName(raw_candidate_id) var canonical := mvp_content_catalog.staff_hire_payload(candidate_id) if canonical.is_empty(): return CommandResult.failed( CommandResult.Code.NOT_FOUND, command.command_id, "Staff candidate was not found in the validated MVP catalog." ) if not bool(canonical.get(&"adult_verified", false)): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Staff candidate does not have verified adult eligibility." ) if operations_gate != null: var reason := StringName(operations_gate.details.get(&"reason", &"")) var can_join_without_investment := _is_approved_free_unique_hire(candidate_id, canonical) \ and reason in [&"ARREARS_REMAIN", &"UNPAID_EXPENSES"] \ and not _operations_should_block_tick() if not can_join_without_investment: return operations_gate if _hired_staff_candidate_ids.has(candidate_id) or staff_service.has_staff(candidate_id): return CommandResult.failed( CommandResult.Code.DUPLICATE_COMMAND_ID, command.command_id, "Staff candidate has already been hired." ) var initial_cell_value: Variant = _parse_world_cell( command.payload.get(&"current_cell", command.payload.get("current_cell", Vector3i.ZERO)) ) if initial_cell_value == null: return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Staff current_cell must be Vector3i or [x, y, layer]." ) var initial_cell: Vector3i = initial_cell_value var path_actor_id := _staff_path_actor_id(candidate_id) var traffic := path_service.get_cell_traffic(initial_cell) if traffic.is_empty() or bool(traffic.get(&"temporary_block", false)) \ or path_service.has_actor_projection(path_actor_id): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Staff current_cell must be an unblocked walkable cell with a free Path actor id." ) var assigned_facility_id := StringName(command.payload.get( &"facility_id", command.payload.get("facility_id", "") )) if not assigned_facility_id.is_empty() \ and not facility_service.has_instance(assigned_facility_id): return CommandResult.failed( CommandResult.Code.NOT_FOUND, command.command_id, "Assigned Staff facility was not found." ) var before := { &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"game_state": _state.create_snapshot(), &"staff": staff_service.create_snapshot(), &"path": path_service.create_snapshot(), &"elevators": elevators.create_snapshot(), &"legacy_stairs": legacy_stairs.create_snapshot(), &"movement_credits": _movement_credits.duplicate(true), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), &"hired_ids": _hired_staff_candidate_ids.duplicate(true), &"staffing_projection_enabled": _staffing_projection_enabled, } var signing_cost := int(canonical[&"signing_cost"]) if signing_cost > 0: var payment := economy_service.transfer( PLAYER_CASH_ACCOUNT, OPERATING_EXPENSE_ACCOUNT, signing_cost, _runtime_event_minute(), &"STAFF_SIGNING_COST", candidate_id, StringName("staff_signing_%s" % candidate_id), &"staff.signing_cost" ) if not payment.is_success(): payment.command_id = command.command_id return payment var options: Dictionary = (canonical[&"options"] as Dictionary).duplicate(true) options[&"current_cell"] = initial_cell options[&"assigned_facility_id"] = assigned_facility_id options[&"employment_effective_minute"] = _runtime_event_minute() var staff_result := staff_service.create_staff( candidate_id, StringName(canonical[&"role_id"]), options ) if not staff_result.is_success(): _rollback_staff_candidate_hire(before) staff_result.command_id = command.command_id return staff_result var preferred_shift := StringName(canonical[&"preferred_shift"]) var shift_day := _candidate_shift_start_day(preferred_shift, _runtime_event_minute()) var shift_result: CommandResult if preferred_shift in [&"day", &"night"]: shift_result = staff_service.create_standard_shift( candidate_id, preferred_shift, shift_day, assigned_facility_id ) else: shift_result = staff_service.create_flex_shift( candidate_id, shift_day, ShiftInstance.DAY_START, ShiftInstance.DAY_END, assigned_facility_id ) var shift_id := StringName(shift_result.details.get(&"shift_id", &"")) var hired_staff := staff_service.get_staff(candidate_id) var hired_shift := staff_service.get_shift(shift_id) if not shift_result.is_success() or hired_staff == null or hired_shift == null \ or hired_staff.daily_wage != int(canonical[&"daily_wage"]) \ or hired_staff.get_employment_kind_name() != StringName(canonical[&"options"][&"employment_kind"]) \ or not path_service.has_actor_projection(path_actor_id) \ or path_service.get_actor_projected_cell(path_actor_id) != initial_cell \ or not _movement_credits.has(path_actor_id): _rollback_staff_candidate_hire(before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Staff hire could not commit its employee, Shift, finance, and Path state atomically." ) _hired_staff_candidate_ids[candidate_id] = true _publish_actor_event( &"STAFF_CANDIDATE_HIRED", &"simulation_runtime", [candidate_id, shift_id], { &"candidate_id": candidate_id, &"role_id": StringName(canonical[&"role_id"]), &"daily_wage": int(canonical[&"daily_wage"]), &"employment_kind": hired_staff.get_employment_kind_name(), &"signing_cost": signing_cost, &"shift_id": shift_id, &"preferred_shift": preferred_shift, } ) return CommandResult.succeeded(command.command_id, { &"candidate_id": candidate_id, &"staff_id": candidate_id, &"role_id": StringName(canonical[&"role_id"]), &"daily_wage": int(canonical[&"daily_wage"]), &"employment_kind": hired_staff.get_employment_kind_name(), &"signing_cost": signing_cost, &"shift_id": shift_id, &"player_cash": economy_service.get_balance(PLAYER_CASH_ACCOUNT), }) func _execute_create_staff_shift(command: SimulationCommand) -> CommandResult: var staff_id := StringName(command.payload.get(&"staff_id", command.payload.get("staff_id", ""))) var shift_id := StringName(command.payload.get(&"shift_id", command.payload.get("shift_id", ""))) var start_day := int(command.payload.get(&"start_day", command.payload.get("start_day", _runtime_event_minute() / 1_440))) var facility_id := StringName(command.payload.get(&"facility_id", command.payload.get("facility_id", ""))) var template_id := StringName(command.payload.get(&"template_id", command.payload.get("template_id", "FLEX"))) var result: CommandResult if String(template_id).to_upper() in ["DAY", "NIGHT"]: result = staff_service.create_standard_shift(staff_id, template_id, start_day, facility_id, shift_id) else: var start_minute := int(command.payload.get(&"start_minute_of_day", command.payload.get("start_minute_of_day", posmod(_runtime_event_minute(), 1_440)))) var end_minute := int(command.payload.get(&"end_minute_of_day", command.payload.get("end_minute_of_day", posmod(start_minute + 480, 1_440)))) result = staff_service.create_flex_shift(staff_id, start_day, start_minute, end_minute, facility_id, shift_id) result.command_id = command.command_id return result func _candidate_shift_start_day(preferred_shift: StringName, absolute_minute: int) -> int: var day := absolute_minute / ShiftInstance.MINUTES_PER_DAY var minute_of_day := posmod(absolute_minute, ShiftInstance.MINUTES_PER_DAY) # A NIGHT record starts at 18:00 of its named day and remains meaningful even # when hired after that start. DAY/FLEX records move to tomorrow once today's # daytime window has ended. if preferred_shift != &"night" and minute_of_day >= ShiftInstance.DAY_END: day += 1 return day func _rollback_staff_candidate_hire(snapshot: Dictionary) -> bool: var restored := economy_service.restore_snapshot(snapshot[&"economy"]) restored = _state.restore_snapshot(snapshot[&"game_state"]) and restored restored = staff_service.restore_snapshot(snapshot[&"staff"]) and restored restored = path_service.restore_snapshot(snapshot[&"path"]) and restored restored = elevators.restore_snapshot(snapshot[&"elevators"]) and restored restored = legacy_stairs.restore_snapshot(snapshot[&"legacy_stairs"]) and restored _movement_credits = (snapshot[&"movement_credits"] as Dictionary).duplicate(true) restored = domain_events.restore_snapshot(snapshot[&"domain_events"]) and restored restored = id_generator.restore_snapshot(snapshot[&"id_generator"]) and restored _hired_staff_candidate_ids = (snapshot[&"hired_ids"] as Dictionary).duplicate(true) _staffing_projection_enabled = bool(snapshot[&"staffing_projection_enabled"]) return restored and _validate_runtime_path_crosslinks( path_service, guest_service, staff_service, _movement_credits, _runtime_event_minute() ) func _staff_role_matches_facility(role_id: StringName, definition: FacilityDefinition) -> bool: if definition.required_roles.is_empty() or role_id in definition.required_roles: return true # Existing public-bath data uses a legacy job ID; the paid roster exposes # the canonical attendant. The other aliases likewise retain saved job IDs. if definition.id == &"public_bath" and role_id == &"attendant" and &"bath_attendant" in definition.required_roles: return true if definition.id == &"massage_room" and role_id == &"masseur" and &"therapist" in definition.required_roles: return true return definition.id == &"security_station" and role_id == &"guard" and &"security" in definition.required_roles func _execute_set_staff_work_assignment(command: SimulationCommand) -> CommandResult: for key: StringName in [&"staff_id", &"facility_id", &"template_id"]: if typeof(command.payload.get(key, null)) not in [TYPE_STRING, TYPE_STRING_NAME]: return _staff_assignment_failure(command.command_id, &"INVALID_PAYLOAD", "Staff, facility, and shift IDs must be strings.") var staff_id := StringName(command.payload[&"staff_id"]) var facility_id := StringName(command.payload[&"facility_id"]) var template_id := StringName(command.payload[&"template_id"]) var staff := staff_service.get_staff(staff_id) if staff == null: return _staff_assignment_failure(command.command_id, &"STAFF_NOT_FOUND", "Staff was not found.") if template_id not in [&"DAY", &"NIGHT"]: return _staff_assignment_failure(command.command_id, &"INVALID_TEMPLATE", "Choose DAY or NIGHT.") var block_reason := _staff_assignment_block_reason(staff) if not block_reason.is_empty(): var messages := { &"DEFENSE_LOCKED": "Staff is participating in a defense operation.", &"EVENT_LOCKED": "Staff is participating in an event.", &"STAFF_BUSY": "Finish the current task before changing assignment.", &"STAFF_UNAVAILABLE": "Staff cannot change assignment in the current state.", } return _staff_assignment_failure(command.command_id, block_reason, messages[block_reason]) if not facility_id.is_empty(): var facility := facility_service.get_instance(facility_id) if facility == null: return _staff_assignment_failure(command.command_id, &"FACILITY_NOT_FOUND", "Facility was not found.") var definition := definition_registry.get_facility(facility.definition_id) if definition == null or definition.staff_max <= 0: return _staff_assignment_failure(command.command_id, &"FACILITY_UNSTAFFED", "This facility does not accept Staff assignments.") if not _staff_role_matches_facility(staff.role_id, definition) and not _is_anri_manager(staff): return _staff_assignment_failure(command.command_id, &"ROLE_MISMATCH", "Staff role does not match this facility.") if _staff_assignment_count(facility_id, template_id, staff_id) >= definition.staff_max: return _staff_assignment_failure(command.command_id, &"FACILITY_FULL", "This shift has reached the facility Staff limit.") if not _staff_can_reach_facility(staff, facility): return _staff_assignment_failure(command.command_id, &"NO_ROUTE", "Staff cannot reach this facility through an accessible route.") var result := staff_service.set_work_assignment(staff_id, facility_id, template_id, _runtime_event_minute()) result.command_id = command.command_id if result.is_success(): var actor_id := _staff_path_actor_id(staff_id) var trip := elevators.get_actor_trip(actor_id) if result.details.get(&"reason", &"") == &"ASSIGNMENT_UPDATED" and trip.get(&"state", &"") == PathService.ELEVATOR_REQUEST_QUEUED: elevators.cancel_actor(actor_id) _staffing_projection_enabled = true _sync_facility_staffing(_runtime_event_minute()) if result.details.get(&"reason", &"") == &"ASSIGNMENT_UPDATED": _publish_actor_event(&"STAFF_WORK_ASSIGNMENT_CHANGED", &"staff_service", [staff_id], result.details) return result ## Shared by the command and presentation; always derived from live state. func _staff_assignment_block_reason(staff: StaffRuntime) -> StringName: if not _defense_lock_owner(staff.staff_id, &"DEFENDER").is_empty(): return &"DEFENSE_LOCKED" if event_service.get_actor_locks().has(staff.staff_id): return &"EVENT_LOCKED" if not staff.current_task_id.is_empty(): return &"STAFF_BUSY" if not staff.can_take_shift() or staff.state not in [StaffRuntime.State.OFF_DUTY, StaffRuntime.State.COMMUTING, StaffRuntime.State.AVAILABLE]: return &"STAFF_UNAVAILABLE" return &"" func _staff_assignment_failure(command_id: int, reason: StringName, message: String) -> CommandResult: return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command_id, message, {&"reason": reason}) func _staff_assignment_count(facility_id: StringName, template_id: StringName, excluded_staff: StringName) -> int: var counted: Dictionary = {} for row: Dictionary in staff_service.get_work_assignments(): var staff_id := StringName(row[&"staff_id"]) if staff_id != excluded_staff and StringName(row[&"facility_id"]) == facility_id \ and StringName(row[&"template_id"]) == template_id: counted[staff_id] = true var interval := StaffService._work_interval(template_id, _runtime_event_minute()) for shift: ShiftInstance in staff_service.get_shifts(): if shift.staff_id != excluded_staff and not shift.cancelled \ and shift.assigned_facility_id == facility_id \ and shift.end_at > maxi(interval.x, _runtime_event_minute()) and shift.start_at < interval.y: counted[shift.staff_id] = true return counted.size() func _staff_can_reach_facility(staff: StaffRuntime, facility: FacilityInstance) -> bool: var occupied: Array[Vector3i] = facility.occupied_cells.duplicate() if occupied.is_empty(): occupied.append(facility.origin) var candidates: Dictionary = {} for cell: Vector3i in occupied: for offset: Vector3i in [Vector3i.ZERO, Vector3i.LEFT, Vector3i.RIGHT]: var candidate := cell + offset if path_service.has_cell(candidate): candidates[candidate] = true for candidate: Vector3i in candidates: if not path_service.find_path(staff.current_cell, candidate, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]).is_empty(): return true return false func _execute_set_staff_schedule_override(command: SimulationCommand) -> CommandResult: var staff_id := StringName(command.payload.get(&"staff_id", command.payload.get("staff_id", ""))) var start_day := int(command.payload.get(&"start_day", command.payload.get("start_day", -1))) var patch_value: Variant = command.payload.get(&"patch", command.payload.get("patch", null)) if not patch_value is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Schedule override patch must be a Dictionary.") var result := staff_service.set_schedule_override(staff_id, start_day, patch_value) result.command_id = command.command_id return result func _execute_create_staff_task(command: SimulationCommand) -> CommandResult: var task_type := StringName(command.payload.get(&"task_type", command.payload.get("task_type", ""))) var priority := int(command.payload.get(&"priority", command.payload.get("priority", 50))) var task_id := StringName(command.payload.get(&"task_id", command.payload.get("task_id", ""))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if task_type == CleaningBridge.TASK_TYPE: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Cleaning jobs are scheduled from actual facility dirt.") if task_type.is_empty() or not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Task type and options are required.") var result := staff_service.create_task(task_type, priority, _runtime_event_minute(), raw_options, task_id) result.command_id = command.command_id return result func _execute_begin_staff_task(command: SimulationCommand) -> CommandResult: var task_id := StringName(command.payload.get(&"task_id", command.payload.get("task_id", ""))) var result := staff_service.begin_task(task_id, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_progress_staff_task(command: SimulationCommand) -> CommandResult: var task_id := StringName(command.payload.get(&"task_id", command.payload.get("task_id", ""))) var task := staff_service.get_task(task_id) if task != null and task.task_type == CleaningBridge.TASK_TYPE: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Cleaning work advances only at its physical destination with game time.") var minutes := int(command.payload.get(&"minutes", command.payload.get("minutes", 1))) var result := staff_service.progress_task(task_id, minutes, _runtime_event_minute()) result.command_id = command.command_id return result func _original_relationship_identities_valid(payload: Dictionary, staff_registry: StaffService, catalog: MvpContentCatalog) -> bool: if payload.get("protagonist_id") != "player" or staff_registry == null: return false var subject := StringName(payload.get("subject_id", "")) var rival := StringName(payload.get("rival_id", "")) if subject == &"player" or rival == &"player" or subject == rival or not staff_registry.has_staff(subject): return false var heroine_definition := catalog.get_entry(&"unique_characters", subject) var rival_definition := catalog.get_entry(&"unique_characters", rival) var subject_staff := staff_registry.get_staff(subject) return subject_staff.employment_kind == StaffRuntime.EmploymentKind.UNIQUE \ and heroine_definition.get(&"role_id") == String(subject_staff.role_id) \ and heroine_definition.get(&"character_kind") == "heroine" \ and rival_definition.get(&"character_kind") == "rival" \ and heroine_definition.get(&"counterpart_id") == String(rival) \ and rival_definition.get(&"counterpart_id") == String(subject) func _execute_register_original_relationship(command: SimulationCommand) -> CommandResult: var result := relationship_service.register_original_relationship(command.payload) result.command_id = command.command_id return result func _execute_create_relationship(command: SimulationCommand) -> CommandResult: var actor_a_id := StringName(command.payload.get(&"actor_a_id", command.payload.get("actor_a_id", ""))) var actor_b_id := StringName(command.payload.get(&"actor_b_id", command.payload.get("actor_b_id", ""))) if not _event_actor_exists(actor_a_id) or not _event_actor_exists(actor_b_id): return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Both relationship actors must exist.") var relationship_id := StringName(command.payload.get(&"relationship_id", command.payload.get("relationship_id", ""))) var id_before := id_generator.create_snapshot() if relationship_id.is_empty(): relationship_id = id_generator.next_id(&"relationship") var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) var options: Dictionary = raw_options.duplicate(true) if raw_options is Dictionary else {} var result := relationship_service.create_relationship(relationship_id, actor_a_id, actor_b_id, options) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id return result func _execute_change_relationship(command: SimulationCommand) -> CommandResult: var relationship_id := StringName(command.payload.get(&"relationship_id", command.payload.get("relationship_id", ""))) var metric_id := StringName(command.payload.get(&"metric_id", command.payload.get("metric_id", ""))) var delta := int(command.payload.get(&"delta", command.payload.get("delta", 0))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) var options: Dictionary = raw_options.duplicate(true) if raw_options is Dictionary else {} var result := relationship_service.apply_value_change( relationship_id, metric_id, delta, _runtime_event_minute(), options ) result.command_id = command.command_id return result func _execute_attack_relationship_shield(command: SimulationCommand) -> CommandResult: var relationship_id := StringName(command.payload.get(&"relationship_id", command.payload.get("relationship_id", ""))) var action_id := StringName(command.payload.get(&"action_id", command.payload.get("action_id", ""))) var raw_terms: Variant = command.payload.get(&"terms", command.payload.get("terms", {})) if not raw_terms is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Shield attack terms must be a Dictionary.") var result := relationship_service.apply_shield_attack( relationship_id, action_id, _runtime_event_minute(), raw_terms ) result.command_id = command.command_id return result func _execute_request_route_lock(command: SimulationCommand) -> CommandResult: var relationship_id := StringName(command.payload.get(&"relationship_id", command.payload.get("relationship_id", ""))) var route_id := StringName(command.payload.get(&"route_id", command.payload.get("route_id", ""))) var lock_index := _route_lock_index(command.payload.get(&"route_lock", command.payload.get("route_lock", -1))) if lock_index < 0: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "A valid Route Lock state is required.") var result := relationship_service.request_route_lock( relationship_id, lock_index, route_id, bool(command.payload.get(&"irreversible_event", command.payload.get("irreversible_event", false))) ) result.command_id = command.command_id return result func _execute_confirm_route_lock(command: SimulationCommand) -> CommandResult: var relationship_id := StringName(command.payload.get(&"relationship_id", command.payload.get("relationship_id", ""))) var route_id := StringName(command.payload.get(&"route_id", command.payload.get("route_id", ""))) var result := relationship_service.confirm_route_lock(relationship_id, route_id) result.command_id = command.command_id return result func _execute_register_event_definition(command: SimulationCommand) -> CommandResult: var raw_definition: Variant = command.payload.get(&"definition", command.payload.get("definition", null)) var definition := EventDefinition.from_variant(raw_definition) if definition == null: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Event definition is malformed.") var result := event_service.register_definition(definition) result.command_id = command.command_id return result func _execute_enqueue_event(command: SimulationCommand) -> CommandResult: var event_id := StringName(command.payload.get(&"event_id", command.payload.get("event_id", ""))) var actor_ids := _name_array(command.payload.get(&"actor_ids", command.payload.get("actor_ids", []))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) var options: Dictionary = raw_options.duplicate(true) if raw_options is Dictionary else {} var result := event_service.enqueue_candidate(event_id, actor_ids, _runtime_event_minute(), options) result.command_id = command.command_id return result func _execute_reserve_event_choice_cost(command: SimulationCommand) -> CommandResult: var instance_id := StringName(command.payload.get(&"event_instance_id", command.payload.get("event_instance_id", ""))) var choice_id := StringName(command.payload.get(&"choice_id", command.payload.get("choice_id", ""))) var result := event_service.reserve_choice_cost(instance_id, choice_id, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_submit_event_choice(command: SimulationCommand) -> CommandResult: var instance_id := StringName(command.payload.get(&"event_instance_id", command.payload.get("event_instance_id", ""))) var choice_instance_id := StringName(command.payload.get(&"choice_instance_id", command.payload.get("choice_instance_id", ""))) var choice_id := StringName(command.payload.get(&"choice_id", command.payload.get("choice_id", ""))) var result := event_service.submit_choice( instance_id, choice_instance_id, choice_id, _runtime_event_minute() ) if result.is_success() and event_service.get_foreground_instance() == null: _clock.set_paused(false) result.command_id = command.command_id return result func _execute_report_authority_incident(command: SimulationCommand) -> CommandResult: var incident_id := StringName(command.payload.get(&"incident_id", command.payload.get("incident_id", ""))) var incident_type := StringName(command.payload.get(&"incident_type", command.payload.get("incident_type", ""))) var detector_id := StringName(command.payload.get(&"detector_id", command.payload.get("detector_id", command.actor_id))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Incident options must be a Dictionary.") var id_before := id_generator.create_snapshot() if incident_id.is_empty(): incident_id = id_generator.next_id(&"incident") var result := authority_service.create_incident( incident_id, incident_type, _runtime_event_minute(), detector_id, (raw_options as Dictionary).duplicate(true) ) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id return result func _execute_register_authority_evidence(command: SimulationCommand) -> CommandResult: var evidence_id := StringName(command.payload.get(&"evidence_id", command.payload.get("evidence_id", ""))) var source := StringName(command.payload.get(&"source", command.payload.get("source", ""))) var severity := int(command.payload.get(&"severity", command.payload.get("severity", -1))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Evidence options must be a Dictionary.") var options: Dictionary = (raw_options as Dictionary).duplicate(true) if not options.has(&"created_at") and not options.has("created_at"): options[&"created_at"] = _runtime_event_minute() var id_before := id_generator.create_snapshot() if evidence_id.is_empty(): evidence_id = id_generator.next_id(&"evidence") var result := authority_service.create_evidence(evidence_id, source, severity, options) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id return result func _execute_discover_authority_evidence(command: SimulationCommand) -> CommandResult: var evidence_id := StringName(command.payload.get(&"evidence_id", command.payload.get("evidence_id", ""))) var inspector_id := StringName(command.payload.get(&"inspector_id", command.payload.get("inspector_id", command.actor_id))) var parsed_cell: Variant = _parse_world_cell(command.payload.get(&"inspector_cell", command.payload.get("inspector_cell", null))) var raw_context: Variant = command.payload.get(&"context", command.payload.get("context", {})) if parsed_cell == null or not raw_context is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Evidence discovery requires inspector_cell and Dictionary context.") var result := authority_service.attempt_discover_evidence( evidence_id, inspector_id, parsed_cell, _runtime_event_minute(), (raw_context as Dictionary).duplicate(true) ) result.command_id = command.command_id return result func _execute_evaluate_authority_inspection(command: SimulationCommand) -> CommandResult: var raw_terms: Variant = command.payload.get(&"terms", command.payload.get("terms", {})) if not raw_terms is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Inspection evaluation terms must be a Dictionary.") var result := authority_service.evaluate_inspection( _runtime_event_minute(), (raw_terms as Dictionary).duplicate(true) ) if result.is_success() and not result.is_replay: _publish_authority_evaluation(result.details) result.command_id = command.command_id return result func _execute_schedule_authority_inspection(command: SimulationCommand) -> CommandResult: var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Inspection options must be a Dictionary.") var scheduled_at := int(command.payload.get(&"scheduled_at", command.payload.get("scheduled_at", _runtime_event_minute()))) var result := authority_service.schedule_inspection( scheduled_at, (raw_options as Dictionary).duplicate(true) ) result.command_id = command.command_id return result func _execute_start_authority_inspection(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var result := authority_service.start_inspection(inspection_id, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_arrive_authority_inspection(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var result := authority_service.mark_inspector_arrived(inspection_id, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_plan_authority_inspection(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var raw_candidates: Variant = command.payload.get(&"candidates", command.payload.get("candidates", null)) if not raw_candidates is Array: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Inspection patrol candidates must be an Array.") var time_budget := int(command.payload.get(&"time_budget_minutes", command.payload.get("time_budget_minutes", 120))) var result := authority_service.plan_patrol( inspection_id, raw_candidates, _runtime_event_minute(), time_budget ) result.command_id = command.command_id return result func _execute_advance_authority_inspection(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var raw_context: Variant = command.payload.get(&"context", command.payload.get("context", {})) if not raw_context is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Inspection path context must be a Dictionary.") var result := authority_service.advance_patrol( inspection_id, _runtime_event_minute(), (raw_context as Dictionary).duplicate(true) ) if result.is_success() and result.details.has(&"reachable"): var event_type := &"INSPECTION_WAYPOINT_REACHED" if bool(result.details[&"reachable"]) else &"INSPECTION_PATH_BLOCKED" _publish_actor_event( event_type, &"authority_service", [inspection_id, StringName(result.details.get(&"waypoint_id", ""))], result.details.duplicate(true) ) result.command_id = command.command_id return result func _execute_inspect_authority_waypoint(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var evidence_ids := _name_array(command.payload.get(&"evidence_ids", command.payload.get("evidence_ids", []))) var raw_context: Variant = command.payload.get(&"context", command.payload.get("context", {})) if not raw_context is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Inspection evidence context must be a Dictionary.") var result := authority_service.inspect_current_waypoint( inspection_id, evidence_ids, _runtime_event_minute(), (raw_context as Dictionary).duplicate(true) ) if result.is_success(): _publish_actor_event( &"INSPECTION_WAYPOINT_INSPECTED", &"authority_service", [inspection_id], result.details.duplicate(true) ) result.command_id = command.command_id return result func _execute_resolve_authority_inspection(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var resolution_id := StringName(command.payload.get(&"resolution_id", command.payload.get("resolution_id", ""))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Inspection resolution options must be a Dictionary.") var id_before := id_generator.create_snapshot() if resolution_id.is_empty(): resolution_id = id_generator.next_id(&"resolution") var result := authority_service.resolve_inspection( inspection_id, resolution_id, _runtime_event_minute(), (raw_options as Dictionary).duplicate(true) ) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id return result func _execute_depart_authority_inspection(command: SimulationCommand) -> CommandResult: var inspection_id := StringName(command.payload.get(&"inspection_id", command.payload.get("inspection_id", ""))) var result := authority_service.depart_inspection(inspection_id, _runtime_event_minute()) result.command_id = command.command_id return result ## Existing Defense commands remain authoritative. This report only explains ## why a live employee or target can be registered; no balance data is invented. func get_defense_management_report(selected_raid_id: StringName = &"") -> Dictionary: var raids: Array[Dictionary] = defense_service.get_management_rows() var selected: Dictionary = {} for row: Dictionary in raids: if row[&"raid_id"] == selected_raid_id: selected = row break if selected.is_empty() and not raids.is_empty(): selected = raids[0] for row: Dictionary in raids: if int(row[&"state"]) not in DefenseService.TERMINAL_STATES: selected = row break var raid_id := StringName(selected.get(&"raid_id", &"")) var detail := defense_service.get_management_detail(raid_id) var defenders: Array = detail.get(&"defenders", []) var assets: Array = detail.get(&"assets", []) var defender_owners: Dictionary = {} var asset_owners: Dictionary = {} for row: Dictionary in raids: if int(row[&"state"]) in DefenseService.TERMINAL_STATES: continue for actor_id: StringName in row[&"defender_ids"]: defender_owners[actor_id] = row[&"raid_id"] for asset_id: StringName in row[&"asset_ids"]: asset_owners[asset_id] = row[&"raid_id"] var preparing := int(selected.get(&"state", -1)) == DefenseService.RaidState.PREPARING var staff_rows: Array[Dictionary] = [] for staff: StaffRuntime in staff_service.get_staff_members(): var entry := mvp_content_catalog.get_recruitment_entry(staff.staff_id) var registered := false for actor: Dictionary in defenders: registered = registered or actor[&"actor_id"] == staff.staff_id var reason := "" if not preparing: reason = "準備中だけ参加者を追加できます。" elif registered: reason = "この防衛戦に参加済みです。" elif defender_owners.has(staff.staff_id) and defender_owners[staff.staff_id] != raid_id: reason = "別の防衛戦に参加しています。" elif not staff.can_take_shift(): reason = "現在の体調や状態では参加できません。" elif not staff.current_task_id.is_empty(): reason = "担当中の業務が完了するまで参加できません。" staff_rows.append({ &"staff_id": staff.staff_id, &"display_name": String(entry.get(&"display_name", staff.staff_id)), &"combat": int(staff.skills.get(&"combat", 0)), &"health": staff.health, &"fatigue": staff.fatigue, &"registered": registered, &"can_register": reason.is_empty(), &"reason": reason, }) var objective_rows: Array[Dictionary] = [] for objective_id: StringName in selected.get(&"objective_ids", []): var facility := facility_service.get_instance(objective_id) var definition: Variant = definition_registry.get_facility(facility.definition_id) if facility != null else null var registered := false for asset: Dictionary in assets: registered = registered or asset[&"asset_id"] == objective_id var reason := "" if not preparing: reason = "準備中だけ防衛対象を登録できます。" elif registered: reason = "登録済みです。" elif facility == null: reason = "指定された施設が見つかりません。" elif facility.durability <= 0: reason = "破壊された施設は登録できません。" elif not facility.queue_entries.is_empty() or not facility.active_services.is_empty(): reason = "利用中または待機中の顧客がいます。" elif asset_owners.has(objective_id) and asset_owners[objective_id] != raid_id: reason = "別の防衛戦の対象です。" objective_rows.append({ &"facility_id": objective_id, &"display_name": String(definition.display_name) if definition != null else String(objective_id), &"durability": facility.durability if facility != null else 0, &"registered": registered, &"can_register": reason.is_empty(), &"reason": reason, }) return {&"raids": raids, &"selected": selected, &"staff": staff_rows, &"objectives": objective_rows, &"defenders": defenders, &"assets": assets, &"can_control": not (_operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal())} func _execute_create_defense_raid(command: SimulationCommand) -> CommandResult: var raw_definition: Variant = command.payload.get(&"definition", command.payload.get("definition", null)) if not raw_definition is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Defense Raid definition must be a Dictionary.") var event_source_value: Variant = raw_definition.get(&"event_source", raw_definition.get("event_source", {})) if not event_source_value is Dictionary or not event_source_value.is_empty(): return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Event threat provenance is reserved to the configured Event boundary.") var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var id_before := id_generator.create_snapshot() if raid_id.is_empty(): raid_id = id_generator.next_id(&"defense_raid") var definition: Dictionary = (raw_definition as Dictionary).duplicate(true) if not definition.has(&"source_id") and not definition.has("source_id"): definition[&"source_id"] = command.actor_id var result := defense_service.create_raid(raid_id, _defense_operation_minute(), definition) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id result.target_id = raid_id return result func _execute_register_defense_combatant(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var actor_id := StringName(command.payload.get(&"actor_id", command.payload.get("actor_id", command.payload.get(&"staff_id", command.payload.get("staff_id", ""))))) var side := StringName(String(command.payload.get(&"side", command.payload.get("side", ""))).to_upper()) var combat := int(command.payload.get(&"combat", command.payload.get("combat", 0))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if raid_id.is_empty() or actor_id.is_empty() or not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Defense combatant requires raid_id, actor_id and Dictionary options.") if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before combatant registration.") var options: Dictionary = (raw_options as Dictionary).duplicate(true) var max_health := int(command.payload.get(&"max_health", command.payload.get("max_health", 100))) if side == &"DEFENDER": var staff := staff_service.get_staff(actor_id) if staff == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Defense defender must reference existing Staff.") if not staff.can_take_shift() or not staff.current_task_id.is_empty(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense defender must be healthy enough to participate and free of Staff tasks.") var lock_owner: StringName = _defense_lock_owner(actor_id, &"DEFENDER", raid_id) if not lock_owner.is_empty(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Staff is already assigned to another non-terminal Raid.", {&"raid_id": lock_owner}) if max_health != StaffRuntime.MAX_PSYCHOLOGY_VALUE \ or ((options.has(&"health") or options.has("health")) and int(options.get(&"health", options.get("health", -1))) != staff.health): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defender health must use the current Staff health on a 100-point baseline.") max_health = StaffRuntime.MAX_PSYCHOLOGY_VALUE options[&"health"] = staff.health options[&"fatigue"] = staff.fatigue var staff_before: Dictionary = staff_service.create_snapshot() staff.transition_to(StaffRuntime.State.UNAVAILABLE, &"DEFENSE_ASSIGNED", _defense_operation_minute()) var defender_result := defense_service.register_combatant( raid_id, actor_id, side, combat, max_health, options ) if not defender_result.is_success(): staff_service.restore_snapshot(staff_before) defender_result.command_id = command.command_id defender_result.target_id = raid_id return defender_result elif side != &"ATTACKER": return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Defense combatant side must be DEFENDER or ATTACKER.") var result := defense_service.register_combatant( raid_id, actor_id, side, combat, max_health, options ) result.command_id = command.command_id result.target_id = raid_id return result func _execute_register_defense_asset(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var asset_id := StringName(command.payload.get(&"asset_id", command.payload.get("asset_id", command.payload.get(&"facility_id", command.payload.get("facility_id", ""))))) var kind := StringName(String(command.payload.get(&"kind", command.payload.get("kind", ""))).to_upper()) var defense := int(command.payload.get(&"defense", command.payload.get("defense", 0))) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if raid_id.is_empty() or asset_id.is_empty() or not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Defense asset requires raid_id, facility asset_id and Dictionary options.") if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before asset registration.") var facility := facility_service.get_instance(asset_id) if facility == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Defense asset must reference an existing Facility.") if not facility.queue_entries.is_empty() or not facility.active_services.is_empty(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "A busy Facility cannot become a Defense asset.") var lock_owner: StringName = _defense_lock_owner(asset_id, &"ASSET", raid_id) if not lock_owner.is_empty(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Facility is already assigned to another non-terminal Raid.", {&"raid_id": lock_owner}) var supplied_maximum := int(command.payload.get(&"max_integrity", command.payload.get("max_integrity", FacilityInstance.DEFAULT_DURABILITY))) var options: Dictionary = (raw_options as Dictionary).duplicate(true) if supplied_maximum != FacilityInstance.DEFAULT_DURABILITY \ or ((options.has(&"integrity") or options.has("integrity")) and int(options.get(&"integrity", options.get("integrity", -1))) != facility.durability): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense asset integrity must use the current Facility durability on a 10000-point baseline.") options[&"integrity"] = facility.durability var result := defense_service.register_asset( raid_id, asset_id, kind, FacilityInstance.DEFAULT_DURABILITY, defense, options ) result.command_id = command.command_id result.target_id = raid_id return result func _execute_start_defense_raid(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before Raid start.") var result := defense_service.start_raid(raid_id, _defense_operation_minute()) result.command_id = command.command_id result.target_id = raid_id return result func _execute_set_defense_retreat_policy(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var policy := StringName(String(command.payload.get(&"policy", command.payload.get("policy", ""))).to_upper()) var threshold := int(command.payload.get(&"threshold", command.payload.get("threshold", -1))) if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before retreat-policy update.") var result := defense_service.set_retreat_policy(raid_id, policy, threshold) result.command_id = command.command_id result.target_id = raid_id return result func _execute_queue_defense_skill(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var action_id := StringName(command.payload.get(&"action_id", command.payload.get("action_id", ""))) var actor_id := StringName(command.payload.get(&"actor_id", command.payload.get("actor_id", command.actor_id))) var kind := StringName(String(command.payload.get(&"kind", command.payload.get("kind", ""))).to_upper()) var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Defense skill options must be a Dictionary.") if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before skill queueing.") var id_before := id_generator.create_snapshot() if action_id.is_empty(): action_id = id_generator.next_id(&"defense_skill_action") var result := defense_service.queue_active_skill( raid_id, action_id, actor_id, kind, (raw_options as Dictionary).duplicate(true) ) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id result.target_id = raid_id return result func _execute_resolve_defense_round(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var resolution_id := StringName(command.payload.get(&"resolution_id", command.payload.get("resolution_id", ""))) var result := _resolve_defense_round_internal(raid_id, resolution_id, _defense_operation_minute()) result.command_id = command.command_id result.target_id = raid_id return result ## Both the explicit command and ordinary clock progression use the same ## transaction, generated-ID namespace and cross-domain callback boundaries. func _resolve_defense_round_internal( raid_id: StringName, resolution_id: StringName, absolute_minute: int ) -> CommandResult: _prepare_defense_round_attempts(absolute_minute) var already_attempted := _defense_round_attempted_raids.has(raid_id) _defense_round_attempted_raids[raid_id] = true var previous_command_minute := _active_command_minute _active_command_minute = absolute_minute if not _validate_live_defense_crosslinks(): _active_command_minute = previous_command_minute return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "Defense cross-domain links are invalid before round resolution.") var id_before := id_generator.create_snapshot() if resolution_id.is_empty(): # Existing explicit IDs can occupy this namespace without advancing the # allocator. Skip those IDs instead of replaying history as a new attack. while id_generator.get_counter(&"defense_round_resolution") < StableIdGenerator.MAX_COUNTER: resolution_id = id_generator.next_id(&"defense_round_resolution") if not defense_service.has_resolution(raid_id, resolution_id): break if resolution_id.is_empty() or defense_service.has_resolution(raid_id, resolution_id): id_generator.restore_snapshot(id_before) _active_command_minute = previous_command_minute var exhausted := CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "Defense round ID namespace is exhausted.") exhausted.target_id = raid_id return exhausted var result := defense_service.resolve_next_round(raid_id, resolution_id, absolute_minute) # Reading an old canonical result is not a new round attempt. Restore only # the guard introduced by this replay; an earlier real failure in this same # minute must still prevent automatic progression from hiding that failure. if result.is_replay and not already_attempted: _defense_round_attempted_raids.erase(raid_id) if not result.is_success(): id_generator.restore_snapshot(id_before) _active_command_minute = previous_command_minute result.target_id = raid_id return result func _prepare_defense_round_attempts(absolute_minute: int) -> void: if _defense_round_attempt_minute == absolute_minute: return _defense_round_attempt_minute = absolute_minute _defense_round_attempted_raids.clear() _defense_automatic_failed_minute = -1 func _reset_defense_round_attempts() -> void: _defense_round_attempt_minute = -1 _defense_round_attempted_raids.clear() _defense_automatic_failed_minute = -1 func _execute_begin_next_defense_wave(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before the next Wave.") var result := defense_service.begin_next_wave(raid_id, _defense_operation_minute()) result.command_id = command.command_id result.target_id = raid_id return result func _execute_retreat_defenders(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var resolution_id := StringName(command.payload.get(&"resolution_id", command.payload.get("resolution_id", ""))) var reason := StringName(command.payload.get(&"reason", command.payload.get("reason", &"PLAYER_RETREAT"))) if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before defender retreat.") var id_before := id_generator.create_snapshot() if resolution_id.is_empty(): resolution_id = id_generator.next_id(&"defense_retreat_resolution") var result := defense_service.retreat_defenders(raid_id, resolution_id, _defense_operation_minute(), reason) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id result.target_id = raid_id return result func _execute_cancel_defense_raid(command: SimulationCommand) -> CommandResult: var raid_id := StringName(command.payload.get(&"raid_id", command.payload.get("raid_id", command.target_id))) var resolution_id := StringName(command.payload.get(&"resolution_id", command.payload.get("resolution_id", ""))) var reason := StringName(command.payload.get(&"reason", command.payload.get("reason", &"CANCELLED"))) if not _validate_live_defense_crosslinks(): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense cross-domain links are invalid before Raid cancellation.") var id_before := id_generator.create_snapshot() if resolution_id.is_empty(): resolution_id = id_generator.next_id(&"defense_cancel_resolution") var result := defense_service.cancel_raid(raid_id, _defense_operation_minute(), reason, resolution_id) if not result.is_success(): id_generator.restore_snapshot(id_before) result.command_id = command.command_id result.target_id = raid_id return result func _execute_create_service_request(command: SimulationCommand) -> CommandResult: var facility_id := StringName(command.payload.get(&"facility_id", command.payload.get("facility_id", ""))) var instance := facility_service.get_instance(facility_id) if instance == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Facility was not found.") var defense_owner: StringName = _defense_lock_owner(facility_id, &"ASSET") if not defense_owner.is_empty(): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "A Facility assigned to a non-terminal Defense Raid cannot accept service requests.", {&"raid_id": defense_owner} ) var guest_ids := _name_array(command.payload.get(&"guest_ids", command.payload.get("guest_ids", []))) if guest_ids.is_empty(): return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "At least one guest id is required.") for guest_id: StringName in guest_ids: if not economy_service.has_account(_guest_wallet_account(guest_id)): return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Every guest needs a registered wallet.") var raw_options: Variant = command.payload.get(&"options", command.payload.get("options", {})) if not raw_options is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Service request options must be a Dictionary.") var options: Dictionary = raw_options.duplicate(true) var quoted_price := maxi( int(options.get(&"quoted_price", options.get("quoted_price", get_facility_business_price(instance)))), 0 ) # Freeze the same price used by candidate selection and the business panel. options[&"quoted_price"] = quoted_price if not instance.operator_open or not instance.is_within_opening_hours(_runtime_event_minute()): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "This facility is closed to new service requests.", {&"reason": &"FACILITY_CLOSED"}) if instance.requires_operating_kitchen() and not bool(facility_service.get_required_kitchen_report(facility_id).get(&"operating", false)): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "This dining facility requires an operating BACK kitchen.", {&"reason": &"FACILITY_CLOSED", &"dependency_reason": facility_service.get_required_kitchen_report(facility_id).get(&"reason", &"")}) var payer_account := _guest_wallet_account(guest_ids[0]) var committed_price := _outstanding_service_commitment(guest_ids[0]) if not economy_service.account_allows_negative(payer_account) \ and economy_service.get_balance(payer_account) < committed_price + quoted_price: return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "The guest cannot fund this service together with existing commitments.", { &"available_gold": economy_service.get_balance(payer_account), &"committed_gold": committed_price, &"quoted_price": quoted_price, } ) var inventory_before := inventory_service.create_snapshot() var facility_before := facility_service.create_snapshot() var guest_before := guest_service.create_snapshot() var events_before := domain_events.create_snapshot() var id_before := id_generator.create_snapshot() var request_id := StringName(command.payload.get(&"request_id", command.payload.get("request_id", ""))) if request_id.is_empty(): request_id = id_generator.next_id(&"service_request") var definition := definition_registry.get_facility(instance.definition_id) if definition == null: id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Facility definition was not found.") var reservation_ids: Array[StringName] = [] for item_id: StringName in _sorted_name_keys(definition.inventory_inputs): var reserve_result := inventory_service.reserve_item( instance.local_inventory_id, item_id, int(definition.inventory_inputs[item_id]), request_id, &"facility_service", _runtime_event_minute(), _runtime_event_minute() + 240, StringName("service.reserve.%s.%s" % [request_id, item_id]) ) if not bool(reserve_result.get(&"ok", false)): inventory_service.restore_snapshot(inventory_before) id_generator.restore_snapshot(id_before) return _command_result_from_dictionary(command.command_id, reserve_result, "Facility input could not be reserved.") for reservation_id: StringName in reserve_result.get(&"reservation_ids", []): reservation_ids.append(reservation_id) options[&"reserved_inventory_ids"] = reservation_ids var request := facility_service.create_request( request_id, facility_id, guest_ids, StringName(command.payload.get(&"service_type", command.payload.get("service_type", "default"))), _runtime_event_minute(), options ) var enqueue_result: CommandResult var runtime_guests: Array[StringName] = [] for guest_id: StringName in guest_ids: if guest_service.has_guest(guest_id): runtime_guests.append(guest_id) if runtime_guests.is_empty(): enqueue_result = facility_service.enqueue_request(request, _runtime_event_minute(), not (_operations_enabled and _active_command_minute == _clock.absolute_minute + 1)) else: var first_guest := runtime_guests[0] var queue_result := guest_service.enter_queue( first_guest, facility_id, request_id, _runtime_event_minute(), int(options.get(&"initial_patience", options.get("initial_patience", -1))), func(_guest_id: StringName, _facility_id: StringName, _request_id: StringName) -> CommandResult: return facility_service.enqueue_request(request, _runtime_event_minute(), not (_operations_enabled and _active_command_minute == _clock.absolute_minute + 1)) ) if bool(queue_result.get(&"ok", false)): for guest_index in range(1, runtime_guests.size()): var joined := guest_service.enter_queue( runtime_guests[guest_index], facility_id, request_id, _runtime_event_minute(), int(options.get(&"initial_patience", options.get("initial_patience", -1))) ) if not bool(joined.get(&"ok", false)): queue_result = joined break enqueue_result = CommandResult.succeeded(0, { &"expected_wait": request.expected_wait, }) if bool(queue_result.get(&"ok", false)) else CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, 0, String(queue_result.get(&"message", "Guest could not enter the facility queue.")), queue_result ) if not enqueue_result.is_success(): inventory_service.restore_snapshot(inventory_before) facility_service.restore_snapshot(facility_before) guest_service.restore_snapshot(guest_before) domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(id_before) enqueue_result.command_id = command.command_id return enqueue_result return CommandResult.succeeded(command.command_id, { &"request_id": request_id, &"reservation_ids": reservation_ids, &"expected_wait": enqueue_result.details.get(&"expected_wait", 0), }) func _execute_set_facility_conditions(command: SimulationCommand) -> CommandResult: var facility_id := StringName(command.payload.get(&"facility_id", command.payload.get("facility_id", ""))) var conditions: Variant = command.payload.get(&"conditions", command.payload.get("conditions", null)) if not conditions is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "conditions must be a Dictionary.") var defense_owner: StringName = _defense_lock_owner(facility_id, &"ASSET") if not defense_owner.is_empty() and (conditions.has(&"durability") or conditions.has("durability")): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Defense-owned Facility durability can only change through its Raid.", {&"raid_id": defense_owner} ) var result := facility_service.set_operational_status(facility_id, conditions, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_set_facility_policy(command: SimulationCommand) -> CommandResult: var facility_id := StringName(command.payload.get(&"facility_id", command.payload.get("facility_id", ""))) var patch: Variant = command.payload.get(&"patch", command.payload.get("patch", null)) if not patch is Dictionary: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "patch must be a Dictionary.") var effective_at := int(command.payload.get(&"effective_at", command.payload.get("effective_at", -1))) var result := facility_service.set_policy(facility_id, patch, effective_at) result.command_id = command.command_id return result func _execute_leave_facility_queue(command: SimulationCommand) -> CommandResult: var facility_id := StringName(command.payload.get(&"facility_id", command.payload.get("facility_id", ""))) var request_id := StringName(command.payload.get(&"request_id", command.payload.get("request_id", ""))) var request := facility_service.get_request(request_id) if request == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Queued service request was not found.") var inventory_before := inventory_service.create_snapshot() var facility_before := facility_service.create_snapshot() var guest_before := guest_service.create_snapshot() var events_before := domain_events.create_snapshot() var id_before := id_generator.create_snapshot() var external_leave := func(_guest_id: StringName = &"", _facility_id: StringName = &"", _request_id: StringName = &"") -> Dictionary: for reservation_id: StringName in request.reserved_inventory_ids: var released := inventory_service.release_reservation( reservation_id, StringName("service.release.%s.%s" % [request_id, reservation_id]), _runtime_event_minute() ) if not bool(released.get(&"ok", false)): return released var leave_result := facility_service.leave_queue(facility_id, request_id) return {&"ok": leave_result.is_success(), &"message": leave_result.message} var runtime_guests: Array[StringName] = [] for guest_id: StringName in request.guest_ids: if guest_service.has_guest(guest_id): runtime_guests.append(guest_id) var successful := true var failure_message := "Facility queue removal failed." if runtime_guests.is_empty(): var external_result: Dictionary = external_leave.call() successful = bool(external_result.get(&"ok", false)) failure_message = String(external_result.get(&"message", failure_message)) else: for guest_index in range(runtime_guests.size()): var queued_guest := guest_service.get_guest(runtime_guests[guest_index]) var abandon := guest_service.tick_queue_patience( queued_guest.guest_id, _runtime_event_minute(), -queued_guest.patience_remaining, external_leave if guest_index == 0 else Callable() ) if not bool(abandon.get(&"ok", false)): successful = false failure_message = String(abandon.get(&"message", failure_message)) break if not successful: inventory_service.restore_snapshot(inventory_before) facility_service.restore_snapshot(facility_before) guest_service.restore_snapshot(guest_before) domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(id_before) return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, failure_message) return CommandResult.succeeded(command.command_id, {&"request_id": request_id, &"reason": &"GUEST_LEFT"}) func preview_build_facility(definition_id: StringName, origin: Vector3i, rotation: int) -> Dictionary: var definition := definition_registry.get_facility(definition_id) var failure := _build_facility_precheck(0, {&"definition_id": definition_id, &"origin": origin, &"rotation": rotation}) if failure != null and definition == null: var code := String(failure.details.get(&"reason", CommandResult.Code.keys()[failure.code])) return {&"can_build": false, &"error_codes": [code], &"reasons": [{&"code": code, &"message": failure.message, &"cells": [], &"details": failure.details.duplicate(true)}], &"cells": []} var report: Dictionary = construction_service.validate_placement(definition, origin, rotation).to_dictionary() if failure != null: report[&"can_build"] = false var code := String(failure.details.get(&"reason", CommandResult.Code.keys()[failure.code])) var codes: Array = Array(report.get(&"error_codes", [])) var reasons: Array = Array(report.get(&"reasons", [])) codes.push_front(code) reasons.push_front({&"code": code, &"message": failure.message, &"cells": [], &"details": failure.details.duplicate(true)}) report[&"error_codes"] = codes report[&"reasons"] = reasons return report func _build_facility_precheck(command_id: int, payload: Dictionary) -> CommandResult: var operations_gate := _operations_construction_investment_gate(command_id) if operations_gate != null: return operations_gate var definition_id := StringName(payload.get(&"definition_id", payload.get("definition_id", ""))) var definition := definition_registry.get_facility(definition_id) if definition == null: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command_id, "Facility definition was not found.") if _facility_definition_is_unsupported_vertical_proxy(definition): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command_id, "Vertical infrastructure topology is not supported.", { &"reason": FacilityInfrastructureProjector.REASON_UNSUPPORTED_VERTICAL_TOPOLOGY, &"definition_id": definition_id, &"unchanged": true, } ) var origin_value: Variant = _parse_world_cell(payload.get(&"origin", payload.get("origin", null))) if origin_value == null: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command_id, "origin must be Vector3i or [x, y, layer].") var origin: Vector3i = origin_value if definition.id in [&"guest_stairs", &"service_stairs"] and not path_service.has_cell(origin + Vector3i(0, -1, 0)): return _staff_assignment_failure(command_id, &"STAIR_LANDING_MISSING", "先に階段の一つ下の階に通路を作ってください。") for shaft: Dictionary in elevators.get_report()[&"shafts"]: if _elevator_touches_module(shaft, definition, origin) and _elevator_shaft_busy(shaft): return _staff_assignment_failure(command_id, &"ELEVATOR_BUSY", "Wait until the shaft is idle before extending it.") var rotation := int(payload.get(&"rotation", payload.get("rotation", 0))) if rotation not in definition.allowed_rotations: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command_id, "The selected rotation is not allowed.") return null func _execute_build_facility(command: SimulationCommand) -> CommandResult: var failure := _build_facility_precheck(command.command_id, command.payload) if failure != null: return failure var definition_id := StringName(command.payload.get(&"definition_id", command.payload.get("definition_id", ""))) var definition := definition_registry.get_facility(definition_id) var origin: Vector3i = _parse_world_cell(command.payload.get(&"origin", command.payload.get("origin", null))) var rotation := int(command.payload.get(&"rotation", command.payload.get("rotation", 0))) var result := construction_service.start_construction( definition, origin, rotation, definition.build_minutes, _runtime_event_minute() ) result.command_id = command.command_id return result func _execute_cancel_construction(command: SimulationCommand) -> CommandResult: var job_id := StringName(command.payload.get(&"job_id", command.payload.get("job_id", ""))) if job_id.is_empty(): return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "job_id is required.") var result := construction_service.cancel_job(job_id, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_start_excavation(command: SimulationCommand) -> CommandResult: var operations_gate := _operations_construction_investment_gate(command.command_id) if operations_gate != null: return operations_gate if progression_service.current_rank < 2: return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "Excavation requires World Tree Rank 2.", {&"required_rank": 2, &"current_rank": progression_service.current_rank} ) var parsed_cell: Variant = _parse_world_cell( command.payload.get(&"cell", command.payload.get("cell", null)) ) if parsed_cell == null: return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "cell must be Vector3i or [x, y, layer]." ) var result := excavation_service.start_excavation(parsed_cell, _runtime_event_minute()) result.command_id = command.command_id return result func _execute_pause_excavation(command: SimulationCommand) -> CommandResult: var job_id := StringName(command.payload.get(&"job_id", command.payload.get("job_id", ""))) if job_id.is_empty(): return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "job_id is required." ) var result := excavation_service.pause_job(job_id) result.command_id = command.command_id return result func _execute_resume_excavation(command: SimulationCommand) -> CommandResult: var job_id := StringName(command.payload.get(&"job_id", command.payload.get("job_id", ""))) if job_id.is_empty(): return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "job_id is required." ) var result := excavation_service.resume_job(job_id) result.command_id = command.command_id return result func _execute_demolish_facility(command: SimulationCommand) -> CommandResult: var instance_id := StringName(command.payload.get(&"instance_id", command.payload.get("instance_id", ""))) if instance_id.is_empty(): return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "instance_id is required.") for shaft: Dictionary in elevators.get_report()[&"shafts"]: if instance_id in shaft[&"module_ids"] and _elevator_shaft_busy(shaft): return _staff_assignment_failure(command.command_id, &"ELEVATOR_BUSY", "Wait until passengers have left before demolition.") var lost_landing_cells := _demolition_lost_route_cells(instance_id) for actor: Dictionary in path_service.create_snapshot()[&"actor_cells"]: if actor[&"cell"] in lost_landing_cells: return _staff_assignment_failure(command.command_id, &"FACILITY_OCCUPIED", "この通路・階段に人がいます。移動し終わってから撤去してください。") var stair_dependents := _stair_demolition_dependents(instance_id) if not stair_dependents.is_empty(): return _staff_assignment_failure(command.command_id, &"STAIR_LANDING_IN_USE", "この通路につながる上の階段を先に撤去するか、建設を中止してください。") var defense_owner: StringName = _defense_lock_owner(instance_id, &"ASSET") if not defense_owner.is_empty(): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "A Facility assigned to a non-terminal Defense Raid cannot be demolished.", {&"raid_id": defense_owner} ) var supply_guard: Dictionary = supply_management.validate_demolition(self, instance_id) if not bool(supply_guard.get(&"ok", false)): return _staff_assignment_failure(command.command_id, StringName(supply_guard.get(&"reason", &"SUPPLY_IN_USE")), "Facility still owns supply stock, a production plan, or an active delivery.") for assignment: Dictionary in staff_service.get_work_assignments(): if StringName(assignment[&"facility_id"]) == instance_id: return _staff_assignment_failure(command.command_id, &"STAFF_ASSIGNED", "Unassign scheduled Staff before demolishing this facility.") var operational_instance := facility_service.get_instance(instance_id) if operational_instance != null \ and (not operational_instance.queue_entries.is_empty() or not operational_instance.active_services.is_empty()): return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, "A busy facility cannot be demolished.") if facility_infrastructure_projector.has_projection(instance_id): var boundary_before := _capture_infrastructure_demolition_state() if boundary_before.is_empty(): return CommandResult.failed( CommandResult.Code.TRANSACTION_CLOSED, command.command_id, "Infrastructure demolition state could not be captured." ) var unprojected := facility_infrastructure_projector.unproject_facility( StringName("runtime.demolish:%s" % instance_id), instance_id ) if not unprojected.is_success(): if not _infrastructure_demolition_state_matches(boundary_before): _restore_infrastructure_demolition_state(boundary_before) var rejection_details := unprojected.details.duplicate(true) rejection_details[&"reason"] = &"INFRASTRUCTURE_UNPROJECT_FAILED" rejection_details[&"instance_id"] = instance_id return CommandResult.failed( unprojected.code, command.command_id, unprojected.message, rejection_details ) var demolished := construction_service.demolish_facility(instance_id) demolished.command_id = command.command_id var transport_ok := false if demolished.is_success(): transport_ok = bool(_synchronize_elevators(_runtime_event_minute()).get(&"ok", false)) \ and bool(_synchronize_warps().get(&"ok", false)) and _prune_legacy_stairs_and_movement() if not transport_ok or not demolished.is_success() \ or not _validate_runtime_path_crosslinks(path_service, guest_service, staff_service, _movement_credits, _runtime_event_minute()) \ or facility_infrastructure_projector.has_projection(instance_id) \ or not construction_service.get_facility(instance_id).is_empty() \ or not world_service.get_entity_cells(instance_id).is_empty() \ or not _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ): var rolled_back := _restore_infrastructure_demolition_state(boundary_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Infrastructure demolition failed and was rolled back.", { &"instance_id": instance_id, &"rolled_back": rolled_back, &"cause": demolished.to_dictionary(), } ) return demolished var result := construction_service.demolish_facility(instance_id) result.command_id = command.command_id if result.is_success(): kitchen_production.remove_plan(instance_id) auto_purchase_plans.remove_warehouse(instance_id) return result func _capture_infrastructure_demolition_state() -> Dictionary: return { &"game_state": _state.create_snapshot(), &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"world": world_service.create_snapshot(), &"path": path_service.create_snapshot(), &"elevators": elevators.create_snapshot(), &"legacy_stairs": legacy_stairs.create_snapshot(), &"construction": construction_service.create_snapshot(), &"excavation": excavation_service.create_snapshot(), &"spatial_subslots": spatial_subslot_service.create_snapshot(), &"route_projection": route_subslot_path_adapter.create_snapshot(), &"facility_infrastructure": facility_infrastructure_projector.create_snapshot(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"mana": mana_network_service.create_snapshot(), &"movement_credits": _movement_credits.duplicate(true), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), } func _restore_infrastructure_demolition_state(snapshot: Dictionary) -> bool: for key: StringName in [ &"game_state", &"economy", &"facility_upkeep", &"upkeep_settlement", &"world", &"path", &"elevators", &"legacy_stairs", &"construction", &"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure", &"facilities", &"facility_cleanliness", &"mana", &"movement_credits", &"domain_events", &"id_generator", ]: if not snapshot.get(key, null) is Dictionary: return false var restored: bool = bool(_state.restore_snapshot(snapshot[&"game_state"])) restored = economy_service.restore_snapshot(snapshot[&"economy"]) and restored restored = facility_upkeep.restore_snapshot(snapshot[&"facility_upkeep"]) and restored restored = upkeep_settlement.restore_snapshot(snapshot[&"upkeep_settlement"], economy_service) and restored restored = world_service.restore_snapshot(snapshot[&"world"]) and restored restored = spatial_subslot_service.restore_snapshot(snapshot[&"spatial_subslots"]) and restored var route_snapshot: Dictionary = snapshot[&"route_projection"] var projected_walkable: Variant = route_snapshot.get(&"projected_walkable_cells", null) if not projected_walkable is Array: return false restored = path_service.rebuild_graph(projected_walkable) and restored restored = path_service.restore_snapshot(snapshot[&"path"]) and restored restored = elevators.restore_snapshot(snapshot[&"elevators"]) and restored restored = legacy_stairs.restore_snapshot(snapshot[&"legacy_stairs"]) and restored restored = route_subslot_path_adapter.restore_snapshot(route_snapshot) and restored restored = facility_infrastructure_projector.restore_snapshot( snapshot[&"facility_infrastructure"] ) and restored restored = construction_service.restore_snapshot(snapshot[&"construction"]) and restored restored = excavation_service.restore_snapshot(snapshot[&"excavation"]) and restored restored = facility_service.restore_snapshot(snapshot[&"facilities"]) and restored restored = facility_cleanliness.restore_snapshot(snapshot.get(&"facility_cleanliness", {})) and restored restored = mana_network_service.restore_snapshot(snapshot[&"mana"]) and restored _movement_credits = (snapshot[&"movement_credits"] as Dictionary).duplicate(true) restored = domain_events.restore_snapshot(snapshot[&"domain_events"]) and restored restored = id_generator.restore_snapshot(snapshot[&"id_generator"]) and restored return restored and _infrastructure_demolition_state_matches(snapshot) \ and _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ) func _infrastructure_demolition_state_matches(snapshot: Dictionary) -> bool: return _strict_variants_equal(_state.create_snapshot(), snapshot.get(&"game_state", {})) \ and _strict_variants_equal(economy_service.create_snapshot(), snapshot.get(&"economy", {})) \ and _strict_variants_equal(facility_upkeep.create_snapshot(), snapshot.get(&"facility_upkeep", {})) \ and _strict_variants_equal(upkeep_settlement.create_snapshot(), snapshot.get(&"upkeep_settlement", {})) \ and _strict_variants_equal(world_service.create_snapshot(), snapshot.get(&"world", {})) \ and _strict_variants_equal(path_service.create_snapshot(), snapshot.get(&"path", {})) \ and _strict_variants_equal(elevators.create_snapshot(), snapshot.get(&"elevators", {})) \ and _strict_variants_equal(legacy_stairs.create_snapshot(), snapshot.get(&"legacy_stairs", {})) \ and _strict_variants_equal(construction_service.create_snapshot(), snapshot.get(&"construction", {})) \ and _strict_variants_equal(excavation_service.create_snapshot(), snapshot.get(&"excavation", {})) \ and _strict_variants_equal(spatial_subslot_service.create_snapshot(), snapshot.get(&"spatial_subslots", {})) \ and _strict_variants_equal(route_subslot_path_adapter.create_snapshot(), snapshot.get(&"route_projection", {})) \ and _strict_variants_equal(facility_infrastructure_projector.create_snapshot(), snapshot.get(&"facility_infrastructure", {})) \ and _strict_variants_equal(facility_service.create_snapshot(), snapshot.get(&"facilities", {})) \ and _strict_variants_equal(facility_cleanliness.create_snapshot(), snapshot.get(&"facility_cleanliness", {})) \ and _strict_variants_equal(mana_network_service.create_snapshot(), snapshot.get(&"mana", {})) \ and _strict_variants_equal(_movement_credits, snapshot.get(&"movement_credits", {})) \ and _strict_variants_equal(domain_events.create_snapshot(), snapshot.get(&"domain_events", {})) \ and _strict_variants_equal(id_generator.create_snapshot(), snapshot.get(&"id_generator", {})) func _execute_set_world_tree_rank(command: SimulationCommand) -> CommandResult: var operations_gate := _operations_construction_investment_gate(command.command_id) if operations_gate != null: return operations_gate var rank := int(command.payload.get(&"rank", command.payload.get("rank", 0))) var request_id := StringName(command.payload.get( &"request_id", command.payload.get("request_id", "rank_up_%d" % rank) )) var progression_before := progression_service.create_snapshot() if not _sync_progression_live_metrics(): progression_service.restore_snapshot(progression_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "Live progression facts could not be synchronized." ) var external_before := _capture_rank_external_state() var result := progression_service.request_rank_up( request_id, rank, func(_current_rank: int, _target_rank: int) -> Dictionary: return {&"ok": true, &"snapshot": external_before.duplicate(true)}, func(target_rank: int, expected_bounds: Dictionary) -> Dictionary: return {&"ok": _apply_rank_external_state(target_rank, expected_bounds)}, func(snapshot: Dictionary) -> Dictionary: return {&"ok": _rollback_rank_external_state(snapshot)}, ) result.command_id = command.command_id if not result.is_success(): progression_service.restore_snapshot(progression_before) return result if result.is_replay: # Progression replays are read-only. The live-fact synchronization above is # deliberately rolled back so a resent command reproduces the first result # without silently changing any metric. progression_service.restore_snapshot(progression_before) return result var event := SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"WORLD_TREE_RANK_ADVANCED", _runtime_event_minute(), &"progression_service", [StringName("world_tree_rank_%d" % rank)], result.details.duplicate(true), command.command_id ) if not _publish_domain_event(event): _rollback_rank_external_state(external_before) progression_service.restore_snapshot(progression_before) return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id, "World Tree promotion event could not be published; the promotion was rolled back." ) return result func _execute_update_progression_metrics(command: SimulationCommand) -> CommandResult: var patch: Variant = command.payload.get(&"metrics", command.payload.get("metrics", null)) if not patch is Dictionary: return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "Progression metrics must be a Dictionary." ) if patch.has(&"guest_lifetime") or patch.has("guest_lifetime"): var requested_value: Variant = patch.get( &"guest_lifetime", patch.get("guest_lifetime", null) ) var guest_snapshot := guest_service.create_snapshot() var records_value: Variant = guest_snapshot.get(&"persistent_records", null) var derived: int = guest_service.guest_count() \ + (records_value.size() if records_value is Array else -1) if typeof(requested_value) != TYPE_INT or derived < 0 \ or int(requested_value) != derived: return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, "guest_lifetime is an authoritative Guest aggregate fact." ) if not progression_service.update_metrics(patch): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, progression_service.last_error ) return CommandResult.succeeded(command.command_id, { &"metrics": progression_service.metrics.duplicate(true), }) func _execute_complete_progression_quest(command: SimulationCommand) -> CommandResult: var raw_quest_id: Variant = command.payload.get(&"quest_id", command.payload.get("quest_id", null)) if typeof(raw_quest_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return CommandResult.failed( CommandResult.Code.INVALID_PAYLOAD, command.command_id, "A progression quest id is required." ) var quest_id := StringName(raw_quest_id) if not progression_service.complete_quest(quest_id): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, progression_service.last_error ) return CommandResult.succeeded(command.command_id, {&"quest_id": quest_id}) func _execute_acknowledge_operations_pause(command: SimulationCommand) -> CommandResult: var acknowledged := acknowledge_operations_pause() if not bool(acknowledged.get(&"ok", false)): return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command.command_id, String(acknowledged.get(&"message", "Operations acknowledgement failed.")), acknowledged.get(&"details", {}) ) return CommandResult.succeeded(command.command_id, acknowledged) func _operations_construction_investment_gate(command_id: int) -> CommandResult: if not _operations_enabled or operations_coordinator == null: return null var gate := operations_coordinator.can_start_construction_or_investment() if bool(gate.get(&"allowed", false)) and _operations_total_arrears() > 0: gate = {&"allowed": false, &"reason": &"UNPAID_EXPENSES", &"arrears_total": _operations_total_arrears()} if bool(gate.get(&"allowed", false)): return null return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command_id, "Operations currently block construction and investment.", gate ) func _advance_construction(absolute_minute: int) -> void: if _operations_tick_blocked: return var preflight := _preflight_proxy_completions(absolute_minute) if not bool(preflight.get(&"ok", false)): _latch_infrastructure_failure( StringName(preflight.get(&"code", &"INFRASTRUCTURE_PREFLIGHT_FAILED")), absolute_minute, String(preflight.get(&"reason", "Infrastructure completion preflight failed.")), preflight.get(&"details", {}) ) return # The expensive composed rollback token is only needed when Construction can # cross the completion boundary this minute. Idle ticks and ordinary progress # retain ConstructionService's direct job_changed notification semantics. if not _construction_completion_is_due_this_tick(): var fast_results: Array[CommandResult] = construction_service.advance_one_minute( absolute_minute ) for fast_result: CommandResult in fast_results: if fast_result == null or not fast_result.is_success(): _latch_infrastructure_failure( &"CONSTRUCTION_ADVANCE_FAILED", absolute_minute, ( fast_result.message if fast_result != null else "Construction returned an invalid result." ), ( fast_result.to_dictionary() if fast_result != null else {} ) ) return excavation_service.advance_one_minute(absolute_minute) if _infrastructure_failure_latched: return if not _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ): _latch_infrastructure_failure( &"INFRASTRUCTURE_POSTCONDITION_FAILED", absolute_minute, "Construction and Excavation ended the minute with invalid infrastructure cross-links." ) return var boundary_before := _capture_infrastructure_demolition_state() if boundary_before.is_empty(): _latch_infrastructure_failure( &"INFRASTRUCTURE_CAPTURE_FAILED", absolute_minute, "The Construction completion boundary could not be captured." ) return var previous_facility_ids: Dictionary = {} for instance: Dictionary in construction_service.get_facilities(): previous_facility_ids[StringName(instance.get(&"instance_id", &""))] = true _construction_completion_transaction_active = true _construction_queued_domain_events.clear() var progress_results: Array[CommandResult] = construction_service.advance_one_minute( absolute_minute, {}, true ) var apply_failure: Dictionary = {} for progress_result: CommandResult in progress_results: if progress_result == null or not progress_result.is_success(): apply_failure = { &"code": &"CONSTRUCTION_ADVANCE_FAILED", &"reason": ( progress_result.message if progress_result != null else "Construction returned an invalid result." ), &"details": ( progress_result.to_dictionary() if progress_result != null else {} ), } break if apply_failure.is_empty(): for instance: Dictionary in construction_service.get_facilities(): var instance_id := StringName(instance.get(&"instance_id", &"")) if previous_facility_ids.has(instance_id): continue var applied := _apply_completed_facility_internal(instance, absolute_minute) if not bool(applied.get(&"ok", false)): apply_failure = applied break if apply_failure.is_empty() and not _construction_completion_crosslinks_are_valid( absolute_minute ): apply_failure = { &"code": &"CONSTRUCTION_COMPLETION_CROSSLINK_FAILED", &"reason": "Construction completion produced invalid Runtime cross-links.", &"details": {}, } if not apply_failure.is_empty(): _fail_construction_completion_boundary( boundary_before, StringName(apply_failure.get(&"code", &"CONSTRUCTION_COMPLETION_FAILED")), absolute_minute, String(apply_failure.get(&"reason", "Construction completion failed.")), apply_failure.get(&"details", {}) ) return # DomainEventStream validates and commits the whole batch before notifying # subscribers. Construction's own raw signals are released only after the # authoritative Runtime state and event batch have both committed. var queued_events: Array[SimulationDomainEvent] = [] for event: SimulationDomainEvent in _construction_queued_domain_events: queued_events.append(event.copy()) _construction_completion_transaction_active = false _construction_queued_domain_events.clear() if not domain_events.publish_batch(queued_events): _fail_construction_completion_boundary( boundary_before, &"CONSTRUCTION_EVENT_COMMIT_FAILED", absolute_minute, "Construction completion DomainEvents could not be committed." ) return _construction_committing_notifications = true var notifications_committed := construction_service.commit_deferred_notifications() _construction_committing_notifications = false if not notifications_committed: # Service-created deferred rows are prevalidated by ConstructionService, # so this branch represents an unrecoverable notification boundary fault. _latch_infrastructure_failure( &"CONSTRUCTION_NOTIFICATION_COMMIT_FAILED", absolute_minute, "Committed Construction state could not release its deferred notifications.", {&"rolled_back": false} ) return # Construction is authoritative first. Excavation then advances to the same # absolute minute, preventing an excavation completion signal from escaping # when the Construction boundary must be rolled back. excavation_service.advance_one_minute(absolute_minute) if _infrastructure_failure_latched: return if not _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector ): _latch_infrastructure_failure( &"INFRASTRUCTURE_POSTCONDITION_FAILED", absolute_minute, "Construction and Excavation ended the minute with invalid infrastructure cross-links." ) func _construction_completion_is_due_this_tick() -> bool: for job: ConstructionJob in construction_service.get_jobs(): if job == null or job.is_terminal(): continue if job.work_units_done + ConstructionJob.NORMAL_WORK_RATE \ >= job.build_minutes * ConstructionJob.MAX_PROGRESS: return true return false func _apply_completed_facility_internal(instance: Dictionary, absolute_minute: int) -> Dictionary: var instance_id := StringName(instance.get(&"instance_id", &"")) var definition_id := StringName(instance.get(&"definition_id", &"")) var definition := definition_registry.get_facility(definition_id) if instance_id.is_empty() or definition == null: return { &"ok": false, &"code": &"INVALID_COMPLETED_FACILITY", &"reason": "Construction produced an invalid completed Facility.", &"details": {&"instance_id": instance_id, &"definition_id": definition_id}, } if _facility_definition_is_schema_proxy(definition): if not _facility_definition_is_supported_infrastructure_proxy(definition): return { &"ok": false, &"code": &"UNSUPPORTED_VERTICAL_TOPOLOGY", &"reason": "An unsupported vertical proxy reached Construction completion.", &"details": {&"instance_id": instance_id, &"definition_id": definition.id}, } if not facility_infrastructure_projector.has_projection(instance_id): var projection_request_id := StringName( "runtime.complete:%s" % String(instance.get(&"source_job_id", instance_id)) ) var projected := facility_infrastructure_projector.project_completed_facility( projection_request_id, definition, instance ) if not projected.is_success(): return { &"ok": false, &"code": &"FACILITY_INFRASTRUCTURE_PROJECTION_FAILED", &"reason": projected.message, &"details": projected.details.duplicate(true), } if not _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"FACILITY_INFRASTRUCTURE_PROJECTED", absolute_minute, &"facility_infrastructure_projector", [instance_id], projected.details.duplicate(true), 0 )): return { &"ok": false, &"code": &"FACILITY_INFRASTRUCTURE_EVENT_FAILED", &"reason": "The infrastructure projection event could not be staged.", &"details": {&"instance_id": instance_id}, } else: if not facility_service.has_instance(instance_id): _register_facility_mana_consumer(instance) _register_operational_facility(instance) if not facility_service.has_instance(instance_id) \ or not economy_service.has_account(_facility_revenue_account(instance_id)): return { &"ok": false, &"code": &"OPERATIONAL_FACILITY_REGISTRATION_FAILED", &"reason": "The completed Facility could not be registered operationally.", &"details": {&"instance_id": instance_id}, } if definition.maintenance_mana > 0 \ and mana_network_service.get_node(instance_id).is_empty(): return { &"ok": false, &"code": &"FACILITY_MANA_REGISTRATION_FAILED", &"reason": "The completed Facility Mana consumer could not be registered.", &"details": {&"instance_id": instance_id}, } if definition.id in [&"guest_elevator", &"service_elevator"]: _register_facility_mana_consumer(instance) var transport := _synchronize_elevators(absolute_minute) if not bool(transport.get(&"ok", false)): return {&"ok": false, &"code": &"ELEVATOR_SYNC_FAILED", &"reason": elevators.last_error, &"details": transport} if definition.id in WarpService.DEFINITION_IDS: _register_facility_mana_consumer(instance) var warp_sync := _synchronize_warps() if not bool(warp_sync.get(&"ok", false)): return {&"ok": false, &"code": &"WARP_SYNC_FAILED", &"reason": String(warp_sync.get(&"reason", "")), &"details": warp_sync} if not _prune_legacy_stairs_and_movement(): return {&"ok": false, &"code": &"LEGACY_STAIR_PRUNE_FAILED", &"reason": "Stair topology could not be updated."} if not _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"FACILITY_COMPLETED", absolute_minute, &"construction_service", [instance_id], instance.duplicate(true), 0 )): return { &"ok": false, &"code": &"FACILITY_COMPLETION_EVENT_FAILED", &"reason": "The Facility completion event could not be staged.", &"details": {&"instance_id": instance_id}, } return {&"ok": true} func _construction_completion_crosslinks_are_valid(absolute_minute: int) -> bool: return construction_service.current_minute == absolute_minute \ and bool(mana_network_service.validate_invariants().get(&"valid", false)) \ and _validate_construction_facility_crosslinks( world_service, construction_service, facility_service, facility_infrastructure_projector ) \ and _validate_infrastructure_crosslinks( world_service, path_service, construction_service, construction_resource_state, excavation_service, spatial_subslot_service, route_subslot_path_adapter, facility_infrastructure_projector, false ) func _fail_construction_completion_boundary( boundary_before: Dictionary, code: StringName, absolute_minute: int, reason: String, details: Dictionary = {} ) -> void: _construction_completion_transaction_active = false _construction_committing_notifications = false _construction_queued_domain_events.clear() var notifications_discarded := construction_service.discard_deferred_notifications() var rolled_back := notifications_discarded \ and _restore_infrastructure_demolition_state(boundary_before) var failure_details := details.duplicate(true) failure_details[&"notifications_discarded"] = notifications_discarded failure_details[&"rolled_back"] = rolled_back _latch_infrastructure_failure( code if rolled_back else &"INFRASTRUCTURE_COMPLETION_ROLLBACK_FAILED", absolute_minute, reason, failure_details ) func _preflight_proxy_completions(absolute_minute: int) -> Dictionary: if construction_service == null or facility_infrastructure_projector == null \ or route_subslot_path_adapter == null or spatial_subslot_service == null: return { &"ok": false, &"code": &"INFRASTRUCTURE_UNAVAILABLE", &"reason": "Infrastructure services are unavailable.", } var due_proxy_ids: Array[StringName] = [] for job: ConstructionJob in construction_service.get_jobs(): if job == null or job.is_terminal(): continue var definition := definition_registry.get_facility(job.definition_id) if definition == null: return { &"ok": false, &"code": &"MISSING_FACILITY_DEFINITION", &"reason": "A Construction job references a missing Facility definition.", &"details": {&"job_id": job.job_id, &"definition_id": job.definition_id}, } if not _facility_definition_is_supported_infrastructure_proxy(definition): continue if job.work_units_done + ConstructionJob.NORMAL_WORK_RATE \ >= job.build_minutes * ConstructionJob.MAX_PROGRESS: due_proxy_ids.append(job.job_id) if due_proxy_ids.is_empty(): return {&"ok": true} if route_subslot_path_adapter.is_quarantined() \ or route_subslot_path_adapter.is_projection_stale() \ or facility_infrastructure_projector.is_quarantined() \ or facility_infrastructure_projector.is_projection_stale() \ or not bool(spatial_subslot_service.validate_invariants().get(&"ok", false)) \ or not bool(route_subslot_path_adapter.validate_invariants().get(&"ok", false)) \ or not bool(facility_infrastructure_projector.validate_invariants().get(&"ok", false)): return { &"ok": false, &"code": &"INFRASTRUCTURE_DEPENDENCY_STALE", &"reason": "Infrastructure dependencies are stale or quarantined.", &"details": {&"job_ids": due_proxy_ids.duplicate()}, } var projector_snapshot := facility_infrastructure_projector.create_snapshot() if int((projector_snapshot.get(&"projections", []) as Array).size()) \ > FacilityInfrastructureProjector.MAX_PROJECTIONS - due_proxy_ids.size() \ or int((projector_snapshot.get(&"processed_requests", []) as Array).size()) \ > FacilityInfrastructureProjector.MAX_PROCESSED_REQUESTS - due_proxy_ids.size(): return { &"ok": false, &"code": &"INFRASTRUCTURE_CAPACITY_EXHAUSTED", &"reason": "Infrastructure projection capacity is exhausted.", &"details": {&"job_ids": due_proxy_ids.duplicate()}, } return {&"ok": true} func _latch_infrastructure_failure( code: StringName, absolute_minute: int, reason: String, details: Dictionary = {} ) -> void: _infrastructure_failure_latched = true _infrastructure_failure_report = { &"code": code, &"minute": absolute_minute, &"reason": reason, &"details": details.duplicate(true), } _operations_tick_blocked = true if _clock != null: _clock.set_paused(true) func get_infrastructure_report() -> Dictionary: return { &"failure_latched": _infrastructure_failure_latched, &"failure_report": _infrastructure_failure_report.duplicate(true), &"excavation": ( excavation_service.validate_invariants().duplicate(true) if excavation_service != null else {&"ok": false} ), &"spatial_subslots": ( spatial_subslot_service.validate_invariants().duplicate(true) if spatial_subslot_service != null else {&"ok": false} ), &"route_projection": ( route_subslot_path_adapter.get_projection_status().duplicate(true) if route_subslot_path_adapter != null else {&"configured": false} ), &"facility_infrastructure": ( facility_infrastructure_projector.get_status().duplicate(true) if facility_infrastructure_projector != null else {&"configured": false} ), }.duplicate(true) func _advance_inventory_and_logistics(absolute_minute: int) -> void: if _operations_tick_blocked: return inventory_service.current_minute = absolute_minute inventory_service.process_expired(absolute_minute, &"inventory.waste") if int(_state.get_resource(&"mana")) > 0: supply_management.advance_auto_purchases(self, absolute_minute) supply_management.ensure_deliveries(self, absolute_minute) supply_logistics.tick(absolute_minute) func _advance_facilities(absolute_minute: int) -> void: if _operations_tick_blocked: return facility_service.advance_one_minute(absolute_minute) kitchen_production.advance_one_minute(absolute_minute) func _advance_guest_vitals(absolute_minute: int) -> void: if _operations_tick_blocked: return var minute_of_day := posmod(absolute_minute, 1_440) guest_service.advance_one_minute(absolute_minute, { &"default": { &"unstimulated": true, &"solo_guest": true, &"is_night": minute_of_day < 360 or minute_of_day >= 1_080, &"intimacy_day": 3, &"intimacy_night": 7, } }) func _advance_guest_queues(absolute_minute: int) -> void: if _operations_tick_blocked: return _release_closed_business_queues(absolute_minute) var guests_by_request: Dictionary = {} for guest: GuestRuntime in guest_service.get_guests(): if guest.state != GuestRuntime.State.QUEUING: continue if not guests_by_request.has(guest.queue_request_id): guests_by_request[guest.queue_request_id] = [] (guests_by_request[guest.queue_request_id] as Array).append(guest) for request_id: StringName in _sorted_name_keys(guests_by_request): var grouped_guests: Array = guests_by_request[request_id] var queued_facility := facility_service.get_instance(grouped_guests[0].target_facility_id) if queued_facility != null and (not queued_facility.operator_open or not queued_facility.is_within_opening_hours(absolute_minute)): continue # A deferred closure must not spend patience while retrying. var abandon_group := false for guest: GuestRuntime in grouped_guests: if guest.patience_remaining <= 1: abandon_group = true break if not abandon_group: for guest: GuestRuntime in grouped_guests: guest_service.tick_queue_patience(guest.guest_id, absolute_minute) continue var inventory_before := inventory_service.create_snapshot() var facility_before := facility_service.create_snapshot() var guest_before := guest_service.create_snapshot() var events_before := domain_events.create_snapshot() var id_before := id_generator.create_snapshot() var group_succeeded := true for guest_index in range(grouped_guests.size()): var guest: GuestRuntime = grouped_guests[guest_index] var abandoned := guest_service.tick_queue_patience( guest.guest_id, absolute_minute, -guest.patience_remaining, Callable(self, "_leave_guest_queue_boundary") if guest_index == 0 else Callable() ) if not bool(abandoned.get(&"ok", false)): group_succeeded = false break if not group_succeeded: inventory_service.restore_snapshot(inventory_before) facility_service.restore_snapshot(facility_before) guest_service.restore_snapshot(guest_before) domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(id_before) func _advance_authority_incidents(absolute_minute: int) -> void: if _operations_tick_blocked: return if _operations_morning_already_processed(absolute_minute): return var advanced := authority_service.advance_time(absolute_minute) if not advanced.is_success(): _clock.set_paused(true) _publish_actor_event(&"AUTHORITY_ADVANCE_FAILED", &"authority_service", [], { &"message": advanced.message, &"details": advanced.details.duplicate(true), }) return if posmod(absolute_minute, 1_440) != AuthorityService.INSPECTION_EVALUATION_MINUTE: return var evaluated := authority_service.evaluate_inspection(absolute_minute) if not evaluated.is_success(): _clock.set_paused(true) _publish_actor_event(&"AUTHORITY_INSPECTION_EVALUATION_FAILED", &"authority_service", [], { &"message": evaluated.message, &"details": evaluated.details.duplicate(true), }) return if not evaluated.is_replay: _publish_authority_evaluation(evaluated.details) func _advance_defense(absolute_minute: int) -> void: if _operations_tick_blocked: return _prepare_defense_round_attempts(absolute_minute) # A deferred operations suffix must not reattempt the same failed automatic # batch. Resume on a later game minute, after the player resumes the clock. if _defense_automatic_failed_minute == absolute_minute: return var advanced := defense_service.advance_time(absolute_minute) if not advanced.is_success(): _defense_automatic_failed_minute = absolute_minute _clock.set_paused(true) _publish_actor_event(&"DEFENSE_ADVANCE_FAILED", &"defense_service", [], { &"message": advanced.message, &"details": advanced.details.duplicate(true), }) return # One attempt per due raid, in stable raid-ID order. No catch-up while-loop, # invented deadline, offline replay, PREPARING start or BETWEEN_WAVES advance. for due: Dictionary in defense_service.get_due_rounds(absolute_minute): if _defense_automatic_failed_minute == absolute_minute: return var raid_id := StringName(due[&"raid_id"]) if _defense_round_attempted_raids.has(raid_id): continue var resolved := _resolve_defense_round_internal(raid_id, &"", absolute_minute) if not resolved.is_success(): _defense_automatic_failed_minute = absolute_minute _clock.set_paused(true) _publish_actor_event(&"DEFENSE_AUTOMATIC_ROUND_FAILED", &"defense_service", [raid_id], { &"round_number": due[&"round_number"], &"earliest_minute": due[&"earliest_minute"], &"message": resolved.message, &"details": resolved.details.duplicate(true), }) return func _advance_relationship_events(absolute_minute: int) -> void: if _operations_tick_blocked: return if _operations_morning_already_processed(absolute_minute): return event_service.process_expired(absolute_minute) event_service.audit_actor_availability(absolute_minute) _enqueue_pending_relationship_candidates() if event_service.get_foreground_instance() == null: var started := event_service.start_next_event( absolute_minute, _build_event_context(absolute_minute) ) if started.is_success() and bool(started.details.get(&"waiting_for_choice", false)): _clock.set_paused(true) if event_service.pause_requested: _clock.set_paused(true) func _operations_morning_already_processed(absolute_minute: int) -> bool: if not _operations_enabled or operations_coordinator == null \ or posmod(absolute_minute, SimulationKernel.MINUTES_PER_DAY) \ != OPERATIONS_START_MINUTE: return false var day := absolute_minute / SimulationKernel.MINUTES_PER_DAY + 1 var report := operations_coordinator.get_morning_report(day) return not report.is_empty() and int(report.get(&"minute", -1)) == absolute_minute func _enqueue_pending_relationship_candidates() -> void: for candidate: Dictionary in relationship_service.get_threshold_candidates(): _on_relationship_threshold_candidate(candidate) func _build_event_context(absolute_minute: int) -> Dictionary: return { &"absolute_minute": absolute_minute, &"day": absolute_minute / 1_440 + 1, &"minute_of_day": posmod(absolute_minute, 1_440), &"player": { &"gold": economy_service.get_balance(PLAYER_CASH_ACCOUNT), &"world_tree_rank": int(_state.world_tree_rank), }, } func _advance_staff_ai(absolute_minute: int) -> void: if _operations_tick_blocked: return _prune_expired_staff_elevator_waits(absolute_minute) _advance_elevators(absolute_minute) _advance_staff_rescue(absolute_minute) staff_service.advance_one_minute(absolute_minute) _prune_expired_staff_elevator_waits(absolute_minute) staff_commute.tick(absolute_minute) _sync_facility_staffing(absolute_minute) cleaning_bridge.tick(absolute_minute) for task: StaffTask in staff_service.get_tasks(): if task.state == StaffTask.State.RESERVED: if task.state_changed_at < absolute_minute: task.begin_moving(task.state_changed_at) elif task.state == StaffTask.State.WORKING and task.task_type == CleaningBridge.TASK_TYPE: # Only cleaning is deferred from StaffService to these attendance and # dependency checks. Other tasks already progressed once this minute. if task.state_changed_at >= absolute_minute or not cleaning_bridge.task_can_work(task, staff_service.get_staff(task.assigned_staff_id)): continue var work_units := CleaningBridge.work_units_per_minute(staff_service.get_staff(task.assigned_staff_id)) staff_service.progress_task(task.task_id, work_units, absolute_minute) for staff: StaffRuntime in staff_service.get_staff_members(): if absolute_minute % 30 != 0: continue if staff.state == StaffRuntime.State.WORKING or (staff.state == StaffRuntime.State.AVAILABLE and staff_commute.is_present(staff, facility_service.get_instance(staff.assigned_facility_id))): staff.add_fatigue(1) if _operations_enabled: staff.health = maxi(staff.health - 1, 0) elif staff.state == StaffRuntime.State.OFF_DUTY: var resting := get_staff_rest_report(staff.staff_id) if not _operations_enabled or resting.get(&"resting", false): staff.add_fatigue(-1) if _operations_enabled and staff.health > 0: staff.health = mini(staff.health + 1, 100) _advance_staff_rescue(absolute_minute) _sync_facility_staffing(absolute_minute) _advance_guest_ai_decisions(absolute_minute) ## Record the actual completed choice, not a fresh prediction made by the UI. func _decide_guest_with_log(guest_id: StringName, candidates: Array, minute: int, context: Dictionary) -> Dictionary: var observation := guest_service.get_observation(guest_id) var result := guest_service.decide(guest_id, candidates, minute, context) if not bool(result.get(&"ok", false)): return result var candidate: Dictionary = result.get(&"candidate", {}) var rejections := guest_service.get_last_candidate_rejections(guest_id) var reason_counts: Dictionary = {} for rejection: Dictionary in rejections: var reason := String(rejection.get(&"reason", "")) reason_counts[reason] = int(reason_counts.get(reason, 0)) + 1 var payload := { &"rejection_counts": reason_counts, &"rejection_examples": rejections.slice(0, 8), &"rejection_count": rejections.size(), &"action": result[&"action"], &"needs": observation.get(&"needs", {}).duplicate(true), &"primary_needs": observation.get(&"primary_needs", []).duplicate(), &"facility_id": candidate.get(&"facility_id", &""), &"need_priority": candidate.get(&"need_priority", 0), &"utility": candidate.get(&"utility", 0), &"travel_minutes": candidate.get(&"travel_minutes", 0), &"wait_minutes": candidate.get(&"utility_terms", {}).get(&"wait_minutes", 0), &"estimated_total_gold": maxi(int(candidate.get(&"estimated_total_gold", candidate.get(&"price_gold", candidate.get(&"quoted_price", 0)))), 0), } _publish_actor_event(&"GUEST_DECISION_RECORDED", &"guest_service", [guest_id], payload) return result func get_guest_decision_record(guest_id: StringName) -> Dictionary: var events := domain_events.recent_events() for index in range(events.size() - 1, -1, -1): var event: SimulationDomainEvent = events[index] if event.event_type == &"GUEST_DECISION_RECORDED" and guest_id in event.entity_ids: var report := event.payload.duplicate(true) report[&"absolute_minute"] = event.absolute_minute return report return {} func _advance_guest_ai_decisions(absolute_minute: int) -> void: for guest: GuestRuntime in guest_service.get_guests(): # A rider inside a cabin cannot decide, leave or despawn until the # elevator sets it down at a real landing (P14-03: passengers never vanish). if elevators.get_actor_trip(_guest_path_actor_id(guest.guest_id)).get(&"state", &"") == PathService.ELEVATOR_REQUEST_BOARDED: continue match guest.state: GuestRuntime.State.SPAWNING: guest_service.transition_guest(guest.guest_id, GuestRuntime.State.ENTERING, absolute_minute, &"ENTRY_STARTED") GuestRuntime.State.ENTERING: guest_service.transition_guest(guest.guest_id, GuestRuntime.State.DECIDING, absolute_minute, &"ENTRY_COMPLETED") GuestRuntime.State.DECIDING: if guest.planned_exit_at <= absolute_minute: guest_service.begin_leaving(guest.guest_id, absolute_minute, &"PLANNED_EXIT") continue guest.wallet_gold = economy_service.get_balance(_guest_wallet_account(guest.guest_id)) _decide_guest_with_log( guest.guest_id, _project_guest_candidates(guest.guest_id), absolute_minute, {&"absolute_minute": absolute_minute} ) GuestRuntime.State.MOVING: pass GuestRuntime.State.REPLANNING, GuestRuntime.State.ABANDONING, GuestRuntime.State.INTERRUPTED: guest_service.transition_guest(guest.guest_id, GuestRuntime.State.DECIDING, absolute_minute, &"REPLAN_AFTER_INTERRUPTION") GuestRuntime.State.LEAVING: if guest.state_entered_at < absolute_minute: guest_service.despawn_guest(guest.guest_id, absolute_minute) _advance_runtime_path_traffic(absolute_minute) func _advance_runtime_path_traffic(absolute_minute: int) -> void: var actors: Array[Dictionary] = [] for guest: GuestRuntime in guest_service.get_guests(): actors.append({ &"actor_id": _guest_path_actor_id(guest.guest_id), &"kind": &"guest", &"entity_id": guest.guest_id, }) for staff: StaffRuntime in staff_service.get_staff_members(): actors.append({ &"actor_id": _staff_path_actor_id(staff.staff_id), &"kind": &"staff", &"entity_id": staff.staff_id, }) actors.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return String(left[&"actor_id"]) < String(right[&"actor_id"]) ) for entry: Dictionary in actors: var actor_id: StringName = entry[&"actor_id"] if not elevators.get_actor_trip(actor_id).is_empty(): _record_expected_actor_wait(actor_id, path_service.get_actor_projected_cell(actor_id), absolute_minute) continue var kind: StringName = entry[&"kind"] var entity_id: StringName = entry[&"entity_id"] if kind == &"guest": var guest := guest_service.get_guest(entity_id) if guest == null: continue if not _path_projection_matches(actor_id, guest.current_cell): _report_path_integration_failure(actor_id, entity_id, kind, guest.current_cell) continue if guest.state == GuestRuntime.State.MOVING \ and guest.state_entered_at < absolute_minute: _advance_guest_path_travel(guest, actor_id, absolute_minute) else: _record_expected_actor_wait(actor_id, guest.current_cell, absolute_minute) continue var staff := staff_service.get_staff(entity_id) if staff == null: continue if not _path_projection_matches(actor_id, staff.current_cell): _report_path_integration_failure(actor_id, entity_id, kind, staff.current_cell) continue if staff.state == StaffRuntime.State.COMMUTING and staff_service.requires_attendance(staff.staff_id): # The commute driver already consumed this tick, including fractional # walking credit under congestion. Do not reset it as an idle task. continue if _operations_enabled and staff.state == StaffRuntime.State.OFF_DUTY and staff.current_task_id.is_empty(): var rest := get_staff_rest_report(staff.staff_id) if rest.get(&"reachable", false): advance_staff_commute(staff.staff_id, rest[&"cell"], absolute_minute) else: _record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute) continue var task := staff_service.get_task(staff.current_task_id) if task != null and task.state == StaffTask.State.MOVING \ and task.assigned_staff_id == staff.staff_id \ and supply_logistics.can_move_task(task.task_id): _advance_staff_task_travel(task, actor_id, absolute_minute) else: _record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute) func _path_projection_matches(actor_id: StringName, cell: Vector3i) -> bool: return path_service.has_actor_projection(actor_id) \ and path_service.get_actor_projected_cell(actor_id) == cell \ and _movement_credits.has(actor_id) func _report_path_integration_failure( actor_id: StringName, entity_id: StringName, kind: StringName, cell: Vector3i ) -> void: _clock.set_paused(true) _publish_actor_event(&"PATH_ACTOR_CROSSLINK_FAILED", &"simulation_runtime", [actor_id, entity_id], { &"actor_kind": kind, &"actor_cell": cell, &"projected_cell": path_service.get_actor_projected_cell(actor_id), &"has_projection": path_service.has_actor_projection(actor_id), &"has_movement_credit": _movement_credits.has(actor_id), }) func _record_expected_actor_wait( actor_id: StringName, cell: Vector3i, absolute_minute: int ) -> void: # Intentional queue, service, decision, and idle waits are valid progress. Reset # the stuck baseline so a later movement does not inherit intentional waiting. path_service.clear_actor_progress(actor_id) var recorded := path_service.record_actor_progress(actor_id, cell, absolute_minute) if not bool(recorded.get(&"ok", false)): _report_path_integration_failure(actor_id, actor_id, &"unknown", cell) return _movement_credits[actor_id] = {&"credit": 0, &"updated_at": absolute_minute} func _record_blocked_actor_wait( actor_id: StringName, entity_id: StringName, kind: StringName, cell: Vector3i, absolute_minute: int, credit: int ) -> void: var recorded := path_service.record_actor_progress(actor_id, cell, absolute_minute) if not bool(recorded.get(&"ok", false)): _report_path_integration_failure(actor_id, entity_id, kind, cell) return _movement_credits[actor_id] = { &"credit": clampi(credit, 0, _cell_movement_credit_limit(path_service, cell) - 1), &"updated_at": absolute_minute, } if int(recorded.get(&"minutes_without_progress", 0)) >= PathService.STUCK_THRESHOLD_MINUTES: _recover_stuck_actor(actor_id, entity_id, kind, cell, absolute_minute) func _movement_credit_after_one_minute( actor_id: StringName, cell: Vector3i, absolute_minute: int ) -> int: var current: Dictionary = _movement_credits.get(actor_id, { &"credit": 0, &"updated_at": absolute_minute - 1, }) var carried := int(current.get(&"credit", 0)) if int(current.get(&"updated_at", -1)) >= absolute_minute: return carried var speed_credit := clampi( roundi(path_service.get_speed_multiplier(cell) * MOVEMENT_CREDIT_SCALE), MIN_MOVEMENT_CREDIT_PER_MINUTE, MOVEMENT_CREDIT_SCALE ) return carried + speed_credit func _advance_guest_path_travel( guest: GuestRuntime, actor_id: StringName, absolute_minute: int ) -> void: var target_cell := _path_handle_target(guest.path_handle, guest.current_cell) if guest.current_cell == target_cell: _record_expected_actor_wait(actor_id, guest.current_cell, absolute_minute) _queue_guest_at_target(guest, absolute_minute) return var path := path_service.find_path(guest.current_cell, target_cell) if path.size() <= 1: _record_blocked_actor_wait( actor_id, guest.guest_id, &"guest", guest.current_cell, absolute_minute, 0 ) return if _request_elevator_step(actor_id, path[0], path[1], absolute_minute, []): return var required_credit := _step_movement_credit(path_service, path[0], path[1], []) var accumulated := _movement_credit_after_one_minute( actor_id, guest.current_cell, absolute_minute ) accumulated = mini(accumulated, required_credit + MOVEMENT_CREDIT_SCALE - 1) var guest_warp := _warp_step_connector(path[0], path[1], []) if accumulated < required_credit or not _warp_fare_affordable(guest_warp, 1): _record_blocked_actor_wait( actor_id, guest.guest_id, &"guest", guest.current_cell, absolute_minute, accumulated ) return var previous_cell := guest.current_cell var previous_progress_cell := guest.last_progress_cell var previous_progress_at := guest.last_progress_at var previous_failure_count := guest.path_failure_count var previous_recovery_kind := guest.recovery_kind if not guest_service.record_path_success(guest.guest_id, path[1], absolute_minute) \ or not path_service.project_actor_position(actor_id, path[1], absolute_minute): guest.current_cell = previous_cell guest.last_progress_cell = previous_progress_cell guest.last_progress_at = previous_progress_at guest.path_failure_count = previous_failure_count guest.recovery_kind = previous_recovery_kind _report_path_integration_failure(actor_id, guest.guest_id, &"guest", previous_cell) return _charge_warp_fare(guest_warp, [actor_id], previous_cell, guest.current_cell) var remaining_credit := accumulated - required_credit _movement_credits[actor_id] = { &"credit": remaining_credit, &"updated_at": absolute_minute, } _publish_actor_event(&"GUEST_MOVED", &"path_service", [guest.guest_id, actor_id], { &"from_cell": previous_cell, &"cell": guest.current_cell, &"target_cell": target_cell, &"movement_credit": remaining_credit, }) if guest.current_cell == target_cell: _queue_guest_at_target(guest, absolute_minute) func _queue_guest_at_target(guest: GuestRuntime, absolute_minute: int) -> void: var operation_key := StringName( "guest.auto_queue.%s.%d" % [guest.guest_id, guest.state_entered_at] ) var result := submit_command( &"create_service_request", { &"facility_id": guest.target_facility_id, &"guest_ids": [guest.guest_id], &"service_type": &"default", }, guest.guest_id, guest.target_facility_id, 0, absolute_minute, operation_key ) if not result.is_success() and guest.state == GuestRuntime.State.MOVING: guest_service.transition_guest( guest.guest_id, GuestRuntime.State.DECIDING, absolute_minute, &"QUEUE_REQUEST_FAILED" ) func _guest_recovery_targets(current_cell: Vector3i) -> Array: return [{ &"kind": &"emergency_exit", &"cell": current_cell, &"path_handle": {&"recovery": true, &"target_cell": current_cell}, }] func _guest_recovery_path_exists( from_cell: Vector3i, to_cell: Vector3i, _guest_id: StringName, _target: Dictionary ) -> bool: return not path_service.find_path(from_cell, to_cell).is_empty() func _advance_staff_task_travel( task: StaffTask, actor_id: StringName, absolute_minute: int ) -> void: var staff := staff_service.get_staff(task.assigned_staff_id) if staff == null or staff.current_task_id != task.task_id: return if task.task_type == CleaningBridge.TASK_TYPE and not cleaning_bridge.prepare_route(task, staff): _record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute) return if staff.current_cell == task.target_cell: _record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute) staff_service.begin_task(task.task_id, absolute_minute) return var path := path_service.find_path( staff.current_cell, task.target_cell, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"] ) if task.task_type in [&"rescue_pickup", &"rescue_transport"]: path = path_service.find_backyard_path(staff.current_cell, task.target_cell, true) if path.size() <= 1: if task.task_type in [&"rescue_pickup", &"rescue_transport"]: _record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute) return _record_blocked_actor_wait( actor_id, staff.staff_id, &"staff", staff.current_cell, absolute_minute, 0 ) return if task.task_type == &"rescue_transport" and elevators.owns_connector(path_service.get_step_connector(path[0], path[1], [&"staff"])): var patient_actor := _staff_path_actor_id(task.target_entity_id) var connector := path_service.get_step_connector(path[0], path[1], [&"staff"]) var pair := {} if not _elevator_extension_pending(elevators.get_shaft_for_connector(connector)): pair = elevators.request_pair(actor_id, patient_actor, path[0], path[1], absolute_minute, [&"staff"]) if pair.get(&"ok", false) and not pair.get(&"replay", false): _publish_actor_event(&"ELEVATOR_RESCUE_REQUESTED", &"elevators", [actor_id, patient_actor], pair[&"trip"]) _record_expected_actor_wait(actor_id, staff.current_cell, absolute_minute) return if _request_elevator_step(actor_id, path[0], path[1], absolute_minute, [&"staff"]): return var required_credit := _step_movement_credit(path_service, path[0], path[1], [&"staff"]) var accumulated := _movement_credit_after_one_minute( actor_id, staff.current_cell, absolute_minute ) accumulated = mini(accumulated, required_credit + MOVEMENT_CREDIT_SCALE - 1) var staff_warp := _warp_step_connector(path[0], path[1], [&"staff"]) var warp_riders := 2 if task.task_type == &"rescue_transport" else 1 if accumulated < required_credit or not _warp_fare_affordable(staff_warp, warp_riders): _record_blocked_actor_wait( actor_id, staff.staff_id, &"staff", staff.current_cell, absolute_minute, accumulated ) return var previous_cell := staff.current_cell staff.current_cell = path[1] if not path_service.project_actor_position(actor_id, staff.current_cell, absolute_minute): staff.current_cell = previous_cell _report_path_integration_failure(actor_id, staff.staff_id, &"staff", previous_cell) return if task.task_type == &"rescue_transport": var patient := staff_service.get_staff(task.target_entity_id) if patient == null or not path_service.project_actor_position(_staff_path_actor_id(patient.staff_id), staff.current_cell, absolute_minute): staff.current_cell = previous_cell path_service.project_actor_position(actor_id, previous_cell, absolute_minute) return patient.current_cell = staff.current_cell _record_expected_actor_wait(_staff_path_actor_id(patient.staff_id), patient.current_cell, absolute_minute) _charge_warp_fare(staff_warp, [actor_id, _staff_path_actor_id(patient.staff_id)], previous_cell, staff.current_cell) else: _charge_warp_fare(staff_warp, [actor_id], previous_cell, staff.current_cell) var remaining_credit := accumulated - required_credit _movement_credits[actor_id] = { &"credit": remaining_credit, &"updated_at": absolute_minute, } _publish_actor_event(&"STAFF_MOVED", &"staff_service", [staff.staff_id, task.task_id], { &"path_actor_id": actor_id, &"from_cell": previous_cell, &"cell": staff.current_cell, &"target_cell": task.target_cell, &"movement_credit": remaining_credit, }) if staff.current_cell == task.target_cell: staff_service.begin_task(task.task_id, absolute_minute) func _recover_stuck_actor( actor_id: StringName, entity_id: StringName, kind: StringName, cell: Vector3i, absolute_minute: int ) -> void: var path_before := path_service.create_snapshot() var guests_before := guest_service.create_snapshot() var staff_before := staff_service.create_snapshot() var inventory_before := inventory_service.create_snapshot() var events_before := domain_events.create_snapshot() var ids_before := id_generator.create_snapshot() var credits_before := _movement_credits.duplicate(true) var report := path_service.get_actor_stuck_report( actor_id, absolute_minute, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK] ) if not bool(report.get(&"ok", false)) or not bool(report.get(&"should_recover", false)): return var action := &"NONE" var action_ok := false if kind == &"guest": var guest := guest_service.get_guest(entity_id) if guest != null: var recovery_targets: Array = [] var recovery_value: Variant = report.get(&"recovery", {}) if recovery_value is Dictionary and bool(recovery_value.get(&"found", false)): var recovery_cell: Vector3i = recovery_value[&"cell"] recovery_targets.append({ &"kind": &"guidance", &"cell": recovery_cell, &"path_handle": { &"recovery": true, &"target_cell": recovery_cell, }, }) var recovery_result: Dictionary = {} for _attempt in range(GuestService.PATH_FAILURE_LIMIT): recovery_result = guest_service.record_path_failure( entity_id, absolute_minute, recovery_targets, Callable(self, "_guest_recovery_path_exists") ) if not bool(recovery_result.get(&"ok", false)) \ or bool(recovery_result.get(&"recovery_started", false)): break action_ok = bool(recovery_result.get(&"ok", false)) \ and bool(recovery_result.get(&"recovery_started", false)) action = &"GUEST_SAFE_RECOVERY" if action_ok else &"GUEST_RECOVERY_FAILED" elif kind == &"staff": var staff := staff_service.get_staff(entity_id) if staff != null and not staff.current_task_id.is_empty(): if supply_logistics.owns_task(staff.current_task_id): var recovery: Dictionary = supply_logistics.handle_path_stuck(staff.current_task_id, absolute_minute) action_ok = bool(recovery.get(&"ok", false)) action = StringName(recovery.get(&"action", &"SUPPLY_RECOVERY_FAILED")) else: var cancelled := staff_service.cancel_task(staff.current_task_id, &"PATH_STUCK", absolute_minute) action_ok = cancelled.is_success() action = &"STAFF_TASK_CANCELLED" if action_ok else &"STAFF_TASK_CANCEL_FAILED" if not action_ok: path_service.restore_snapshot(path_before) guest_service.restore_snapshot(guests_before) staff_service.restore_snapshot(staff_before) inventory_service.restore_snapshot(inventory_before) domain_events.restore_snapshot(events_before) id_generator.restore_snapshot(ids_before) _movement_credits = credits_before return _movement_credits[actor_id] = {&"credit": 0, &"updated_at": absolute_minute} _publish_actor_event(&"PATH_ACTOR_STUCK", &"path_service", [actor_id, entity_id], { &"actor_kind": kind, &"cell": cell, &"minutes_without_progress": int(report[&"minutes_without_progress"]), &"threshold_minutes": int(report[&"threshold_minutes"]), &"recovery": (report[&"recovery"] as Dictionary).duplicate(true), &"action": action, &"implicit_move_applied": false, }) func _leave_guest_queue_boundary( _guest_id: StringName, facility_id: StringName, request_id: StringName ) -> Dictionary: var request := facility_service.get_request(request_id) if request == null: return {&"ok": true, &"orphan_repaired": true} var inventory_before := inventory_service.create_snapshot() for reservation_id: StringName in request.reserved_inventory_ids: var released := inventory_service.release_reservation( reservation_id, StringName("guest.abandon.%s.%s" % [request_id, reservation_id]), _clock.absolute_minute ) if not bool(released.get(&"ok", false)): inventory_service.restore_snapshot(inventory_before) return released var leave_result := facility_service.leave_queue(facility_id, request_id, &"PATIENCE_EXHAUSTED") if not leave_result.is_success(): inventory_service.restore_snapshot(inventory_before) return {&"ok": false, &"message": leave_result.message} return {&"ok": true} func _reserve_staff_task_items(task: StaffTask, absolute_minute: int) -> Dictionary: var inventory_id := &"staff.supplies" var facility := facility_service.get_instance(task.target_entity_id) if facility != null: inventory_id = facility.local_inventory_id var before := inventory_service.create_snapshot() var reservation_ids: Array[StringName] = [] for item_id: StringName in _sorted_name_keys(task.required_items): var result := inventory_service.reserve_item( inventory_id, item_id, int(task.required_items[item_id]), task.task_id, &"staff_task", absolute_minute, absolute_minute + 480, StringName("staff.task.reserve.%s.%s" % [task.task_id, item_id]) ) if not bool(result.get(&"ok", false)): inventory_service.restore_snapshot(before) return { &"ok": false, &"reason": StringName(result.get(&"code", &"ITEM_RESERVATION_FAILED")), } for reservation_id: StringName in result.get(&"reservation_ids", []): reservation_ids.append(reservation_id) return {&"ok": true, &"reservation_ids": reservation_ids} func _release_staff_task_items( reservation_ids: Array[StringName], task: StaffTask, absolute_minute: int ) -> Dictionary: var before := inventory_service.create_snapshot() for reservation_id: StringName in reservation_ids: var result := inventory_service.release_reservation( reservation_id, StringName("staff.task.release.%s.%s" % [task.task_id, reservation_id]), absolute_minute ) if not bool(result.get(&"ok", false)): inventory_service.restore_snapshot(before) return result return {&"ok": true} func _estimate_staff_task_travel(staff: StaffRuntime, task: StaffTask, _absolute_minute: int) -> int: if task.task_type == CleaningBridge.TASK_TYPE and not cleaning_bridge.can_dispatch(staff): return -1 if task.task_type in [&"rescue_pickup", &"rescue_transport"]: if not elevators.get_actor_trip(_staff_path_actor_id(staff.staff_id)).is_empty(): return -1 var route := path_service.find_backyard_path(staff.current_cell, task.target_cell, true) return _estimated_actor_path_minutes(route, [&"staff"]) if not route.is_empty() else -1 if staff.current_cell == task.target_cell: return 0 var path := path_service.find_path( staff.current_cell, task.target_cell, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"] ) if not path.is_empty(): return _estimated_actor_path_minutes(path, [&"staff"]) return -1 func _sync_facility_staffing(absolute_minute: int) -> void: if not _staffing_projection_enabled: return var staff_members := staff_service.get_staff_members() for facility: FacilityInstance in facility_service.get_instances(): var active_ids: Array[StringName] = [] for staff: StaffRuntime in staff_members: if (staff_commute.is_present(staff, facility) or CleaningBridge.is_deployed(staff, facility, staff_service)) and not _is_anri_manager(staff): active_ids.append(staff.staff_id) facility_service.set_operational_status( facility.instance_id, {&"active_staff_ids": active_ids}, absolute_minute ) func _project_guest_candidates(guest_id: StringName) -> Array: var guest := guest_service.get_guest(guest_id) var candidates: Array = [] if guest == null: return candidates var query_goals: Array = [] for instance: FacilityInstance in facility_service.get_instances(): query_goals.append_array(_facility_access_candidates(instance)) path_service.begin_guest_path_query_batch(guest.current_cell, query_goals) for facility: FacilityInstance in facility_service.get_instances(): var definition := definition_registry.get_facility(facility.definition_id) if definition == null or &"staff_only" in definition.tags: continue if &"entrance" in definition.tags and _guest_has_checked_in(guest): continue var has_inputs := true for item_id: StringName in _sorted_name_keys(definition.inventory_inputs): if inventory_service.available_quantity(facility.local_inventory_id, item_id) < int(definition.inventory_inputs[item_id]): has_inputs = false break if not has_inputs: # Preserve the existing exclusion without doing unnecessary path queries, # but let the decision evidence explain why this facility was unavailable. candidates.append({&"facility_id": facility.instance_id, &"inputs_available": false}) continue var access := _find_facility_access_path(guest.current_cell, facility) var path: Array = access.get(&"path", []) var need_relief := _need_relief_for_facility_tags(definition.tags) candidates.append({ &"facility_id": facility.instance_id, &"facility_tags": definition.tags.duplicate(), &"is_open": facility.state in [FacilityInstance.State.OPEN, FacilityInstance.State.DEGRADED], &"reachable": bool(access.get(&"reachable", false)), &"travel_minutes": _estimated_actor_path_minutes(path, []), &"expected_wait": facility.queue_entries.size() * facility.base_service_minutes / maxi(facility.capacity, 1), &"estimated_total_gold": get_facility_business_price(facility), &"expected_quality": clampi(facility.base_quality, 0, 100), &"need_relief": need_relief, &"risk_level": 0, &"illegal_level": 0, &"group_fit": mini(facility.capacity, 4) * 5, &"service_category": _service_category_for_tags(definition.tags), &"path_handle": { &"target_cell": access.get(&"target_cell", guest.current_cell), &"graph_revision": path_service.graph_revision, }, }) path_service.end_guest_path_query_batch() return candidates func _facility_access_candidates(facility: FacilityInstance) -> Array: var candidate_cells: Dictionary = {} var occupied: Array[Vector3i] = facility.occupied_cells.duplicate() if occupied.is_empty(): occupied.append(facility.origin) for cell: Vector3i in occupied: for direction: Vector3i in [Vector3i.LEFT, Vector3i.RIGHT]: var candidate := Vector3i(cell.x + direction.x, cell.y + direction.y, cell.z) if path_service.has_cell(candidate): candidate_cells[candidate] = true if path_service.has_cell(facility.origin): candidate_cells[facility.origin] = true var ordered: Array = candidate_cells.keys() ordered.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: if a.z != b.z: return a.z < b.z if a.y != b.y: return a.y < b.y return a.x < b.x ) return ordered func _find_facility_access_path(start: Vector3i, facility: FacilityInstance) -> Dictionary: var ordered := _facility_access_candidates(facility) var best_path: Array[Vector3i] = [] var best_cell := facility.origin for candidate: Vector3i in ordered: var path := path_service.find_path(start, candidate) if path.is_empty(): continue if best_path.is_empty() or path_service.calculate_guest_batch_path_cost(start, candidate, path) < path_service.calculate_guest_batch_path_cost(start, best_cell, best_path): best_path.assign(path) best_cell = candidate return {&"reachable": not best_path.is_empty(), &"path": best_path, &"target_cell": best_cell} func _need_relief_for_facility_tags(tags: Array[StringName]) -> Dictionary: var relief := { &"hunger": 0, &"sleep": 0, &"hygiene": 0, &"medical": 0, &"leisure": 0, &"intimacy": 0, } if &"dining" in tags: relief[&"hunger"] = 80 relief[&"leisure"] = 10 if &"lodging" in tags: relief[&"sleep"] = 90 relief[&"hygiene"] = maxi(int(relief[&"hygiene"]), 10) if &"social" in tags or &"leisure" in tags: relief[&"leisure"] = maxi(int(relief[&"leisure"]), 40) if &"hygiene" in tags or &"bath" in tags: relief[&"hygiene"] = maxi(int(relief[&"hygiene"]), 80) if &"medical" in tags: relief[&"medical"] = 90 elif &"wellness" in tags: relief[&"medical"] = maxi(int(relief[&"medical"]), 20) if &"adult_only" in tags or &"restricted_service" in tags: relief[&"intimacy"] = 80 return relief func _service_category_for_tags(tags: Array[StringName]) -> StringName: if &"dining" in tags: return &"food" if &"lodging" in tags: return &"lodging" if &"social" in tags: return &"leisure" return &"service" func _path_handle_target(path_handle: Variant, fallback: Vector3i) -> Vector3i: if path_handle is Dictionary: var value: Variant = path_handle.get(&"target_cell", path_handle.get("target_cell", fallback)) var parsed: Variant = _parse_world_cell(value) if parsed != null: return parsed return fallback func _event_actor_exists(actor_id: StringName) -> bool: if actor_id == &"player": return true return guest_service.has_guest(actor_id) or staff_service.has_staff(actor_id) func _event_entity_exists(entity_id: Variant) -> bool: var normalized := StringName(entity_id) return _event_actor_exists(normalized) \ or facility_service.has_instance(normalized) \ or relationship_service.has_relationship(normalized) \ or event_service.get_instance(normalized) != null func _event_path_exists(value: Variant) -> bool: if not value is Dictionary: return false var from_value: Variant = _parse_world_cell(value.get(&"from", value.get("from", null))) var to_value: Variant = _parse_world_cell(value.get(&"to", value.get("to", null))) if from_value == null or to_value == null: return false return not path_service.find_path(from_value, to_value).is_empty() func _roll_authority_inspection(_day_index: int, _chance: int, _terms: Dictionary) -> int: var stream := get_rng_stream(&"authority") return stream.randi_range(0, 99) if stream != null else 99 func _authority_line_of_sight( from_cell: Vector3i, to_cell: Vector3i, _context: Dictionary ) -> bool: if from_cell.z != to_cell.z \ or not world_service.is_in_bounds(from_cell) \ or not world_service.is_in_bounds(to_cell): return false if from_cell == to_cell: return true var x := from_cell.x var y := from_cell.y var delta_x := absi(to_cell.x - from_cell.x) var delta_y := absi(to_cell.y - from_cell.y) var step_x := 1 if from_cell.x < to_cell.x else -1 var step_y := 1 if from_cell.y < to_cell.y else -1 var error := delta_x - delta_y while x != to_cell.x or y != to_cell.y: var doubled_error := error * 2 if doubled_error > -delta_y: error -= delta_y x += step_x if doubled_error < delta_x: error += delta_x y += step_y var current := Vector3i(x, y, from_cell.z) if current == to_cell: return true if not world_service.is_walkable(current) \ or not world_service.get_occupant(current).is_empty(): return false return true func _authority_path_query( from_cell: Vector3i, to_cell: Vector3i, context: Dictionary ) -> Dictionary: var connector_access := _name_array( context.get(&"connector_access", context.get("connector_access", [])) ) var path := path_service.find_path( from_cell, to_cell, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], connector_access ) if path.is_empty(): return { &"reachable": false, &"reason": StringName(path_service.last_error if not path_service.last_error.is_empty() else "PATH_UNREACHABLE"), &"travel_minutes": 0, } return { &"reachable": true, &"reason": &"OK", &"travel_minutes": maxi(path.size() - 1, 0), &"path": path, } func _apply_authority_resolution_boundary( payload: Dictionary, phase: StringName, rollback_token: Variant ) -> Dictionary: if phase == &"rollback": if not rollback_token is Dictionary \ or not rollback_token.get(&"economy", null) is Dictionary \ or not rollback_token.get(&"game_state", null) is Dictionary \ or not rollback_token.get(&"facilities", null) is Dictionary \ or not rollback_token.get(&"domain_events", null) is Dictionary \ or not rollback_token.get(&"id_generator", null) is Dictionary: return {&"ok": false, &"error": "Authority rollback token is malformed."} var economy_ok: bool = economy_service.restore_snapshot(rollback_token[&"economy"]) var facilities_ok: bool = facility_service.restore_snapshot(rollback_token[&"facilities"]) facilities_ok = facility_cleanliness.restore_snapshot(rollback_token.get(&"facility_cleanliness", {})) and facilities_ok var events_ok: bool = domain_events.restore_snapshot(rollback_token[&"domain_events"]) var ids_ok: bool = id_generator.restore_snapshot(rollback_token[&"id_generator"]) var state_ok: bool = _state.restore_snapshot(rollback_token[&"game_state"]) return { &"ok": economy_ok and state_ok and facilities_ok and events_ok and ids_ok, &"error": "Authority fine rollback failed." if not economy_ok or not state_ok or not facilities_ok or not events_ok or not ids_ok else "", } var resolution_id := StringName(payload.get(&"resolution_id", payload.get("resolution_id", ""))) var inspection_id := StringName(payload.get(&"inspection_id", payload.get("inspection_id", ""))) var fine_amount := int(payload.get(&"fine_amount", payload.get("fine_amount", -1))) var absolute_minute := int(payload.get(&"absolute_minute", payload.get("absolute_minute", -1))) var idempotency_key := _authority_resolution_idempotency_key(resolution_id) if resolution_id.is_empty() or inspection_id.is_empty() or fine_amount < 0 \ or absolute_minute != _runtime_event_minute() \ or not economy_service.has_account(PLAYER_CASH_ACCOUNT) \ or not economy_service.has_account(AUTHORITY_FINE_ACCOUNT): return {&"ok": false, &"error": "Authority resolution payload is invalid."} if phase == &"validate": var existing_transaction := economy_service.get_transaction_id_for_idempotency_key(idempotency_key) if not existing_transaction.is_empty(): if not _authority_fine_transaction_matches( economy_service, existing_transaction, inspection_id, fine_amount ): return {&"ok": false, &"error": "Authority fine idempotency key conflicts with its ledger transaction."} return {&"ok": true, &"token": { &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"game_state": _state.create_snapshot(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), &"already_applied": true, &"transaction_id": existing_transaction, }} if fine_amount > 0 \ and not economy_service.account_allows_negative(PLAYER_CASH_ACCOUNT) \ and economy_service.get_balance(PLAYER_CASH_ACCOUNT) < fine_amount: return {&"ok": false, &"error": "PLAYER_CASH cannot fund the Authority fine."} return {&"ok": true, &"token": { &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"game_state": _state.create_snapshot(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), &"already_applied": false, }} if phase != &"apply" or not rollback_token is Dictionary: return {&"ok": false, &"token": rollback_token, &"error": "Unknown Authority resolution phase or token."} if StringName(payload.get(&"result", &"")) == &"TEMPORARY_CLOSURE": var targets: Dictionary = {} for raw_id: Variant in payload.get(&"evidence_ids", []): var evidence := authority_service.get_evidence(StringName(raw_id)) if evidence != null and evidence.is_discovered() and not evidence.is_expired(absolute_minute) and facility_service.has_instance(evidence.facility_id): targets[evidence.facility_id] = true for target_id: StringName in _sorted_name_keys(targets): var stopped := facility_service.set_operational_status(target_id, {&"suspension_reason": &"RAIDED"}, absolute_minute) if not stopped.is_success(): return {&"ok": false, &"token": rollback_token, &"error": stopped.message} if bool(rollback_token.get(&"already_applied", false)) or fine_amount == 0: return { &"ok": true, &"transaction_id": StringName(rollback_token.get(&"transaction_id", &"")), } var posted := economy_service.transfer( PLAYER_CASH_ACCOUNT, AUTHORITY_FINE_ACCOUNT, fine_amount, absolute_minute, &"AUTHORITY_FINE", inspection_id, idempotency_key, &"authority.inspection.fine" ) if not posted.is_success(): return { &"ok": false, &"token": rollback_token, &"error": posted.message, } return { &"ok": true, &"transaction_id": posted.details.get(&"transaction_id", &""), } func _authority_resolution_idempotency_key(resolution_id: StringName) -> StringName: return StringName("authority.resolution.%s" % resolution_id) func _authority_fine_transaction_matches( economy: EconomyService, transaction_id: StringName, inspection_id: StringName, fine_amount: int ) -> bool: if fine_amount == 0: return transaction_id.is_empty() var entries := economy.get_transaction_entries(transaction_id) if entries.size() != 1: return false var entry: LedgerEntry = entries[0] return entry.debit_account == AUTHORITY_FINE_ACCOUNT \ and entry.credit_account == PLAYER_CASH_ACCOUNT \ and entry.amount == fine_amount \ and entry.category == &"AUTHORITY_FINE" \ and entry.source_entity_id == inspection_id func _roll_defense_round(_key: StringName, _context: Dictionary) -> int: var stream := get_rng_stream(&"defense") return stream.randi_range(0, 100) if stream != null else 50 func _apply_defense_rng_boundary( _context: Dictionary, phase: StringName, token: Variant ) -> Dictionary: var stream := get_rng_stream(&"defense") if stream == null: return {&"ok": false, &"error": "Defense RNG stream is unavailable."} if phase == &"snapshot": return {&"ok": true, &"token": stream.snapshot()} if phase == &"commit": return {&"ok": true, &"token": token} if phase == &"rollback": if not token is Dictionary or not stream.restore(token): return {&"ok": false, &"error": "Defense RNG rollback token is invalid."} return {&"ok": true} return {&"ok": false, &"error": "Unknown Defense RNG boundary phase."} func _apply_defense_skill_boundary( payload: Dictionary, phase: StringName, rollback_token: Variant ) -> Dictionary: if phase == &"rollback": return _restore_defense_skill_token(rollback_token) var cost_report := _parse_defense_skill_cost(payload) if not bool(cost_report[&"ok"]): return cost_report var cost: Dictionary = cost_report[&"cost"] var raid_id := StringName(payload.get(&"raid_id", payload.get("raid_id", ""))) var action_id := StringName(payload.get(&"action_id", payload.get("action_id", ""))) if raid_id.is_empty() or action_id.is_empty(): return {&"ok": false, &"error": "Defense skill boundary IDs are invalid."} if phase == &"validate": var token := { &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"game_state": _state.create_snapshot(), &"inventory": inventory_service.create_snapshot(), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), &"cost": cost.duplicate(true), &"reservation_ids": [], } var gold := int(cost[&"gold"]) var mana := int(cost[&"mana"]) if economy_service.get_balance(PLAYER_CASH_ACCOUNT) < gold \ or int(_state.get_resource(&"mana")) < mana: return {&"ok": false, &"error": "Defense skill resources are insufficient."} var ledger_key := _defense_skill_ledger_key(raid_id, action_id) if gold > 0 and not economy_service.get_transaction_id_for_idempotency_key(ledger_key).is_empty(): return {&"ok": false, &"error": "Defense skill ledger key already exists without an applied skill."} var reservation_ids: Array[StringName] = [] var items: Array = cost[&"items"] for item_index: int in range(items.size()): var item: Dictionary = items[item_index] var reserved := inventory_service.reserve_item( StringName(item[&"inventory_id"]), StringName(item[&"item_id"]), int(item[&"quantity"]), action_id, &"defense_skill", _defense_operation_minute(), -1, StringName("defense.skill.reserve.%s.%s.%d" % [raid_id, action_id, item_index]) ) if not bool(reserved.get(&"ok", false)): token[&"reservation_ids"] = reservation_ids return { &"ok": false, &"token": token, &"error": "Defense skill item reservation failed.", &"details": reserved, } for reservation_value: Variant in reserved.get(&"reservation_ids", []): reservation_ids.append(StringName(reservation_value)) token[&"reservation_ids"] = reservation_ids return {&"ok": true, &"token": token} if phase != &"apply" or not _defense_skill_token_is_valid(rollback_token): return {&"ok": false, &"token": rollback_token, &"error": "Defense skill apply token is invalid."} var token: Dictionary = rollback_token var mana := int(cost[&"mana"]) if mana > 0 and not bool(_state.apply_resource_deltas({&"mana": -mana})): return {&"ok": false, &"token": token, &"error": "Defense skill Mana application failed."} var gold := int(cost[&"gold"]) if gold > 0: var posted := economy_service.transfer( PLAYER_CASH_ACCOUNT, OPERATING_EXPENSE_ACCOUNT, gold, _defense_operation_minute(), &"DEFENSE_SKILL_COST", action_id, _defense_skill_ledger_key(raid_id, action_id), &"defense.skill.cost" ) if not posted.is_success(): return {&"ok": false, &"token": token, &"error": posted.message} var reservation_ids: Array[StringName] = [] for reservation_value: Variant in token[&"reservation_ids"]: reservation_ids.append(StringName(reservation_value)) if not reservation_ids.is_empty(): var consumed := inventory_service.consume_reservations( reservation_ids, StringName("defense.skill.consume.%s.%s" % [raid_id, action_id]), _defense_operation_minute() ) if not bool(consumed.get(&"ok", false)): return {&"ok": false, &"token": token, &"error": "Defense skill item consumption failed.", &"details": consumed} return {&"ok": true, &"token": token} func _parse_defense_skill_cost(payload: Dictionary) -> Dictionary: var metadata_value: Variant = payload.get(&"metadata", payload.get("metadata", {})) if not metadata_value is Dictionary: return {&"ok": false, &"error": "Defense skill metadata must be a Dictionary."} var metadata: Dictionary = metadata_value var cost_value: Variant = metadata.get(&"cost", metadata.get("cost", {})) if not cost_value is Dictionary: return {&"ok": false, &"error": "Defense skill metadata.cost must be a Dictionary."} var cost_data: Dictionary = cost_value var gold_value: Variant = cost_data.get(&"gold", cost_data.get("gold", 0)) var mana_value: Variant = cost_data.get(&"mana", cost_data.get("mana", 0)) if not gold_value is int or not mana_value is int: return {&"ok": false, &"error": "Defense skill Gold and Mana costs must be integers."} var gold := int(gold_value) var mana := int(mana_value) if gold < 0 or mana < 0: return {&"ok": false, &"error": "Defense skill costs cannot be negative."} var items_value: Variant = cost_data.get(&"items", cost_data.get("items", [])) if not items_value is Array: return {&"ok": false, &"error": "Defense skill item costs must be an Array."} var items: Array[Dictionary] = [] for raw_item: Variant in items_value: if not raw_item is Dictionary: return {&"ok": false, &"error": "Defense skill item cost is malformed."} var inventory_value: Variant = raw_item.get(&"inventory_id", raw_item.get("inventory_id", "")) var item_value: Variant = raw_item.get(&"item_id", raw_item.get("item_id", "")) var quantity_value: Variant = raw_item.get(&"quantity", raw_item.get("quantity", 0)) if not (inventory_value is String or inventory_value is StringName) \ or not (item_value is String or item_value is StringName) \ or not quantity_value is int: return {&"ok": false, &"error": "Defense skill item cost types are invalid."} var inventory_id := StringName(inventory_value) var item_id := StringName(item_value) var quantity := int(quantity_value) if inventory_id.is_empty() or item_id.is_empty() or quantity <= 0: return {&"ok": false, &"error": "Defense skill item cost requires inventory_id, item_id and positive quantity."} items.append({&"inventory_id": inventory_id, &"item_id": item_id, &"quantity": quantity}) items.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: var left_key := "%s:%s" % [left[&"inventory_id"], left[&"item_id"]] var right_key := "%s:%s" % [right[&"inventory_id"], right[&"item_id"]] return left_key < right_key ) return {&"ok": true, &"cost": {&"gold": gold, &"mana": mana, &"items": items}} func _defense_skill_ledger_key(raid_id: StringName, action_id: StringName) -> StringName: return StringName("defense.skill.%s.%s" % [raid_id, action_id]) func _defense_skill_token_is_valid(token: Variant) -> bool: return token is Dictionary \ and token.get(&"economy", null) is Dictionary \ and token.get(&"game_state", null) is Dictionary \ and token.get(&"inventory", null) is Dictionary \ and token.get(&"domain_events", null) is Dictionary \ and token.get(&"id_generator", null) is Dictionary \ and token.get(&"reservation_ids", null) is Array func _restore_defense_skill_token(token: Variant) -> Dictionary: if not _defense_skill_token_is_valid(token): return {&"ok": false, &"error": "Defense skill rollback token is malformed."} var economy_ok: bool = economy_service.restore_snapshot(token[&"economy"]) var state_ok: bool = _state.restore_snapshot(token[&"game_state"]) var inventory_ok: bool = inventory_service.restore_snapshot(token[&"inventory"]) var events_ok: bool = domain_events.restore_snapshot(token[&"domain_events"]) var ids_ok: bool = id_generator.restore_snapshot(token[&"id_generator"]) return { &"ok": economy_ok and state_ok and inventory_ok and events_ok and ids_ok, &"error": "Defense skill rollback failed." if ( not economy_ok or not state_ok or not inventory_ok or not events_ok or not ids_ok ) else "", } func _apply_defense_outcome_boundary( payload: Dictionary, phase: StringName, rollback_token: Variant ) -> Dictionary: if phase == &"rollback": return _restore_defense_outcome_token(rollback_token) var validation := _validate_defense_outcome_payload(payload) if not bool(validation[&"ok"]): return validation if phase == &"validate": return {&"ok": true, &"token": { &"staff": staff_service.create_snapshot(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), }} if phase != &"apply" or not _defense_outcome_token_is_valid(rollback_token): return {&"ok": false, &"token": rollback_token, &"error": "Defense outcome apply token is invalid."} var token: Dictionary = rollback_token var combatant_results: Array = payload[&"combatant_results"] for result_value: Variant in combatant_results: var result: Dictionary = result_value if StringName(result[&"side"]) != &"DEFENDER": continue var staff := staff_service.get_staff(StringName(result[&"actor_id"])) staff.health = int(result[&"health"]) staff.fatigue = int(result[&"fatigue"]) var defense_state := StringName(result[&"state"]) if defense_state == &"ACTIVE": staff.transition_to(StaffRuntime.State.AVAILABLE, &"DEFENSE_RELEASED", _defense_operation_minute()) elif defense_state == &"INCAPACITATED": staff.transition_to(StaffRuntime.State.INCAPACITATED, &"DEFENSE_INCAPACITATED", _defense_operation_minute()) elif defense_state == &"RETREATED": staff.transition_to(StaffRuntime.State.FLEEING, &"DEFENSE_RETREATED", _defense_operation_minute()) var asset_results: Array = payload[&"asset_results"] for result_value: Variant in asset_results: var result: Dictionary = result_value var updated := facility_service.set_operational_status( StringName(result[&"asset_id"]), {&"durability": int(result[&"integrity"])}, _defense_operation_minute() ) if not updated.is_success(): return {&"ok": false, &"token": token, &"error": updated.message} var staff_report := staff_service.validate_invariants() if not bool(staff_report.get(&"ok", false)): return {&"ok": false, &"token": token, &"error": "Defense outcome violates Staff invariants.", &"details": staff_report} return {&"ok": true, &"token": token} func _validate_defense_outcome_payload(payload: Dictionary) -> Dictionary: var raid_id := StringName(payload.get(&"raid_id", payload.get("raid_id", ""))) var resolution_id := StringName(payload.get(&"resolution_id", payload.get("resolution_id", ""))) var operation_kind := StringName(payload.get(&"operation_kind", payload.get("operation_kind", ""))) var absolute_minute := int(payload.get(&"absolute_minute", payload.get("absolute_minute", -1))) var combatants_value: Variant = payload.get(&"combatant_results", payload.get("combatant_results", null)) var assets_value: Variant = payload.get(&"asset_results", payload.get("asset_results", null)) var raid := defense_service.get_raid(raid_id) if raid_id.is_empty() or resolution_id.is_empty() \ or operation_kind not in DefenseService.VALID_OPERATION_KINDS \ or absolute_minute != _defense_operation_minute() \ or not combatants_value is Array or not assets_value is Array \ or raid.is_empty(): return {&"ok": false, &"error": "Defense outcome payload root is invalid."} var expected_combatants: Dictionary = raid[&"combatants"] var expected_assets: Dictionary = raid[&"assets"] if combatants_value.size() != expected_combatants.size() \ or assets_value.size() != expected_assets.size(): return {&"ok": false, &"error": "Defense outcome does not cover the complete Raid roster."} var seen_combatants: Dictionary = {} for value: Variant in combatants_value: if not value is Dictionary: return {&"ok": false, &"error": "Defense combatant outcome is malformed."} var actor_id := StringName(value.get(&"actor_id", value.get("actor_id", ""))) var side := StringName(value.get(&"side", value.get("side", ""))) var state := StringName(value.get(&"state", value.get("state", ""))) var health := int(value.get(&"health", value.get("health", -1))) var maximum := int(value.get(&"max_health", value.get("max_health", -1))) var fatigue := int(value.get(&"fatigue", value.get("fatigue", -1))) if actor_id.is_empty() or seen_combatants.has(actor_id) or not expected_combatants.has(actor_id) \ or side not in [&"DEFENDER", &"ATTACKER"] \ or state not in [&"ACTIVE", &"INCAPACITATED", &"RETREATED"] \ or health < 0 or health > maximum or fatigue < 0 or fatigue > 100 \ or (health == 0 and state == &"ACTIVE"): return {&"ok": false, &"error": "Defense combatant outcome values are invalid."} var expected_combatant: Dictionary = expected_combatants[actor_id] if side != StringName(expected_combatant[&"side"]) \ or maximum != int(expected_combatant[&"max_health"]): return {&"ok": false, &"error": "Defense combatant outcome differs from the Raid roster."} seen_combatants[actor_id] = true if side == &"DEFENDER" and (maximum != StaffRuntime.MAX_PSYCHOLOGY_VALUE or not staff_service.has_staff(actor_id)): return {&"ok": false, &"error": "Defense outcome references invalid Staff."} var seen_assets: Dictionary = {} for value: Variant in assets_value: if not value is Dictionary: return {&"ok": false, &"error": "Defense asset outcome is malformed."} var asset_id := StringName(value.get(&"asset_id", value.get("asset_id", ""))) var integrity := int(value.get(&"integrity", value.get("integrity", -1))) var maximum := int(value.get(&"max_integrity", value.get("max_integrity", -1))) if asset_id.is_empty() or seen_assets.has(asset_id) or not expected_assets.has(asset_id) \ or maximum != FacilityInstance.DEFAULT_DURABILITY \ or integrity < 0 or integrity > maximum \ or not facility_service.has_instance(asset_id): return {&"ok": false, &"error": "Defense outcome references invalid Facility data."} var expected_asset: Dictionary = expected_assets[asset_id] if maximum != int(expected_asset[&"max_integrity"]): return {&"ok": false, &"error": "Defense asset outcome differs from the Raid roster."} seen_assets[asset_id] = true return {&"ok": true} func _defense_outcome_token_is_valid(token: Variant) -> bool: return token is Dictionary \ and token.get(&"staff", null) is Dictionary \ and token.get(&"facilities", null) is Dictionary \ and token.get(&"domain_events", null) is Dictionary \ and token.get(&"id_generator", null) is Dictionary func _restore_defense_outcome_token(token: Variant) -> Dictionary: if not _defense_outcome_token_is_valid(token): return {&"ok": false, &"error": "Defense outcome rollback token is malformed."} var staff_ok: bool = staff_service.restore_snapshot(token[&"staff"]) var facilities_ok: bool = facility_service.restore_snapshot(token[&"facilities"]) facilities_ok = facility_cleanliness.restore_snapshot(token.get(&"facility_cleanliness", {})) and facilities_ok var events_ok: bool = domain_events.restore_snapshot(token[&"domain_events"]) var ids_ok: bool = id_generator.restore_snapshot(token[&"id_generator"]) return { &"ok": staff_ok and facilities_ok and events_ok and ids_ok, &"error": "Defense outcome rollback failed." if not staff_ok or not facilities_ok or not events_ok or not ids_ok else "", } func _execute_event_action_boundary( action: EventAction, phase: StringName, context: Dictionary, rollback_token: Variant ) -> Dictionary: if action.type not in [ EventAction.Type.ADD_LEDGER_ENTRY, EventAction.Type.ADD_OR_REMOVE_ITEM, EventAction.Type.CREATE_TASK, EventAction.Type.CREATE_DEFENSE_RAID, EventAction.Type.SET_FACILITY_STATE, EventAction.Type.TELEPORT_ENTITY_FOR_RECOVERY, ]: return {&"not_handled": true} if phase == &"rollback": return _restore_event_action_token(rollback_token) var validation := _build_event_defense_request(action, context) if action.type == EventAction.Type.CREATE_DEFENSE_RAID else _validate_event_action(action) if not bool(validation.get(&"ok", false)) or phase == &"validate": return validation var token := _capture_event_action_token() if action.type == EventAction.Type.CREATE_DEFENSE_RAID: token[&"defense"] = defense_service.create_snapshot() var result := _apply_event_action(action, context) if not bool(result.get(&"ok", false)): var rollback := _restore_event_action_token(token) if not bool(rollback.get(&"ok", false)): result[&"fatal"] = true result[&"rollback"] = rollback result[&"error"] = "Event Action failed and rollback failed: %s; %s" % [result.get(&"error", ""), rollback.get(&"error", "")] return result result[&"token"] = token return result func _validate_event_action(action: EventAction) -> Dictionary: match action.type: EventAction.Type.ADD_LEDGER_ENTRY: var from_account := StringName(action.payload.get(&"from_account", action.payload.get("from_account", PLAYER_CASH_ACCOUNT))) var to_account := StringName(action.payload.get(&"to_account", action.payload.get("to_account", OPERATING_EXPENSE_ACCOUNT))) var amount := int(action.payload.get(&"amount", action.payload.get("amount", 0))) if amount <= 0 or not economy_service.has_account(from_account) or not economy_service.has_account(to_account): return {&"ok": false, &"error": "Ledger Action has invalid accounts or amount."} if not economy_service.account_allows_negative(from_account) and economy_service.get_balance(from_account) < amount: return {&"ok": false, &"error": "Ledger Action source cannot fund the amount."} EventAction.Type.ADD_OR_REMOVE_ITEM: var inventory_id := StringName(action.payload.get(&"inventory_id", action.payload.get("inventory_id", ""))) var item_id := StringName(action.payload.get(&"item_id", action.payload.get("item_id", ""))) var delta := int(action.payload.get(&"quantity_delta", action.payload.get("quantity_delta", 0))) if inventory_id.is_empty() or item_id.is_empty() or delta == 0: return {&"ok": false, &"error": "Inventory Action requires inventory, item and non-zero delta."} if delta < 0 and inventory_service.available_quantity(inventory_id, item_id) < -delta: return {&"ok": false, &"error": "Inventory Action stock is unavailable."} EventAction.Type.CREATE_TASK: if StringName(action.payload.get(&"task_type", action.payload.get("task_type", ""))).is_empty(): return {&"ok": false, &"error": "CREATE_TASK requires task_type."} EventAction.Type.SET_FACILITY_STATE: var facility_id := StringName(action.payload.get(&"facility_id", action.payload.get("facility_id", ""))) var conditions_value: Variant = action.payload.get(&"conditions", action.payload.get("conditions", null)) if not facility_service.has_instance(facility_id) or not conditions_value is Dictionary: return {&"ok": false, &"error": "Facility Action target or conditions are invalid."} var defense_owner: StringName = _defense_lock_owner(facility_id, &"ASSET") if not defense_owner.is_empty() \ and (conditions_value.has(&"durability") or conditions_value.has("durability")): return {&"ok": false, &"error": "Event Action cannot mutate Defense-owned Facility durability."} EventAction.Type.TELEPORT_ENTITY_FOR_RECOVERY: var entity_id := StringName(action.payload.get(&"entity_id", action.payload.get("entity_id", ""))) var cell_value: Variant = _parse_world_cell( action.payload.get(&"cell", action.payload.get("cell", null)) ) if not _event_actor_exists(entity_id) or cell_value == null: return {&"ok": false, &"error": "Recovery teleport target is invalid."} var target_cell: Vector3i = cell_value var traffic := path_service.get_cell_traffic(target_cell) var actor_id := ( _guest_path_actor_id(entity_id) if guest_service.has_guest(entity_id) else _staff_path_actor_id(entity_id) ) if traffic.is_empty() or bool(traffic.get(&"temporary_block", false)) \ or not path_service.has_actor_projection(actor_id): return {&"ok": false, &"error": "Recovery teleport requires a projected actor and an unblocked walkable target."} return {&"ok": true} func _apply_event_action(action: EventAction, context: Dictionary) -> Dictionary: var instance_id := StringName(context.get(&"event_instance_id", "")) match action.type: EventAction.Type.CREATE_DEFENSE_RAID: return _apply_event_defense_raid(action, context) EventAction.Type.ADD_LEDGER_ENTRY: var from_account := StringName(action.payload.get(&"from_account", action.payload.get("from_account", PLAYER_CASH_ACCOUNT))) var to_account := StringName(action.payload.get(&"to_account", action.payload.get("to_account", OPERATING_EXPENSE_ACCOUNT))) var amount := int(action.payload.get(&"amount", action.payload.get("amount", 0))) var result := economy_service.transfer( from_account, to_account, amount, _runtime_event_minute(), &"EVENT_ACTION", instance_id, StringName("event.action.%s.%s" % [instance_id, action.action_id]) ) return {&"ok": result.is_success(), &"error": result.message} EventAction.Type.ADD_OR_REMOVE_ITEM: var inventory_id := StringName(action.payload.get(&"inventory_id", action.payload.get("inventory_id", ""))) var item_id := StringName(action.payload.get(&"item_id", action.payload.get("item_id", ""))) var delta := int(action.payload.get(&"quantity_delta", action.payload.get("quantity_delta", 0))) if delta > 0: var added := inventory_service.add_stack(inventory_id, item_id, delta, 100, -1, _runtime_event_minute()) return {&"ok": bool(added.get(&"ok", false)), &"error": added.get(&"message", "")} var reserved := inventory_service.reserve_item( inventory_id, item_id, -delta, instance_id, &"event_action", _runtime_event_minute(), _runtime_event_minute() + 1, StringName("event.item.reserve.%s.%s" % [instance_id, action.action_id]) ) if not bool(reserved.get(&"ok", false)): return {&"ok": false, &"error": reserved.get(&"message", "Item reservation failed.")} var consumed := inventory_service.consume_reservations( _name_array(reserved.get(&"reservation_ids", [])), StringName("event.item.consume.%s.%s" % [instance_id, action.action_id]), _runtime_event_minute() ) return {&"ok": bool(consumed.get(&"ok", false)), &"error": consumed.get(&"message", "")} EventAction.Type.CREATE_TASK: var raw_options: Variant = action.payload.get(&"options", action.payload.get("options", {})) var options: Dictionary = raw_options.duplicate(true) if raw_options is Dictionary else {} var created := staff_service.create_task( StringName(action.payload.get(&"task_type", action.payload.get("task_type", ""))), int(action.payload.get(&"priority", action.payload.get("priority", 50))), _runtime_event_minute(), options, StringName(action.payload.get(&"task_id", action.payload.get("task_id", ""))) ) return {&"ok": created.is_success(), &"error": created.message} EventAction.Type.SET_FACILITY_STATE: var updated := facility_service.set_operational_status( StringName(action.payload.get(&"facility_id", action.payload.get("facility_id", ""))), action.payload.get(&"conditions", action.payload.get("conditions", {})), _runtime_event_minute() ) return {&"ok": updated.is_success(), &"error": updated.message} EventAction.Type.TELEPORT_ENTITY_FOR_RECOVERY: var entity_id := StringName(action.payload.get(&"entity_id", action.payload.get("entity_id", ""))) var cell: Vector3i = _parse_world_cell(action.payload.get(&"cell", action.payload.get("cell", null))) var guest := guest_service.get_guest(entity_id) var actor_id: StringName var moved := false if guest != null: actor_id = _guest_path_actor_id(entity_id) moved = guest_service.record_path_success( entity_id, cell, _runtime_event_minute() ) else: var staff := staff_service.get_staff(entity_id) actor_id = _staff_path_actor_id(entity_id) staff.current_cell = cell moved = true if not moved or not path_service.project_actor_position( actor_id, cell, _runtime_event_minute() ): return {&"ok": false, &"error": "Recovery teleport could not commit its Path projection."} _movement_credits[actor_id] = { &"credit": 0, &"updated_at": _runtime_event_minute(), } _publish_actor_event(&"ENTITY_RECOVERY_TELEPORTED", &"event_service", [entity_id, actor_id], { &"cell": cell, &"path_actor_id": actor_id, }) return {&"ok": true} return {&"ok": false, &"error": "Unsupported Event Action."} func _capture_event_action_token() -> Dictionary: return { &"economy": economy_service.create_snapshot(), &"facility_upkeep": facility_upkeep.create_snapshot(), &"upkeep_settlement": upkeep_settlement.create_snapshot(), &"game_state": _state.create_snapshot(), &"inventory": inventory_service.create_snapshot(), &"staff": staff_service.create_snapshot(), &"facilities": facility_service.create_snapshot(), &"facility_cleanliness": facility_cleanliness.create_snapshot(), &"guests": guest_service.create_snapshot(), &"path": path_service.create_snapshot(), &"elevators": elevators.create_snapshot(), &"legacy_stairs": legacy_stairs.create_snapshot(), &"movement_credits": _movement_credits.duplicate(true), &"domain_events": domain_events.create_snapshot(), &"id_generator": id_generator.create_snapshot(), } func _restore_event_action_token(token: Variant) -> Dictionary: if not token is Dictionary: return {&"ok": false, &"error": "Event rollback token is malformed."} # Every participant must be asked to restore even if an earlier participant # reports an error; short-circuiting here would leave a half-rolled-back world. var economy_ok: bool = economy_service.restore_snapshot(token.get(&"economy", {})) var state_ok: bool = _state.restore_snapshot(token.get(&"game_state", {})) var inventory_ok: bool = inventory_service.restore_snapshot(token.get(&"inventory", {})) var staff_ok: bool = staff_service.restore_snapshot(token.get(&"staff", {})) var facilities_ok: bool = facility_service.restore_snapshot(token.get(&"facilities", {})) facilities_ok = facility_cleanliness.restore_snapshot(token.get(&"facility_cleanliness", {})) and facilities_ok var guests_ok: bool = guest_service.restore_snapshot(token.get(&"guests", {})) var path_ok: bool = path_service.restore_snapshot(token.get(&"path", {})) path_ok = elevators.restore_snapshot(token.get(&"elevators", {})) and path_ok path_ok = legacy_stairs.restore_snapshot(token.get(&"legacy_stairs", {})) and path_ok var credits_value: Variant = token.get(&"movement_credits", null) var credits_ok := credits_value is Dictionary if credits_ok: _movement_credits = (credits_value as Dictionary).duplicate(true) var defense_ok := true if token.has(&"defense"): # This creates PREPARING incidents only; rollback must not reset another # raid's pre-existing same-tick automatic/manual attempt guard. defense_ok = token[&"defense"] is Dictionary and defense_service.restore_snapshot(token[&"defense"]) var events_ok: bool = domain_events.restore_snapshot(token.get(&"domain_events", {})) var ids_ok: bool = id_generator.restore_snapshot(token.get(&"id_generator", {})) var ok: bool = economy_ok and state_ok and inventory_ok and staff_ok \ and facilities_ok and guests_ok and path_ok and credits_ok and defense_ok and events_ok and ids_ok return {&"ok": ok, &"error": "Event Action rollback failed." if not ok else ""} func _reserve_event_cost(cost: Dictionary, context: Dictionary) -> Dictionary: var normalized: Dictionary = {} for raw_resource: Variant in cost: var resource_id := StringName(raw_resource) var amount := int(cost[raw_resource]) if resource_id.is_empty() or amount < 0: return {&"ok": false, &"error": "Event Choice cost is invalid."} normalized[resource_id] = amount var economy_before := economy_service.create_snapshot() var state_before: Dictionary = _state.create_snapshot() var ids_before := id_generator.create_snapshot() var instance_id := StringName(context.get(&"event_instance_id", "")) var reservation_id: StringName = &"" for amount: int in normalized.values(): if amount > 0: if id_generator.get_counter(&"event_cost") >= StableIdGenerator.MAX_COUNTER: return {&"ok": false, &"error": "Event cost reservation IDs are exhausted."} reservation_id = id_generator.next_id(&"event_cost") break var escrow_account := StringName("EVENT_ESCROW:%s" % instance_id) var reservation := {&"event_instance_id": instance_id, &"cost": normalized, &"escrow_account": escrow_account} if not reservation_id.is_empty(): reservation[&"reservation_id"] = reservation_id var gold := int(normalized.get(&"gold", 0)) if gold > 0: if not economy_service.has_account(escrow_account): economy_service.register_account(escrow_account) var transfer := economy_service.transfer( PLAYER_CASH_ACCOUNT, escrow_account, gold, _runtime_event_minute(), &"EVENT_COST_RESERVED", instance_id, _event_cost_ledger_key(reservation, &"reserve") ) if not transfer.is_success(): economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) id_generator.restore_snapshot(ids_before) return {&"ok": false, &"error": transfer.message} var other_costs := normalized.duplicate(true) other_costs.erase(&"gold") if not other_costs.is_empty() and not _state.spend(other_costs): economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) id_generator.restore_snapshot(ids_before) return {&"ok": false, &"error": "Event Choice resources are unavailable."} return {&"ok": true, &"token": reservation} func _release_event_cost(token: Variant, _context: Dictionary) -> Dictionary: if token is Dictionary and token.is_empty(): return {&"ok": true} if not token is Dictionary or not token.get(&"cost", null) is Dictionary: return {&"ok": false, &"error": "Event Cost release token is malformed."} var cost: Dictionary = token[&"cost"] var instance_id := StringName(token.get(&"event_instance_id", "")) var escrow_account := StringName(token.get(&"escrow_account", "")) var gold := int(cost.get(&"gold", 0)) if gold > 0: var released := economy_service.transfer( escrow_account, PLAYER_CASH_ACCOUNT, gold, _runtime_event_minute(), &"EVENT_COST_RELEASED", instance_id, _event_cost_ledger_key(token, &"release") ) if not released.is_success(): return {&"ok": false, &"error": released.message} for raw_resource: Variant in cost: var resource_id := StringName(raw_resource) if resource_id != &"gold": _state.add_resource(resource_id, int(cost[raw_resource])) return {&"ok": true} func _commit_event_cost(token: Variant, _context: Dictionary) -> Dictionary: if token is Dictionary and token.is_empty(): return {&"ok": true} if not token is Dictionary or not token.get(&"cost", null) is Dictionary: return {&"ok": false, &"error": "Event Cost commit token is malformed."} var cost: Dictionary = token[&"cost"] var gold := int(cost.get(&"gold", 0)) if gold <= 0: return {&"ok": true} var instance_id := StringName(token.get(&"event_instance_id", "")) var committed := economy_service.transfer( StringName(token.get(&"escrow_account", "")), OPERATING_EXPENSE_ACCOUNT, gold, _runtime_event_minute(), &"EVENT_COST_COMMITTED", instance_id, _event_cost_ledger_key(token, &"commit") ) return {&"ok": committed.is_success(), &"error": committed.message} func _event_cost_ledger_key(token: Dictionary, operation: StringName) -> StringName: var reservation_id := StringName(token.get(&"reservation_id", "")) if not reservation_id.is_empty(): return StringName("event.reserved_cost.%s.%s" % [operation, reservation_id]) # Existing pending save tokens keep the original ledger operation identity. return StringName("event.cost.%s.%s" % [operation, token.get(&"event_instance_id", "")]) func _on_relationship_threshold_candidate(candidate: Dictionary) -> void: var candidate_id := StringName(candidate.get(&"candidate_id", "")) if candidate_id.is_empty() or event_service.get_instance(candidate_id) != null: return event_service.enqueue_candidate( &"relationship.threshold", candidate.get(&"actor_ids", []), int(candidate.get(&"created_at", _runtime_event_minute())), { &"event_instance_id": candidate_id, &"deduplication_key": StringName(candidate.get(&"deduplication_key", candidate_id)), &"context": {&"relationship_candidate": candidate.duplicate(true)}, } ) func _route_lock_index(value: Variant) -> int: if value is int: return value if value >= 0 and value < RelationshipState.RouteLock.keys().size() else -1 return RelationshipState.RouteLock.keys().find(String(value).to_upper()) func _validate_construction_facility_crosslinks( world: WorldService, construction: ConstructionService, facilities: FacilityService, projector: FacilityInfrastructureProjector = null ) -> bool: for construction_facility: Dictionary in construction.get_facilities(): var instance_id := StringName(construction_facility.get(&"instance_id", &"")) var definition_id := StringName(construction_facility.get(&"definition_id", &"")) var definition := definition_registry.get_facility(definition_id) if instance_id.is_empty() or definition == null: return false if _facility_definition_is_schema_proxy(definition): if facilities.has_instance(instance_id): return false if _facility_definition_is_supported_infrastructure_proxy(definition): if projector == null or not projector.has_projection(instance_id): return false elif projector != null and projector.has_projection(instance_id): return false for cell: Vector3i in construction_facility.get(&"occupied_cells", []): if world.get_occupant(cell) != instance_id \ or world.get_occupant_kind(cell) != &"facility": return false continue var operational := facilities.get_instance(instance_id) if operational == null or (projector != null and projector.has_projection(instance_id)): return false if operational.definition_id != StringName( construction_facility.get(&"definition_id", &"") ) or operational.origin != construction_facility.get(&"origin", Vector3i.ZERO): return false var expected_cells: Dictionary = {} for cell: Vector3i in construction_facility.get(&"occupied_cells", []): expected_cells[cell] = true var actual_cells: Dictionary = {} for cell: Vector3i in operational.occupied_cells: actual_cells[cell] = true if expected_cells != actual_cells: return false for cell: Vector3i in expected_cells: if world.get_occupant(cell) != instance_id \ or world.get_occupant_kind(cell) != &"facility": return false return true func _facility_definition_is_schema_proxy(definition: Variant) -> bool: return definition is FacilityDefinition and &"schema_facility_proxy" in definition.tags func _facility_definition_is_supported_infrastructure_proxy(definition: Variant) -> bool: return _facility_definition_is_schema_proxy(definition) \ and definition.id in FacilityInfrastructureProjector.SUPPORTED_KINDS func _facility_definition_is_unsupported_vertical_proxy(definition: Variant) -> bool: return _facility_definition_is_schema_proxy(definition) \ and definition.id in FacilityInfrastructureProjector.UNSUPPORTED_VERTICAL_KINDS func _migrate_legacy_infrastructure_proxies( construction: ConstructionService, facilities: FacilityService, projector: FacilityInfrastructureProjector ) -> bool: if construction == null or facilities == null or projector == null: return false for instance: Dictionary in construction.get_facilities(): var instance_id := StringName(instance.get(&"instance_id", &"")) var definition := definition_registry.get_facility( StringName(instance.get(&"definition_id", &"")) ) if instance_id.is_empty() or definition == null: return false if not _facility_definition_is_schema_proxy(definition): continue if facilities.has_instance(instance_id) \ and not facilities.unregister_instance(instance_id).is_success(): return false if _facility_definition_is_unsupported_vertical_proxy(definition): continue if not _facility_definition_is_supported_infrastructure_proxy(definition): return false var projected := projector.project_completed_facility( StringName("runtime.migrate.v6:%s" % instance_id), definition, instance ) if not projected.is_success(): return false return true func _validate_infrastructure_crosslinks( world: WorldService, path: PathService, construction: ConstructionService, _resource_state: Variant, excavation: ExcavationService, spatial: SpatialSubslotService, adapter: RouteSubslotPathAdapter, projector: FacilityInfrastructureProjector, require_minute_alignment := true ) -> bool: if world == null or path == null or construction == null or excavation == null \ or spatial == null or adapter == null or projector == null: return false # An active projector validates its bound Spatial and RouteAdapter services # itself. Reuse that composed check only for these exact dependencies. # Quarantine intentionally suppresses its live dependency check, so retain # the independent validations on that path (and for a different binding). var composed_dependencies := projector.is_bound_to(adapter, spatial, path) and not projector.is_quarantined() if not adapter.is_bound_to(spatial, path) \ or (not composed_dependencies and ( not bool(spatial.validate_invariants().get(&"ok", false)) or not bool(adapter.validate_invariants().get(&"ok", false)))) \ or not bool(projector.validate_invariants().get(&"ok", false)) \ or not bool(excavation.validate_invariants().get(&"ok", false)) \ or (require_minute_alignment \ and excavation.current_minute != construction.current_minute) \ or not _same_vector3i_set( adapter.get_base_walkable_cells(false), world.get_walkable_cells(false) ) \ or not _same_vector3i_set( adapter.get_projected_route_cells(), spatial.get_route_cells() ) \ or not path.matches_passable_cells(adapter.get_projected_walkable_cells()): return false var expected_projection_ids: Dictionary = {} for instance: Dictionary in construction.get_facilities(): var instance_id := StringName(instance.get(&"instance_id", &"")) var definition_id := StringName(instance.get(&"definition_id", &"")) var definition := definition_registry.get_facility(definition_id) if instance_id.is_empty() or definition == null: return false if not _facility_definition_is_schema_proxy(definition): if projector.has_projection(instance_id): return false continue if _facility_definition_is_unsupported_vertical_proxy(definition): if projector.has_projection(instance_id): return false continue if not _facility_definition_is_supported_infrastructure_proxy(definition): return false expected_projection_ids[instance_id] = true var projection := projector.get_projection(instance_id) if projection.is_empty() \ or StringName(projection.get(&"definition_id", &"")) != definition_id \ or StringName(projection.get(&"source_job_id", &"")) \ != StringName(instance.get(&"source_job_id", &"")) \ or projection.get(&"origin", null) != instance.get(&"origin", null) \ or int(projection.get(&"rotation", -1)) != int(instance.get(&"rotation", -2)) \ or int(projection.get(&"layer", -1)) != int(instance.get(&"layer", -2)) \ or not _same_vector3i_set( projection.get(&"cells", []), instance.get(&"occupied_cells", []) ): return false var actual_projection_ids: Dictionary = {} for instance_id: StringName in projector.get_projection_ids(): if actual_projection_ids.has(instance_id): return false actual_projection_ids[instance_id] = true return actual_projection_ids == expected_projection_ids func _same_vector3i_set(first: Variant, second: Variant) -> bool: if not first is Array or not second is Array or first.size() != second.size(): return false var expected: Dictionary = {} for value: Variant in first: if not value is Vector3i or expected.has(value): return false expected[value] = true for value: Variant in second: if not value is Vector3i or not expected.has(value): return false expected.erase(value) return expected.is_empty() func _validate_runtime_crosslinks( facilities: FacilityService, guests: GuestService, economy: EconomyService, inventory: InventoryService, staff: StaffService, absolute_minute: int, staffing_projection_enabled: bool ) -> bool: if absolute_minute < 0 \ or facilities.current_minute != absolute_minute \ or guests.current_minute != absolute_minute \ or inventory.current_minute != absolute_minute \ or staff.current_minute != absolute_minute: return false for assignment: Dictionary in staff.get_work_assignments(): var facility_id := StringName(assignment[&"facility_id"]) if facility_id.is_empty(): continue var assigned_facility := facilities.get_instance(facility_id) if assigned_facility == null: return false var assigned_staff := staff.get_staff(StringName(assignment[&"staff_id"])) var definition := definition_registry.get_facility(assigned_facility.definition_id) if assigned_staff == null or definition == null or definition.staff_max <= 0 \ or (not _staff_role_matches_facility(assigned_staff.role_id, definition) and not _is_anri_manager(assigned_staff)): return false var queued_links: Dictionary = {} var service_links: Dictionary = {} for facility: FacilityInstance in facilities.get_instances(): if staffing_projection_enabled: for staff_id: StringName in facility.active_staff_ids: var active_staff := staff.get_staff(staff_id) if active_staff == null \ or active_staff.assigned_facility_id != facility.instance_id \ or (active_staff.state != StaffRuntime.State.AVAILABLE and not CleaningBridge.is_deployed(active_staff, facility, staff)) \ or _is_anri_manager(active_staff): return false for request: ServiceRequest in facilities.get_queue(facility.instance_id): if not _reservation_references_are_valid( request.reserved_inventory_ids, request.request_id, inventory ): return false for guest_id: StringName in request.guest_ids: if not guests.has_guest(guest_id): continue var guest := guests.get_guest(guest_id) if guest.state != GuestRuntime.State.QUEUING \ or guest.queue_request_id != request.request_id \ or guest.target_facility_id != facility.instance_id: return false queued_links[guest_id] = request.request_id for service: ServiceInstance in facilities.get_active_services(facility.instance_id): if not _reservation_references_are_valid( service.inventory_reservations, service.request_id, inventory ): return false for guest_id: StringName in service.guest_ids: if not guests.has_guest(guest_id): continue var guest := guests.get_guest(guest_id) if guest.state != GuestRuntime.State.RECEIVING_SERVICE \ or guest.current_service_id != service.service_id \ or guest.target_facility_id != facility.instance_id: return false service_links[guest_id] = service.service_id for guest: GuestRuntime in guests.get_guests(): if not guest.queue_request_id.is_empty() \ and queued_links.get(guest.guest_id, &"") != guest.queue_request_id: return false if not guest.current_service_id.is_empty() \ and service_links.get(guest.guest_id, &"") != guest.current_service_id: return false var account_id := _guest_wallet_account(guest.guest_id) if not economy.has_account(account_id) \ or economy.get_balance(account_id) != guest.wallet_gold: return false for task: StaffTask in staff.get_tasks(): if not _reservation_references_are_valid( task.reservation_ids, task.task_id, inventory ): return false return true func _validate_authority_economy_crosslinks( authority: AuthorityService, economy: EconomyService, absolute_minute: int ) -> bool: if authority == null or authority.current_minute != absolute_minute \ or not bool(authority.validate_invariants().get(&"ok", false)) \ or not economy.has_account(AUTHORITY_FINE_ACCOUNT): return false var authority_snapshot := authority.create_snapshot() var raw_resolutions: Variant = authority_snapshot.get(&"applied_resolutions", null) if not raw_resolutions is Array: return false var expected_fine_balance := 0 for raw_resolution: Variant in raw_resolutions: if not raw_resolution is Dictionary \ or not raw_resolution.get(&"payload", null) is Dictionary: return false var resolution_id := StringName(raw_resolution.get(&"resolution_id", "")) var payload: Dictionary = raw_resolution[&"payload"] var inspection_id := StringName(payload.get(&"inspection_id", "")) var fine_amount := int(payload.get(&"fine_amount", -1)) if resolution_id.is_empty() or inspection_id.is_empty() or fine_amount < 0: return false expected_fine_balance += fine_amount if fine_amount == 0: continue var transaction_id := economy.get_transaction_id_for_idempotency_key( _authority_resolution_idempotency_key(resolution_id) ) if transaction_id.is_empty() or not _authority_fine_transaction_matches( economy, transaction_id, inspection_id, fine_amount ): return false return economy.get_balance(AUTHORITY_FINE_ACCOUNT) == expected_fine_balance func _validate_live_defense_crosslinks() -> bool: return _validate_defense_crosslinks( defense_service, staff_service, facility_service, _defense_operation_minute() ) func _validate_defense_crosslinks( defense: DefenseService, staff: StaffService, facilities: FacilityService, absolute_minute: int ) -> bool: if defense == null or staff == null or facilities == null \ or absolute_minute < 0 or defense.current_minute != absolute_minute \ or not bool(defense.validate_invariants().get(&"ok", false)): return false var defender_owners: Dictionary = {} var asset_owners: Dictionary = {} for raid_id: StringName in defense.get_raid_ids(): var raid := defense.get_raid(raid_id) if raid.is_empty(): return false var state := int(raid.get(&"state", -1)) var terminal := state in DefenseService.TERMINAL_STATES var preparing := state == DefenseService.RaidState.PREPARING var combatants_value: Variant = raid.get(&"combatants", null) var assets_value: Variant = raid.get(&"assets", null) if not combatants_value is Dictionary or not assets_value is Dictionary: return false var combatants: Dictionary = combatants_value for actor_id: StringName in _sorted_name_keys(combatants): var combatant_value: Variant = combatants[actor_id] if not combatant_value is Dictionary: return false var combatant: Dictionary = combatant_value if StringName(combatant.get(&"side", &"")) != &"DEFENDER": continue if not terminal: if defender_owners.has(actor_id): return false defender_owners[actor_id] = raid_id if int(combatant.get(&"max_health", -1)) != StaffRuntime.MAX_PSYCHOLOGY_VALUE \ or int(combatant.get(&"initial_health", -1)) != StaffRuntime.MAX_PSYCHOLOGY_VALUE: return false # A terminal Raid is historical. Its reconciled Staff may later heal, # work elsewhere, leave the roster, or enter another Raid; requiring the # old outcome to remain a permanent live mirror would make future saves # unrestorable. Capacity baselines remain self-contained above. if terminal: continue var linked_staff := staff.get_staff(actor_id) if linked_staff == null or linked_staff.state != StaffRuntime.State.UNAVAILABLE \ or not linked_staff.current_task_id.is_empty(): return false if preparing and ( int(combatant.get(&"health", -1)) != linked_staff.health or int(combatant.get(&"fatigue", -1)) != linked_staff.fatigue ): return false var assets: Dictionary = assets_value for asset_id: StringName in _sorted_name_keys(assets): var asset_value: Variant = assets[asset_id] if not asset_value is Dictionary: return false var asset: Dictionary = asset_value if not terminal: if asset_owners.has(asset_id): return false asset_owners[asset_id] = raid_id if int(asset.get(&"max_integrity", -1)) != FacilityInstance.DEFAULT_DURABILITY \ or int(asset.get(&"initial_integrity", -1)) != FacilityInstance.DEFAULT_DURABILITY: return false # Terminal assets are historical for the same reason as terminal Staff: # the live Facility can subsequently be repaired, reused, or demolished. if terminal: continue var facility := facilities.get_instance(asset_id) if facility == null: return false if preparing and int(asset.get(&"integrity", -1)) != facility.durability: return false return true func _defense_lock_owner( external_id: StringName, lock_kind: StringName, excluded_raid_id: StringName = &"" ) -> StringName: if defense_service == null or external_id.is_empty() \ or lock_kind not in [&"DEFENDER", &"ASSET"]: return &"" for raid_id: StringName in defense_service.get_raid_ids(): if raid_id == excluded_raid_id: continue var raid := defense_service.get_raid(raid_id) if raid.is_empty() or int(raid.get(&"state", -1)) in DefenseService.TERMINAL_STATES: continue if lock_kind == &"DEFENDER": var combatants_value: Variant = raid.get(&"combatants", {}) if not combatants_value is Dictionary or not combatants_value.has(external_id): continue var combatant_value: Variant = combatants_value[external_id] if combatant_value is Dictionary \ and StringName(combatant_value.get(&"side", &"")) == &"DEFENDER": return raid_id else: var assets_value: Variant = raid.get(&"assets", {}) if assets_value is Dictionary and assets_value.has(external_id): return raid_id return &"" func _reservation_references_are_valid( reservation_ids: Array[StringName], requester_id: StringName, inventory: InventoryService ) -> bool: for reservation_id: StringName in reservation_ids: var reservation := inventory.get_reservation(reservation_id) if reservation == null or reservation.requester_id != requester_id: return false return true ## HUD view of the A06 reserve: supply, consumption and storage per interval. func get_mana_reserve_report() -> Dictionary: var demand := _mana_reserve_demand() return { &"amount": int(_state.get_resource(&"mana")), &"capacity": ManaReserveCycle.CAPACITY, &"generation": ManaReserveCycle.WORLD_TREE_GENERATION, &"interval_minutes": ManaReserveCycle.INTERVAL_MINUTES, &"demand": demand, &"net": ManaReserveCycle.WORLD_TREE_GENERATION - demand, &"last_flow": mana_reserve.get_last_flow(), } ## A06 upkeep demand: every owned consumer, open or not, except destroyed ## facilities. Removed facilities are already unregistered from the network. func _mana_reserve_demand() -> int: var destroyed: Array[StringName] = [] for facility: FacilityInstance in facility_service.get_instances(): if facility.suspension_reason == &"DESTROYED": destroyed.append(facility.instance_id) return mana_network_service.get_total_consumer_demand(destroyed) func _advance_mana(absolute_minute: int) -> void: if _operations_tick_blocked: return var infrastructure_preflight := _preflight_proxy_completions(absolute_minute) if not bool(infrastructure_preflight.get(&"ok", false)): _latch_infrastructure_failure( StringName(infrastructure_preflight.get( &"code", &"INFRASTRUCTURE_PREFLIGHT_FAILED" )), absolute_minute, String(infrastructure_preflight.get( &"reason", "Infrastructure completion preflight failed." )), infrastructure_preflight.get(&"details", {}) ) return # A06: generation and upkeep demand settle before local distribution and the # whole-tower gate, so the network and projection see the committed reserve. var reserve_flow := mana_reserve.advance_to( absolute_minute, int(_state.get_resource(&"mana")), _mana_reserve_demand() ) if not bool(reserve_flow.get(&"ok", false)): _latch_infrastructure_failure( StringName(reserve_flow.get(&"code", &"MANA_RESERVE_FAILED")), absolute_minute, "The finite Mana reserve could not settle this boundary.", {&"mana": int(_state.get_resource(&"mana"))} ) return if bool(reserve_flow.get(&"processed", false)): _state.set_resource(&"mana", int(reserve_flow[&"after"])) var report := mana_network_service.advance_one_minute(absolute_minute) # Finite reserve is an additional gate, even between network evaluations and # for zero-demand facilities. The network remains the local supply authority. preload("res://scripts/mana/mana_power_projection.gd").apply( facility_service, mana_network_service, int(_state.get_resource(&"mana")), absolute_minute ) var warp_sync := _synchronize_warps() if not bool(warp_sync.get(&"ok", false)): _latch_infrastructure_failure(&"WARP_SYNC_FAILED", absolute_minute, "Warp connectors could not follow the current power state.", warp_sync) return for change: Dictionary in report.get(&"state_changes", []): var consumer_id := StringName(change.get(&"consumer_id", "")) _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"MANA_STATE_CHANGED", absolute_minute, &"mana_network_service", [consumer_id], change, 0 )) func _on_facility_completed(instance: Dictionary) -> void: # The staged Runtime boundary has already projected/registered this Facility # and committed its DomainEvents before Construction releases raw signals. if _construction_committing_notifications: return var instance_id := StringName(instance.get(&"instance_id", &"")) var definition := definition_registry.get_facility( StringName(instance.get(&"definition_id", &"")) ) if instance_id.is_empty() or definition == null: _latch_infrastructure_failure( &"INVALID_COMPLETED_FACILITY", _runtime_event_minute(), "Construction emitted an invalid completed Facility.", {&"instance_id": instance_id} ) return if _facility_definition_is_schema_proxy(definition): if not _facility_definition_is_supported_infrastructure_proxy(definition): _latch_infrastructure_failure( &"UNSUPPORTED_VERTICAL_TOPOLOGY", _runtime_event_minute(), "An unsupported vertical proxy reached Construction completion.", {&"instance_id": instance_id, &"definition_id": definition.id} ) return var projection_request_id := StringName( "runtime.complete:%s" % String(instance.get(&"source_job_id", instance_id)) ) var projected := facility_infrastructure_projector.project_completed_facility( projection_request_id, definition, instance ) if not projected.is_success(): _latch_infrastructure_failure( &"FACILITY_INFRASTRUCTURE_PROJECTION_FAILED", _runtime_event_minute(), projected.message, projected.details ) return _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"FACILITY_INFRASTRUCTURE_PROJECTED", _runtime_event_minute(), &"facility_infrastructure_projector", [instance_id], projected.details.duplicate(true), 0 )) else: _register_facility_mana_consumer(instance) _register_operational_facility(instance) _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"FACILITY_COMPLETED", _runtime_event_minute(), &"construction_service", [instance_id], instance, 0 )) func _on_excavation_completed(report: Dictionary) -> void: var job_id := StringName(report.get(&"job_id", &"")) if job_id.is_empty(): return var synchronized := route_subslot_path_adapter.synchronize_projection( StringName("runtime.excavation:%s" % job_id), world_service.get_walkable_cells() ) if not synchronized.is_success(): _latch_infrastructure_failure( &"EXCAVATION_ROUTE_SYNCHRONIZATION_FAILED", int(report.get(&"completed_at", _runtime_event_minute())), synchronized.message, synchronized.details ) return _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"EXCAVATION_COMPLETED", int(report.get(&"completed_at", _runtime_event_minute())), &"excavation_service", [job_id], report.duplicate(true), 0 )) func _on_facility_demolished(instance_id: StringName, refund: Dictionary) -> void: peeping_room_service.target_removed(instance_id) if not mana_network_service.get_node(instance_id).is_empty(): mana_network_service.unregister_node(instance_id) if facility_service.has_instance(instance_id): facility_service.unregister_instance(instance_id) _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"FACILITY_DEMOLISHED", _runtime_event_minute(), &"construction_service", [instance_id], {&"refund": refund}, 0 )) func _on_peeping_target_removed(instance_id: StringName) -> void: peeping_room_service.target_removed(instance_id) func _execute_peeping_room(command: SimulationCommand) -> CommandResult: var payload := command.payload var report: Dictionary if command.command_type == &"peeping_room_place": report = PeepingRooms.result(&"BUILD_DEFINITION_PENDING_D04") elif command.command_type == &"peeping_room_validate": if typeof(payload.get(&"origin")) != TYPE_VECTOR3I or typeof(payload.get(&"footprint", Vector2i(2, 1))) != TYPE_VECTOR2I or typeof(payload.get(&"target_instance_id", &"")) not in [TYPE_STRING, TYPE_STRING_NAME]: report = PeepingRooms.result(&"INVALID_PAYLOAD") else: report = peeping_room_service.validate_placement(payload[&"origin"], payload.get(&"footprint", Vector2i(2, 1)), StringName(payload.get(&"target_instance_id", &""))) else: var key: StringName = &"target_instance_id" if command.command_type == &"peeping_room_relink" else &"source_event_key" if typeof(payload.get(&"room_id")) not in [TYPE_STRING, TYPE_STRING_NAME] or typeof(payload.get(key, &"")) not in [TYPE_STRING, TYPE_STRING_NAME]: report = PeepingRooms.result(&"INVALID_PAYLOAD") elif command.command_type == &"peeping_room_relink": report = peeping_room_service.relink(StringName(payload[&"room_id"]), StringName(payload.get(key, &""))) else: report = peeping_room_service.record_evidence(StringName(payload[&"room_id"]), StringName(payload.get(key, &"")), _runtime_event_minute()) if report[&"ok"]: return CommandResult.succeeded(command.command_id, report) return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, command.command_id, String(report[&"reason"]), report) func _on_ledger_transaction_posted(transaction_id: StringName, _entries: Array) -> void: if _operations_flushing_economy_notifications.has(transaction_id): return if _state != null: _state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT)) func get_mvp_content_report() -> Dictionary: return mvp_content_report.duplicate(true) func get_mvp_guest_definition_ids() -> Array[StringName]: var result: Array[StringName] = [] for definition: GuestDefinition in mvp_content_catalog.build_all_guest_definitions(): result.append(definition.guest_type_id) return result func get_mvp_staff_candidate_ids() -> Array[StringName]: return mvp_content_catalog.get_staff_candidate_ids() func get_mvp_staff_candidate_payload(candidate_id: StringName) -> Dictionary: return mvp_content_catalog.staff_hire_payload(candidate_id).duplicate(true) ## Read-only presentation data for the hiring screen. The validated catalog ## remains authoritative and the UI never receives a mutable reference to it. func get_mvp_staff_candidate_summaries() -> Array[Dictionary]: var summaries: Array[Dictionary] = [] for candidate_id: StringName in mvp_content_catalog.get_recruitable_staff_ids(): var entry := mvp_content_catalog.get_recruitment_entry(candidate_id) var payload := mvp_content_catalog.staff_hire_payload(candidate_id) if entry.is_empty() or payload.is_empty(): continue var options_value: Variant = payload.get(&"options", {}) var options: Dictionary = options_value if options_value is Dictionary else {} var skills_value: Variant = options.get(&"skills", {}) var skills: Dictionary = skills_value if skills_value is Dictionary else {} summaries.append({ &"candidate_id": candidate_id, &"display_name": String(entry.get(&"display_name", candidate_id)), &"age": int(payload.get(&"age", 0)), &"role_id": StringName(payload.get(&"role_id", &"")), &"daily_wage": int(payload.get(&"daily_wage", 0)), &"signing_cost": int(payload.get(&"signing_cost", 0)), &"preferred_shift": StringName(payload.get(&"preferred_shift", &"any")), &"skills": skills.duplicate(true), &"hired": _hired_staff_candidate_ids.has(candidate_id), &"unique": candidate_id == &"heroine_anri", }) return summaries ## Defensive presentation snapshot for the Staff management panel. Planned ## assignment remains visible at night/off-duty even when the active link clears. func get_staff_management_report() -> Dictionary: var rows: Array[Dictionary] = [] for staff: StaffRuntime in staff_service.get_staff_members(): var entry := mvp_content_catalog.get_recruitment_entry(staff.staff_id) var plan := staff_service.get_work_assignment(staff.staff_id) var shift := staff_service.get_shift(staff.shift_id) var template_id: StringName = &"DAY" var effective_from := _runtime_event_minute() var next_start_at := -1 var assigned_facility_id := staff.assigned_facility_id if not plan.is_empty(): template_id = StringName(plan[&"template_id"]) assigned_facility_id = StringName(plan[&"facility_id"]) effective_from = int(plan[&"effective_from"]) next_start_at = int(plan[&"next_start_at"]) elif shift != null: template_id = shift.template_id effective_from = shift.start_at else: template_id = &"NIGHT" if StringName(entry.get(&"preferred_shift", &"day")) == &"night" else &"DAY" var block_reason := _staff_assignment_block_reason(staff) rows.append({ &"staff_id": staff.staff_id, &"display_name": String(entry.get(&"display_name", staff.staff_id)), &"role_id": staff.role_id, &"state": staff.state, &"state_name": StaffRuntime.State.keys()[staff.state], &"assigned_facility_id": assigned_facility_id, &"active_facility_id": staff.assigned_facility_id, &"template_id": template_id, &"recurring": not plan.is_empty() and not assigned_facility_id.is_empty(), &"effective_from": effective_from, &"next_start_at": next_start_at, &"daily_wage": staff.get_effective_daily_wage(), &"health": staff.health, &"fatigue": staff.fatigue, &"unpaid_wages": staff.unpaid_wages, &"current_task_id": staff.current_task_id, &"rescue_status": _staff_rescue_status(staff.staff_id), &"rest_status": get_staff_rest_report(staff.staff_id).get(&"label", ""), &"can_edit": block_reason.is_empty(), &"assignment_block_reason": block_reason, &"unique_contract": _staff_unique_contract_report(staff), &"original_relationship": relationship_service.get_original_relationship(staff.staff_id), }) var facilities: Array[Dictionary] = [] for facility: FacilityInstance in facility_service.get_instances(): var definition := definition_registry.get_facility(facility.definition_id) if definition == null or definition.staff_max <= 0: continue facilities.append({ &"facility_id": facility.instance_id, &"display_name": definition.display_name, &"definition_id": facility.definition_id, &"layer": facility.origin.z, &"origin": facility.origin, &"required_roles": definition.required_roles.duplicate(), &"staff_min": definition.staff_min, &"staff_max": definition.staff_max, }) return {&"staff": rows, &"facilities": facilities} ## Original 6.1 / 6.2 presentation, derived from validated canonical content ## and the same planned placement as pricing. This report owns all containers; ## it does not add a hire, consent, growth policy, save field or tick-side effect. func _staff_unique_contract_report(staff: StaffRuntime) -> Dictionary: if staff == null or staff.employment_kind != StaffRuntime.EmploymentKind.UNIQUE or not mvp_content_catalog.is_valid(): return {} var definition := mvp_content_catalog.get_entry(&"unique_characters", staff.staff_id) if definition.is_empty() or StringName(definition.get(&"role_id", &"")) != staff.role_id: return {} var passive_effects: Array[Dictionary] = [] var contract := { &"canonical_id": staff.staff_id, &"free_employment": true, &"free_upkeep": staff.get_effective_daily_wage() == 0, &"permanent_removal_forbidden": true, &"passive_effects": passive_effects, } # Only Anri's explicitly specified effect is presented here. Neither kind # heroine nor a zero wage grants this effect to another employee. if not _is_anri_manager(staff): return contract var effects: Dictionary = definition.get(&"mechanical_effects", {}) if not effects.has(&"assigned_floor_revenue_multiplier_bp"): return contract var plan := staff_service.get_work_assignment(staff.staff_id) var facility_id := StringName(plan.get(&"facility_id", &"")) var facility := facility_service.get_instance(facility_id) var effect := { &"effect_id": &"assigned_floor_revenue_multiplier_bp", &"display_name": "看板娘の微笑", &"multiplier_bp": int(effects[&"assigned_floor_revenue_multiplier_bp"]), &"scope": &"ASSIGNED_FLOOR_ALL_LAYERS", &"applied": facility != null, &"assigned_facility_id": facility_id, } if facility != null: effect[&"floor_y"] = facility.origin.y passive_effects.append(effect) return contract func get_hired_staff_candidate_ids() -> Array[StringName]: return _sorted_name_keys(_hired_staff_candidate_ids) func get_live_progression_report() -> Dictionary: # Project current cash/facility facts without changing saved progression. var projected := ProgressionService.new() var source := progression_service.create_snapshot() if not projected.restore_snapshot(source): return {&"snapshot":source, &"evaluation":{&"eligible":false,&"requirements":[],&"reason":&"UNAVAILABLE"}} var synchronized := projected.update_metrics({ &"asset_gold":economy_service.get_balance(PLAYER_CASH_ACCOUNT), &"active_above_ground_facilities":_count_active_facilities(facility_service,false), &"active_underground_facilities":_count_active_facilities(facility_service,true), }) var evaluation := projected.evaluate_rank() if not synchronized: evaluation[&"eligible"] = false evaluation[&"reason"] = &"UNAVAILABLE" var gate := _operations_construction_investment_gate(0) if gate != null: evaluation[&"eligible"] = false evaluation[&"reason"] = &"OPERATIONS_BLOCKED" evaluation[&"block_reason"] = gate.details.get(&"reason", &"") return {&"snapshot":projected.create_snapshot(), &"evaluation":evaluation} func get_progression_report(target_rank := -1) -> Dictionary: return { &"snapshot": progression_service.create_snapshot(), &"evaluation": progression_service.evaluate_rank(target_rank).duplicate(true), } func _saved_content_manifest_matches(catalog: MvpContentCatalog, data: Dictionary) -> bool: var expected := _create_mvp_content_manifest(catalog) var saved: Variant = data.get(&"catalog_manifest", null) if _strict_variants_equal(expected, saved): return true # The sole v1-to-v2 catalog addition is the chapel cleric. Require the # complete old manifest, not an arbitrary subset of current content. expected[&"manifest_version"] = 1 expected[&"staff_candidate_ids"].erase("staff_uma") if not _strict_variants_equal(expected, saved): return false var hired: Variant = data.get(&"hired_staff_candidate_ids", null) var staff_section: Variant = data.get(&"staff", null) if not hired is Array or "staff_uma" in hired or not staff_section is Dictionary: return false var staff_rows: Variant = staff_section.get(&"staff", null) if not staff_rows is Array: return false for staff: Variant in staff_rows: if not staff is Dictionary or String(staff.get(&"staff_id", "")) == "staff_uma": return false return true func _clear_mvp_content_manifest_cache() -> void: _mvp_manifest_cache_catalog = null _mvp_manifest_cache_generation = -1 _mvp_manifest_cache = {} func _create_mvp_content_manifest(catalog: MvpContentCatalog) -> Dictionary: # Restore validates a temporary catalog through the full original builder. # A rejected restore must not replace or invalidate the live Runtime cache. if catalog != mvp_content_catalog: return _build_mvp_content_manifest(catalog) var generation := catalog.get_manifest_generation() if _mvp_manifest_cache_catalog == catalog \ and _mvp_manifest_cache_generation == generation: return _mvp_manifest_cache.duplicate(true) var manifest := _build_mvp_content_manifest(catalog) _mvp_manifest_cache_catalog = catalog _mvp_manifest_cache_generation = generation _mvp_manifest_cache = manifest # Legacy v1 matching edits its returned manifest, including a nested array. # The private template must stay owned even on the first cache fill. return _mvp_manifest_cache.duplicate(true) func _build_mvp_content_manifest(catalog: MvpContentCatalog) -> Dictionary: var guest_definition_ids: Array[String] = [] for definition: GuestDefinition in catalog.build_all_guest_definitions(): guest_definition_ids.append(String(definition.guest_type_id)) return { &"manifest_version": MVP_CONTENT_MANIFEST_VERSION, &"schema_version": MvpContentCatalog.SCHEMA_VERSION, &"guest_class_ids": _names_to_strings_for_manifest(catalog.get_guest_class_ids()), &"species_ids": _names_to_strings_for_manifest(catalog.get_species_ids()), &"staff_candidate_ids": _names_to_strings_for_manifest(catalog.get_staff_candidate_ids()), &"unique_character_ids": _names_to_strings_for_manifest(catalog.get_unique_character_ids()), &"guest_definition_ids": guest_definition_ids, } func _names_to_strings_for_manifest(values: Array[StringName]) -> Array[String]: var result: Array[String] = [] for value: StringName in values: result.append(String(value)) return result func _catalog_definitions_are_registered(service: GuestService) -> bool: if not bool(mvp_content_report.get(&"valid", false)): return false return _saved_catalog_guest_definitions_match( service.create_snapshot(), mvp_content_catalog ) func _saved_catalog_guest_definitions_match( guest_snapshot: Dictionary, catalog: MvpContentCatalog ) -> bool: var raw_definitions: Variant = guest_snapshot.get(&"definitions", null) if not raw_definitions is Array: return false var saved_by_id: Dictionary = {} for raw_definition: Variant in raw_definitions: if not raw_definition is Dictionary: return false var raw_id: Variant = raw_definition.get(&"guest_type_id", null) if typeof(raw_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return false var definition_id := StringName(raw_id) if definition_id.is_empty() or saved_by_id.has(definition_id): return false saved_by_id[definition_id] = raw_definition var expected_definitions := catalog.build_all_guest_definitions() if expected_definitions.size() != 32: return false for definition: GuestDefinition in expected_definitions: if not saved_by_id.has(definition.guest_type_id) \ or not _strict_variants_equal( definition.create_snapshot(), saved_by_id[definition.guest_type_id] ): return false return true func _register_or_validate_legacy_catalog_definitions( service: GuestService, catalog: MvpContentCatalog ) -> bool: var definitions := catalog.build_all_guest_definitions() if definitions.size() != 32: return false for definition: GuestDefinition in definitions: var existing := service.get_definition(definition.guest_type_id) if existing == null: if not service.register_definition(definition): return false elif not _strict_variants_equal( existing.create_snapshot(), definition.create_snapshot() ): return false return true func _serialize_hired_staff_candidate_ids() -> Array[String]: var result: Array[String] = [] for candidate_id: StringName in _sorted_name_keys(_hired_staff_candidate_ids): result.append(String(candidate_id)) return result func _is_mvp_staff_candidate_id(staff_id: StringName) -> bool: return mvp_content_catalog.get_staff_candidate_ids().has(staff_id) func _deserialize_hired_staff_candidate_ids( raw_ids: Array, catalog: MvpContentCatalog ) -> Variant: var allowed: Dictionary = {} for candidate_id: StringName in catalog.get_recruitable_staff_ids(): allowed[candidate_id] = true var result: Dictionary = {} var previous := "" for raw_id: Variant in raw_ids: if typeof(raw_id) not in [TYPE_STRING, TYPE_STRING_NAME]: return null var candidate_id := StringName(raw_id) var candidate_text := String(candidate_id) if not allowed.has(candidate_id) or result.has(candidate_id) \ or (not previous.is_empty() and candidate_text <= previous): return null result[candidate_id] = true previous = candidate_text return result func _validate_catalog_staff_crosslinks( staff: StaffService, hired_ids: Dictionary, catalog: MvpContentCatalog ) -> bool: if hired_ids.has(&"heroine_anri") and not staff.has_staff(&"heroine_anri"): return false var candidate_ids: Dictionary = {} for candidate_id: StringName in catalog.get_recruitable_staff_ids(): candidate_ids[candidate_id] = true for member: StaffRuntime in staff.get_staff_members(): if not candidate_ids.has(member.staff_id): continue if not hired_ids.has(member.staff_id): return false var canonical := catalog.staff_hire_payload(member.staff_id) if canonical.is_empty() \ or member.role_id != StringName(canonical[&"role_id"]) \ or member.daily_wage != int(canonical[&"daily_wage"]) \ or member.get_employment_kind_name() != StringName(canonical[&"options"][&"employment_kind"]) \ or not _strict_variants_equal( member.permitted_task_tags, (canonical[&"options"] as Dictionary)[&"permitted_task_tags"] ) \ or not _strict_variants_equal( member.skills, (canonical[&"options"] as Dictionary)[&"skills"] ): return false return true func _derive_legacy_hired_staff_candidate_ids( staff: StaffService, staff_snapshot: Dictionary, catalog: MvpContentCatalog ) -> Dictionary: var allowed: Dictionary = {} for candidate_id: StringName in catalog.get_recruitable_staff_ids(): allowed[candidate_id] = true var result: Dictionary = {} for member: StaffRuntime in staff.get_staff_members(): if allowed.has(member.staff_id): result[member.staff_id] = true var raw_departed: Variant = staff_snapshot.get(&"departed_payroll", []) if raw_departed is Array: for raw_record: Variant in raw_departed: if raw_record is Dictionary: var candidate_id := StringName(raw_record.get(&"staff_id", "")) if allowed.has(candidate_id): result[candidate_id] = true return result func _strict_variants_equal(expected: Variant, actual: Variant) -> bool: if typeof(expected) != typeof(actual): return false if expected is Dictionary: var expected_dictionary := expected as Dictionary var actual_dictionary := actual as Dictionary if expected_dictionary.size() != actual_dictionary.size(): return false var unmatched_actual_keys: Array = actual_dictionary.keys() for expected_key: Variant in expected_dictionary: var matched_index := -1 for index in range(unmatched_actual_keys.size()): var actual_key: Variant = unmatched_actual_keys[index] if typeof(actual_key) == typeof(expected_key) and actual_key == expected_key: matched_index = index break if matched_index < 0: return false var matched_key: Variant = unmatched_actual_keys[matched_index] if not _strict_variants_equal( expected_dictionary[expected_key], actual_dictionary[matched_key] ): return false unmatched_actual_keys.remove_at(matched_index) return unmatched_actual_keys.is_empty() if expected is Array: var expected_array := expected as Array var actual_array := actual as Array if expected_array.size() != actual_array.size(): return false for index in range(expected_array.size()): if not _strict_variants_equal(expected_array[index], actual_array[index]): return false return true return expected == actual func _operations_snapshot_comparison_value(value: Variant) -> Variant: if typeof(value) == TYPE_STRING_NAME: return String(value) if value is Array: var normalized_array: Array = [] for member: Variant in value: normalized_array.append(_operations_snapshot_comparison_value(member)) return normalized_array if value is Dictionary: var normalized_dictionary: Dictionary = {} for raw_key: Variant in value: var normalized_key: Variant = raw_key if typeof(raw_key) == TYPE_STRING_NAME: normalized_key = String(raw_key) if normalized_dictionary.has(normalized_key): # A String/StringName alias collision is not a canonical snapshot. return null normalized_dictionary[normalized_key] = _operations_snapshot_comparison_value( value[raw_key] ) return normalized_dictionary return value func get_actor_movement_credit(actor_id: StringName) -> Dictionary: if not _movement_credits.has(actor_id): return {} return (_movement_credits[actor_id] as Dictionary).duplicate(true) func _guest_path_actor_id(guest_id: StringName) -> StringName: return StringName("guest:%s" % guest_id) func _staff_path_actor_id(staff_id: StringName) -> StringName: return StringName("staff:%s" % staff_id) func _expected_runtime_actor_cells( guests: GuestService, staff: StaffService ) -> Dictionary: var expected: Dictionary = {} for guest: GuestRuntime in guests.get_guests(): var guest_actor_id := _guest_path_actor_id(guest.guest_id) if guest_actor_id.is_empty() or expected.has(guest_actor_id): return {} expected[guest_actor_id] = guest.current_cell for member: StaffRuntime in staff.get_staff_members(): var staff_actor_id := _staff_path_actor_id(member.staff_id) if staff_actor_id.is_empty() or expected.has(staff_actor_id): return {} expected[staff_actor_id] = member.current_cell return expected func _serialize_movement_credits() -> Array[Dictionary]: var saved: Array[Dictionary] = [] for actor_id: StringName in _sorted_name_keys(_movement_credits): var entry: Dictionary = _movement_credits[actor_id] saved.append({ &"actor_id": String(actor_id), &"credit": int(entry[&"credit"]), &"updated_at": int(entry[&"updated_at"]), }) return saved func _deserialize_movement_credits(raw_entries: Array, absolute_minute: int) -> Variant: var restored: Dictionary = {} for raw_entry: Variant in raw_entries: if not raw_entry is Dictionary: return null var actor_id_value: Variant = raw_entry.get(&"actor_id", null) var credit_value: Variant = raw_entry.get(&"credit", null) var updated_at_value: Variant = raw_entry.get(&"updated_at", null) if not (actor_id_value is String or actor_id_value is StringName) \ or not credit_value is int or not updated_at_value is int: return null var actor_id := StringName(actor_id_value) var credit := int(credit_value) var updated_at := int(updated_at_value) if actor_id.is_empty() or restored.has(actor_id) \ or credit < 0 or credit >= STAIR_MOVEMENT_CREDIT \ or updated_at < 0 or updated_at > absolute_minute: return null restored[actor_id] = { &"credit": credit, &"updated_at": updated_at, } return restored func _rebuild_legacy_path_actor_projection( path: PathService, guests: GuestService, staff: StaffService, absolute_minute: int ) -> Variant: var expected := _expected_runtime_actor_cells(guests, staff) var entries: Array[Dictionary] = [] var progress_entries: Array[Dictionary] = [] var expected_density: Dictionary = {} var rebuilt: Dictionary = {} for actor_id: StringName in _sorted_name_keys(expected): var cell: Vector3i = expected[actor_id] if not path.has_cell(cell): return null entries.append({&"actor_id": actor_id, &"cell": cell}) progress_entries.append({ &"actor_id": actor_id, &"last_cell": cell, &"last_progress_at": absolute_minute, &"last_observed_at": absolute_minute, &"stuck_reported_at": -1, }) expected_density[cell] = int(expected_density.get(cell, 0)) + 1 rebuilt[actor_id] = {&"credit": 0, &"updated_at": absolute_minute} var migrated_path := path.create_snapshot() migrated_path[&"actor_cells"] = entries migrated_path[&"actor_progress"] = progress_entries migrated_path[&"traffic_revision"] = int(migrated_path[&"traffic_revision"]) + 1 for traffic: Dictionary in migrated_path[&"cell_traffic"]: traffic[&"current_density"] = int(expected_density.get(traffic[&"cell"], 0)) if not path.restore_snapshot(migrated_path): return null return rebuilt func _validate_runtime_path_crosslinks( path: PathService, guests: GuestService, staff: StaffService, credits: Dictionary, absolute_minute: int ) -> bool: if path == null or guests == null or staff == null or absolute_minute < 0: return false var expected := _expected_runtime_actor_cells(guests, staff) if expected.size() != guests.guest_count() + staff.get_staff_members().size() \ or credits.size() != expected.size(): return false var path_snapshot := path.create_snapshot() var raw_actor_cells: Variant = path_snapshot.get(&"actor_cells", null) var raw_progress: Variant = path_snapshot.get(&"actor_progress", null) var raw_traffic: Variant = path_snapshot.get(&"cell_traffic", null) if not raw_actor_cells is Array or not raw_progress is Array or not raw_traffic is Array: return false var projected: Dictionary = {} for raw_actor: Variant in raw_actor_cells: if not raw_actor is Dictionary: return false var actor_id := StringName(raw_actor.get(&"actor_id", &"")) var cell: Variant = raw_actor.get(&"cell", null) if actor_id.is_empty() or not cell is Vector3i or projected.has(actor_id): return false projected[actor_id] = cell if projected.size() != expected.size(): return false var progress_by_actor: Dictionary = {} for raw_entry: Variant in raw_progress: if not raw_entry is Dictionary: return false var actor_id := StringName(raw_entry.get(&"actor_id", &"")) if actor_id.is_empty() or progress_by_actor.has(actor_id): return false progress_by_actor[actor_id] = raw_entry if progress_by_actor.size() != expected.size(): return false var expected_density: Dictionary = {} for actor_id: StringName in _sorted_name_keys(expected): var cell: Vector3i = expected[actor_id] if projected.get(actor_id, null) != cell or not credits.has(actor_id): return false var credit_entry: Variant = credits[actor_id] var progress_entry: Variant = progress_by_actor.get(actor_id, null) if not credit_entry is Dictionary or not progress_entry is Dictionary: return false var credit_value: Variant = credit_entry.get(&"credit", null) var credit_updated_value: Variant = credit_entry.get(&"updated_at", null) var last_progress_value: Variant = progress_entry.get(&"last_progress_at", null) var last_observed_value: Variant = progress_entry.get(&"last_observed_at", null) var stuck_reported_value: Variant = progress_entry.get(&"stuck_reported_at", null) if not credit_value is int or not credit_updated_value is int \ or not last_progress_value is int or not last_observed_value is int \ or not stuck_reported_value is int: return false var credit := int(credit_value) var updated_at := int(credit_updated_value) var last_progress_at := int(last_progress_value) var last_observed_at := int(last_observed_value) var stuck_reported_at := int(stuck_reported_value) if credit < 0 or credit >= _cell_movement_credit_limit(path, cell) \ or updated_at < 0 or updated_at > absolute_minute \ or progress_entry.get(&"last_cell", null) != cell \ or last_progress_at < 0 or last_progress_at > last_observed_at \ or last_observed_at != updated_at \ or last_observed_at > absolute_minute \ or (stuck_reported_at != -1 and ( stuck_reported_at < last_progress_at or stuck_reported_at > last_observed_at )): return false if not path.is_actor_in_transit(actor_id): expected_density[cell] = int(expected_density.get(cell, 0)) + 1 for raw_cell: Variant in raw_traffic: if not raw_cell is Dictionary: return false var cell: Variant = raw_cell.get(&"cell", null) var density_value: Variant = raw_cell.get(&"current_density", null) if not cell is Vector3i or not density_value is int \ or int(density_value) != int(expected_density.get(cell, 0)): return false return true func _path_snapshot_cells_match_world( path_snapshot: Dictionary, walkable_cells: Array[Vector3i] ) -> bool: var schema_value: Variant = path_snapshot.get( &"schema_version", path_snapshot.get("schema_version", null) ) if not schema_value is int or int(schema_value) not in [PathService.ELEVATOR_INDEX_SNAPSHOT_VERSION, PathService.SHAFT_SNAPSHOT_VERSION, PathService.SNAPSHOT_VERSION]: return false var raw_traffic: Variant = path_snapshot.get( &"cell_traffic", path_snapshot.get("cell_traffic", null) ) if not raw_traffic is Array or raw_traffic.size() != walkable_cells.size(): return false var expected_cells: Dictionary = {} for cell: Vector3i in walkable_cells: if expected_cells.has(cell): return false expected_cells[cell] = true var actual_cells: Dictionary = {} for raw_entry: Variant in raw_traffic: if not raw_entry is Dictionary: return false var cell_value: Variant = raw_entry.get(&"cell", raw_entry.get("cell", null)) if not cell_value is Vector3i \ or actual_cells.has(cell_value) \ or not expected_cells.has(cell_value): return false actual_cells[cell_value] = true return actual_cells.size() == expected_cells.size() func _serialize_logistics_requests() -> Array[Dictionary]: var saved: Array[Dictionary] = [] for request_id: StringName in _sorted_name_keys(logistics_requests): saved.append((logistics_requests[request_id] as LogisticsRequest).create_snapshot()) return saved func _derive_legacy_logistics_ownership( dispatcher: LogisticsDispatcher, requests: Dictionary, absolute_minute: int ) -> Dictionary: var latest_history: Dictionary = {} for assignment: Dictionary in dispatcher.get_assignment_history(): var request_id := StringName(assignment.get(&"request_id", &"")) var assigned_at := int(assignment.get(&"assigned_at", -1)) if request_id.is_empty() or assigned_at < 0 or assigned_at > absolute_minute: return {} latest_history[request_id] = assignment var expected: Dictionary = {} for request_id: StringName in _sorted_name_keys(requests): var request: LogisticsRequest = requests[request_id] if request.state not in [ LogisticsRequest.State.ASSIGNED, LogisticsRequest.State.PICKING_UP, LogisticsRequest.State.DELIVERING, ]: continue if request.assigned_carrier_id.is_empty() or request.total_weight <= 0 \ or not latest_history.has(request_id): return {} var latest: Dictionary = latest_history[request_id] if StringName(latest.get(&"carrier_id", &"")) != request.assigned_carrier_id \ or int(latest.get(&"weight", -1)) != request.total_weight: return {} expected[request_id] = { &"carrier_id": request.assigned_carrier_id, &"weight": request.total_weight, &"assigned_at": int(latest[&"assigned_at"]), } return expected func _validate_logistics_dispatcher_crosslinks( dispatcher: LogisticsDispatcher, requests: Dictionary, absolute_minute: int ) -> bool: if dispatcher == null or absolute_minute < 0: return false var ownership := dispatcher.get_assignment_ownership() var expected_ids: Dictionary = {} for request_id: StringName in _sorted_name_keys(requests): var request: LogisticsRequest = requests[request_id] if request.created_at < 0 or request.state_changed_at < request.created_at \ or request.state_changed_at > absolute_minute: return false var active := request.state in [ LogisticsRequest.State.ASSIGNED, LogisticsRequest.State.PICKING_UP, LogisticsRequest.State.DELIVERING, ] if not active: if not request.assigned_carrier_id.is_empty(): return false continue if request.assigned_carrier_id.is_empty() or request.total_weight <= 0: return false expected_ids[request_id] = true var entry: Variant = ownership.get(request_id, null) if not entry is Dictionary \ or StringName(entry.get(&"carrier_id", &"")) != request.assigned_carrier_id \ or int(entry.get(&"weight", -1)) != request.total_weight: return false var assigned_at := int(entry.get(&"assigned_at", -1)) if assigned_at < request.created_at or assigned_at > request.state_changed_at: return false if ownership.size() != expected_ids.size(): return false for request_id: StringName in _sorted_name_keys(ownership): if not expected_ids.has(request_id): return false var latest_history: Dictionary = {} var history_fingerprints: Dictionary = {} for assignment: Dictionary in dispatcher.get_assignment_history(): var request_id := StringName(assignment.get(&"request_id", &"")) if request_id.is_empty() or not requests.has(request_id): return false var request: LogisticsRequest = requests[request_id] var carrier_id := StringName(assignment.get(&"carrier_id", &"")) var weight := int(assignment.get(&"weight", -1)) var assigned_at := int(assignment.get(&"assigned_at", -1)) var fingerprint := "%s|%s|%d|%d" % [request_id, carrier_id, weight, assigned_at] if carrier_id.is_empty() or weight != request.total_weight \ or assigned_at < request.created_at or assigned_at > absolute_minute \ or history_fingerprints.has(fingerprint): return false history_fingerprints[fingerprint] = true latest_history[request_id] = assignment for request_id: StringName in _sorted_name_keys(ownership): if not latest_history.has(request_id): return false var entry: Dictionary = ownership[request_id] var latest: Dictionary = latest_history[request_id] if StringName(latest[&"carrier_id"]) != StringName(entry[&"carrier_id"]) \ or int(latest[&"weight"]) != int(entry[&"weight"]) \ or int(latest[&"assigned_at"]) != int(entry[&"assigned_at"]): return false return true func _serialize_event_definitions() -> Array[Dictionary]: var saved: Array[Dictionary] = [] for definition: EventDefinition in event_service.get_definitions(): saved.append(definition.create_snapshot()) return saved func _deserialize_logistics_requests(raw_requests: Array) -> Variant: var restored: Dictionary = {} for raw_request: Variant in raw_requests: if not raw_request is Dictionary: return null var request := LogisticsRequest.from_snapshot(raw_request) if request == null or restored.has(request.request_id): return null restored[request.request_id] = request return restored func _sorted_logistics_requests() -> Array: var requests: Array = [] for request_id: StringName in _sorted_name_keys(logistics_requests): requests.append(logistics_requests[request_id]) return requests func _sorted_name_keys(source: Dictionary) -> Array[StringName]: var keys: Array[StringName] = [] for raw_key: Variant in source: keys.append(StringName(raw_key)) keys.sort_custom(func(first: StringName, second: StringName) -> bool: return String(first) < String(second) ) return keys func _name_array(source: Variant) -> Array[StringName]: var result: Array[StringName] = [] if not source is Array: return result for value: Variant in source: result.append(StringName(value)) return result func _guest_wallet_account(guest_id: StringName) -> StringName: return StringName("GUEST_WALLET:%s" % guest_id) func _outstanding_service_commitment(guest_id: StringName) -> int: var total := 0 var counted_requests: Dictionary = {} for facility: FacilityInstance in facility_service.get_instances(): for request: ServiceRequest in facility_service.get_queue(facility.instance_id): if not request.guest_ids.is_empty() and request.guest_ids[0] == guest_id: total += request.quoted_price counted_requests[request.request_id] = true for service: ServiceInstance in facility_service.get_active_services(facility.instance_id): if counted_requests.has(service.request_id): continue var request := facility_service.get_request(service.request_id) if request != null and not request.guest_ids.is_empty() \ and request.guest_ids[0] == guest_id: total += request.quoted_price counted_requests[request.request_id] = true return total func _facility_revenue_account(facility_id: StringName) -> StringName: return StringName("FACILITY_REVENUE:%s" % facility_id) func _command_result_from_dictionary( command_id: int, result: Dictionary, fallback_message: String ) -> CommandResult: if bool(result.get(&"ok", result.get("ok", false))): return CommandResult.succeeded(command_id, result.duplicate(true)) return CommandResult.failed( CommandResult.Code.VALIDATION_FAILED, command_id, String(result.get(&"message", result.get("message", fallback_message))), result.duplicate(true) ) func _register_facility_mana_consumer(instance: Dictionary) -> void: var instance_id := StringName(instance.get(&"instance_id", &"")) var definition_id := StringName(instance.get(&"definition_id", &"")) var definition := definition_registry.get_facility(definition_id) if instance_id.is_empty() or definition == null or definition.maintenance_mana <= 0: return var priority := ManaNetworkService.ConsumerPriority.NORMAL if &"entrance" in definition.tags or &"medical" in definition.tags: priority = ManaNetworkService.ConsumerPriority.ESSENTIAL if mana_network_service.register_consumer(instance_id, definition.maintenance_mana, priority): mana_network_service.connect_nodes(instance_id, STARTER_MANA_BUS) func _register_operational_facility(construction_instance: Dictionary) -> void: var instance_id := StringName(construction_instance.get(&"instance_id", &"")) var definition_id := StringName(construction_instance.get(&"definition_id", &"")) var definition := definition_registry.get_facility(definition_id) if instance_id.is_empty() or definition == null or facility_service.has_instance(instance_id): return economy_service.register_account(_facility_revenue_account(instance_id)) facility_service.create_instance({ &"instance_id": instance_id, &"definition_id": definition_id, &"origin": construction_instance.get(&"origin", Vector3i.ZERO), &"rotation": construction_instance.get(&"rotation", 0), &"occupied_cells": construction_instance.get(&"occupied_cells", []), &"local_inventory_id": StringName("facility.inventory:%s" % instance_id), &"power_available": int(_state.get_resource(&"mana")) > 0, &"customer_capacity": definition.customer_capacity, &"service_attribute": definition.service_attribute, &"capacity": maxi(definition.capacity, 1), &"queue_capacity": definition.queue_capacity, &"base_service_minutes": definition.base_service_minutes, &"base_quality": definition.base_quality, &"base_price": definition.base_price, &"staff_min": definition.staff_min, &"staff_max": definition.staff_max, &"created_at": int(construction_instance.get(&"completed_at", _clock.absolute_minute)), }) func _validate_service_start(request: ServiceRequest, _facility: FacilityInstance, absolute_minute: int) -> Dictionary: if int(_state.get_resource(&"mana")) == 0: return {&"ok": false, &"message": "The tower has no stored Mana."} for guest_id: StringName in request.guest_ids: var guest := guest_service.get_guest(guest_id) if guest == null: continue if guest.state != GuestRuntime.State.QUEUING \ or guest.queue_request_id != request.request_id \ or guest.target_facility_id != request.facility_id: return {&"ok": false, &"message": "Guest and Facility queue ownership do not match."} if guest.transition_tick == absolute_minute \ and guest.transitions_this_tick >= GuestService.MAX_TRANSITIONS_PER_TICK: return {&"ok": false, &"message": "Guest transition budget is exhausted for this minute."} for reservation_id: StringName in request.reserved_inventory_ids: var reservation := inventory_service.get_reservation(reservation_id) if reservation == null or not reservation.is_active() or reservation.is_expired(absolute_minute) \ or inventory_service.is_stack_expired(reservation.stack_id, absolute_minute): return {&"ok": false, &"message": "A required inventory reservation is unavailable or expired."} var payer_account := _guest_wallet_account(request.guest_ids[0]) if not economy_service.has_account(payer_account) \ or economy_service.get_balance(payer_account) < request.quoted_price: return {&"ok": false, &"message": "The guest cannot pay the quoted service price."} return {&"ok": true} func _complete_service_transaction( service: ServiceInstance, request: ServiceRequest, facility: FacilityInstance, absolute_minute: int ) -> Variant: # A sale is only observable after its inventory, rewards and cleanliness # effects commit. Rollback removes provisional receipts before scope exit. economy_service.begin_notification_scope() var result: Variant = _apply_service_transaction(service, request, facility, absolute_minute) if result is bool and result: _state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT)) economy_service.end_notification_scope() return result func _apply_service_transaction( service: ServiceInstance, request: ServiceRequest, facility: FacilityInstance, absolute_minute: int ) -> Variant: for guest_id: StringName in service.guest_ids: var guest := guest_service.get_guest(guest_id) if guest == null: continue if guest.state != GuestRuntime.State.RECEIVING_SERVICE \ or guest.current_service_id != service.service_id \ or guest.target_facility_id != facility.instance_id: return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, 0, "Guest and Facility service ownership do not match." ) if guest.transition_tick == absolute_minute \ and guest.transitions_this_tick >= GuestService.MAX_TRANSITIONS_PER_TICK: return CommandResult.failed( CommandResult.Code.TRANSACTION_APPLY_FAILED, 0, "Guest transition budget is exhausted for this minute." ) var inventory_before := inventory_service.create_snapshot() var economy_before := economy_service.create_snapshot() var state_before: Dictionary = _state.create_snapshot() if not service.inventory_reservations.is_empty(): var consumed := inventory_service.consume_reservations( service.inventory_reservations, StringName("service.consume.%s" % service.service_id), absolute_minute ) if not bool(consumed.get(&"ok", false)): return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, 0, "Reserved service inventory could not be consumed.", consumed) if service.price_snapshot > 0: var payer_account := _guest_wallet_account(request.guest_ids[0]) var revenue_account := _facility_revenue_account(facility.instance_id) if not economy_service.has_account(payer_account) or not economy_service.has_account(revenue_account): inventory_service.restore_snapshot(inventory_before) return CommandResult.failed(CommandResult.Code.NOT_FOUND, 0, "A service settlement account is missing.") var sale_entries: Array = [ LedgerEntry.new().configure( &"", absolute_minute, revenue_account, payer_account, service.price_snapshot, &"SERVICE_SALE", facility.instance_id ), LedgerEntry.new().configure( &"", absolute_minute, PLAYER_CASH_ACCOUNT, revenue_account, service.price_snapshot, &"SERVICE_CASH_SWEEP", facility.instance_id ), ] var payment := economy_service.post( sale_entries, StringName("service.payment.%s" % service.service_id) ) if not payment.is_success(): inventory_service.restore_snapshot(inventory_before) economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) return payment var definition := definition_registry.get_facility(facility.definition_id) if definition != null: for item_id: StringName in _sorted_name_keys(definition.inventory_outputs): var output := inventory_service.add_stack( facility.local_inventory_id, item_id, int(definition.inventory_outputs[item_id]), service.quality_snapshot, -1, absolute_minute ) if not bool(output.get(&"ok", false)): inventory_service.restore_snapshot(inventory_before) economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, 0, "Facility outputs could not be committed.", output) var progression_before: Dictionary = {} if definition != null and definition.faith_per_service > 0: progression_before = progression_service.create_snapshot() var next_faith := int(progression_service.metrics[&"faith_lifetime"]) + definition.faith_per_service if not progression_service.update_metrics({&"faith_lifetime":next_faith}) or not _state.apply_resource_deltas({&"faith":definition.faith_per_service}): inventory_service.restore_snapshot(inventory_before) economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) progression_service.restore_snapshot(progression_before) return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, 0, "Service Faith reward could not be committed.") if definition != null and definition.cleanliness_decay_per_service > 0: if not facility_cleanliness.record_use(facility.instance_id, service.service_id, definition.cleanliness_decay_per_service): inventory_service.restore_snapshot(inventory_before) economy_service.restore_snapshot(economy_before) _state.restore_snapshot(state_before) if not progression_before.is_empty(): progression_service.restore_snapshot(progression_before) return CommandResult.failed(CommandResult.Code.TRANSACTION_APPLY_FAILED, 0, "Facility cleanliness could not be committed.") return true func _on_facility_state_changed(instance_id: StringName, old_state: int, new_state: int, reasons: Array[StringName]) -> void: _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"FACILITY_STATE_CHANGED", _runtime_event_minute(), &"facility_service", [instance_id], {&"old_state": old_state, &"state": new_state, &"reasons": reasons}, 0 )) func _on_service_started(service: ServiceInstance) -> void: var effective_minute := _runtime_event_minute() for guest_id: StringName in service.guest_ids: var guest := guest_service.get_guest(guest_id) if guest == null: continue var wallet_account := _guest_wallet_account(guest_id) if economy_service.has_account(wallet_account): guest.wallet_gold = economy_service.get_balance(wallet_account) var synchronized := guest_service.begin_service( guest_id, service.facility_id, service.service_id, effective_minute ) if not bool(synchronized.get(&"ok", false)): _publish_actor_event( &"GUEST_SERVICE_SYNC_FAILED", &"simulation_runtime", [guest_id, service.service_id], synchronized ) _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"SERVICE_STARTED", effective_minute, &"facility_service", [service.facility_id, service.service_id], service.create_snapshot(), 0 )) func _on_service_interrupted(service: ServiceInstance, reason: StringName) -> void: _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"SERVICE_INTERRUPTED", _runtime_event_minute(), &"facility_service", [service.facility_id, service.service_id], {&"reason": reason, &"service": service.create_snapshot()}, 0 )) func _on_service_completed(service: ServiceInstance) -> void: var effective_minute := _runtime_event_minute() for guest_id: StringName in service.guest_ids: var guest := guest_service.get_guest(guest_id) if guest == null: continue var wallet_account := _guest_wallet_account(guest_id) if economy_service.has_account(wallet_account): guest.wallet_gold = economy_service.get_balance(wallet_account) var synchronized := guest_service.complete_service( guest_id, service.service_id, effective_minute, _service_satisfaction_sample(service, guest), _service_need_effects(service.facility_id) ) if not bool(synchronized.get(&"ok", false)): _publish_actor_event( &"GUEST_SERVICE_SYNC_FAILED", &"simulation_runtime", [guest_id, service.service_id], synchronized ) _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"SERVICE_COMPLETED", effective_minute, &"facility_service", [service.facility_id, service.service_id], service.create_snapshot(), 0 )) func _on_service_failed(service: ServiceInstance, reason: StringName) -> void: var effective_minute := _runtime_event_minute() for reservation_id: StringName in service.inventory_reservations: inventory_service.release_reservation( reservation_id, StringName("service.failed.%s.%s" % [service.service_id, reservation_id]), effective_minute ) for guest_id: StringName in service.guest_ids: var guest := guest_service.get_guest(guest_id) if guest != null and guest.current_service_id == service.service_id: guest_service.interrupt_service( guest_id, service.service_id, effective_minute ) _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), &"SERVICE_FAILED", effective_minute, &"facility_service", [service.facility_id, service.service_id], {&"reason": reason, &"service": service.create_snapshot()}, 0 )) func _on_guest_spawned(guest: GuestRuntime) -> void: if _operations_flushing_guest_spawn_notifications.has(guest.guest_id): return var actor_id := _guest_path_actor_id(guest.guest_id) var projected := path_service.actor_enter_cell( actor_id, guest.current_cell, _runtime_event_minute() ) if projected: _movement_credits[actor_id] = { &"credit": 0, &"updated_at": _runtime_event_minute(), } var payload := guest.create_snapshot() payload[&"path_actor_id"] = actor_id payload[&"path_projected"] = projected payload[&"path_error"] = path_service.last_error if not projected else "" _publish_actor_event(&"GUEST_SPAWNED", &"guest_service", [guest.guest_id], payload) func _on_guest_state_changed( guest_id: StringName, old_state: int, new_state: int, reason: StringName ) -> void: _publish_actor_event(&"GUEST_STATE_CHANGED", &"guest_service", [guest_id], { &"old_state": old_state, &"state": new_state, &"reason": reason, }) func _on_guest_queue_abandoned( guest_id: StringName, facility_id: StringName, request_id: StringName ) -> void: _publish_actor_event( &"GUEST_QUEUE_ABANDONED", &"guest_service", [guest_id, facility_id, request_id], {} ) func _on_guest_recovery_started( guest_id: StringName, recovery_kind: StringName, target_cell: Vector3i ) -> void: _publish_actor_event(&"GUEST_RECOVERY_STARTED", &"guest_service", [guest_id], { &"recovery_kind": recovery_kind, &"target_cell": target_cell, }) func _on_guest_despawned(guest_id: StringName, persistent_record: Dictionary) -> void: var actor_id := _guest_path_actor_id(guest_id) elevators.cancel_actor(actor_id) var path_removed := path_service.despawn_actor(actor_id) _movement_credits.erase(actor_id) _synchronize_operations_population() var payload := persistent_record.duplicate(true) payload[&"path_actor_id"] = actor_id payload[&"path_projection_removed"] = path_removed _publish_actor_event(&"GUEST_DESPAWNED", &"guest_service", [guest_id], payload) func _on_staff_registered(staff: StaffRuntime) -> void: _staffing_projection_enabled = true var actor_id := _staff_path_actor_id(staff.staff_id) var projected := path_service.actor_enter_cell( actor_id, staff.current_cell, _runtime_event_minute() ) if projected: _movement_credits[actor_id] = { &"credit": 0, &"updated_at": _runtime_event_minute(), } var payload := staff.create_snapshot() payload[&"path_actor_id"] = actor_id payload[&"path_projected"] = projected payload[&"path_error"] = path_service.last_error if not projected else "" _publish_actor_event(&"STAFF_REGISTERED", &"staff_service", [staff.staff_id], payload) func _on_staff_removed(staff_id: StringName, reason: StringName) -> void: var actor_id := _staff_path_actor_id(staff_id) elevators.cancel_actor(actor_id) var path_removed := path_service.despawn_actor(actor_id) _movement_credits.erase(actor_id) _publish_actor_event(&"STAFF_REMOVED", &"staff_service", [staff_id], { &"reason": reason, &"path_actor_id": actor_id, &"path_projection_removed": path_removed, }) func _on_shift_registered(shift: ShiftInstance) -> void: _publish_actor_event( &"STAFF_SHIFT_REGISTERED", &"staff_service", [shift.staff_id, shift.shift_instance_id], shift.create_snapshot() ) func _on_shift_state_changed(staff_id: StringName, shift_id: StringName, state: int) -> void: _publish_actor_event( &"STAFF_SHIFT_STATE_CHANGED", &"staff_service", [staff_id, shift_id], {&"state": state} ) func _on_staff_task_registered(task: StaffTask) -> void: _publish_actor_event(&"STAFF_TASK_REGISTERED", &"staff_service", [task.task_id], task.create_snapshot()) func _on_staff_task_assigned(task_id: StringName, staff_id: StringName) -> void: var task := staff_service.get_task(task_id) if task != null and task.state == StaffTask.State.RESERVED: task.begin_moving(_runtime_event_minute()) _publish_actor_event(&"STAFF_TASK_ASSIGNED", &"staff_service", [task_id, staff_id], {}) func _on_staff_task_reopened(task_id: StringName, reason: StringName) -> void: _publish_actor_event(&"STAFF_TASK_REOPENED", &"staff_service", [task_id], {&"reason": reason}) func _on_staff_task_completed(task_id: StringName, staff_id: StringName) -> void: var task := staff_service.get_task(task_id) if task != null and task.task_type == CleaningBridge.TASK_TYPE: if not cleaning_bridge.completed(task, staff_service.get_staff(staff_id)): _latch_infrastructure_failure(&"CLEANING_COMPLETION_FAILED", _runtime_event_minute(), "Cleaning task effect could not be committed.") if task != null and task.task_type == &"rescue_transport": var patient := staff_service.get_staff(task.target_entity_id) var clinic := facility_service.get_instance(task.rescue_facility_id) if patient != null and clinic != null and patient.current_cell == task.target_cell and clinic.operation_state == FacilityInstance.OperationState.OPERATING: patient.health = 100 patient.fatigue = 0 patient.transition_to(StaffRuntime.State.OFF_DUTY, &"RESCUE_TREATED", _runtime_event_minute()) _publish_actor_event(&"STAFF_RESCUED", &"staff_service", [patient.staff_id, staff_id, clinic.instance_id], {}) _publish_actor_event(&"STAFF_TASK_COMPLETED", &"staff_service", [task_id, staff_id], {}) func _on_authority_heat_changed( old_value: int, new_value: int, source_id: StringName ) -> void: var entity_ids: Array[StringName] = [] if not source_id.is_empty(): entity_ids.append(source_id) _publish_actor_event(&"AUTHORITY_HEAT_CHANGED", &"authority_service", entity_ids, { &"old_value": old_value, &"new_value": new_value, &"source_id": source_id, }) func _on_authority_incident_created(incident: AuthorityIncident) -> void: _publish_actor_event( &"INCIDENT_CREATED", &"authority_service", [incident.incident_id], incident.create_snapshot() ) func _on_authority_incident_detector_added( incident_id: StringName, detector_id: StringName ) -> void: var incident := authority_service.get_incident(incident_id) _publish_actor_event( &"INCIDENT_DETECTOR_ADDED", &"authority_service", [incident_id, detector_id], {&"incident": incident.create_snapshot() if incident != null else {}} ) func _on_authority_evidence_registered(evidence: AuthorityEvidence) -> void: _publish_actor_event( &"AUTHORITY_EVIDENCE_REGISTERED", &"authority_service", [evidence.evidence_id, evidence.incident_id, evidence.facility_id], evidence.create_snapshot() ) func _on_authority_evidence_discovered( evidence: AuthorityEvidence, method: StringName ) -> void: _publish_actor_event( &"AUTHORITY_EVIDENCE_DISCOVERED", &"authority_service", [evidence.evidence_id, evidence.incident_id, evidence.facility_id], { &"method": method, &"evidence": evidence.create_snapshot(), &"authority_evidence_points": authority_service.get_authority_state().evidence_points, } ) func _on_authority_inspection_scheduled(inspection: AuthorityInspection) -> void: _publish_actor_event( &"INSPECTION_SCHEDULED", &"authority_service", [inspection.inspection_id, inspection.inspector_id], inspection.create_snapshot() ) func _on_authority_inspection_state_changed( inspection_id: StringName, old_state: int, new_state: int, reason: StringName ) -> void: var inspection := authority_service.get_inspection(inspection_id) var entity_ids: Array[StringName] = [inspection_id] if inspection != null: entity_ids.append(inspection.inspector_id) var payload := { &"old_state": StringName(AuthorityInspection.State.keys()[old_state]), &"new_state": StringName(AuthorityInspection.State.keys()[new_state]), &"reason": reason, } _publish_actor_event( &"INSPECTION_STATE_CHANGED", &"authority_service", entity_ids, payload ) if new_state == AuthorityInspection.State.ARRIVING: _publish_actor_event( &"INSPECTION_STARTED", &"authority_service", entity_ids, payload ) func _on_authority_resolution_applied( inspection_id: StringName, resolution: Dictionary ) -> void: _publish_actor_event( &"INSPECTION_RESOLVED", &"authority_service", [inspection_id, StringName(resolution.get(&"resolution_id", ""))], resolution.duplicate(true) ) func _on_defense_raid_created(raid_id: StringName) -> void: _publish_actor_event( &"DEFENSE_RAID_CREATED", &"defense_service", [raid_id], defense_service.get_raid(raid_id) ) func _on_defense_raid_state_changed( raid_id: StringName, old_state: int, new_state: int, reason: StringName ) -> void: _publish_actor_event( &"DEFENSE_RAID_STATE_CHANGED", &"defense_service", [raid_id], { &"old_state": StringName(DefenseService.RaidState.keys()[old_state]), &"new_state": StringName(DefenseService.RaidState.keys()[new_state]), &"reason": reason, } ) func _on_defense_active_skill_applied( raid_id: StringName, action_id: StringName, round_number: int ) -> void: _publish_actor_event( &"DEFENSE_SKILL_APPLIED", &"defense_service", [raid_id, action_id], {&"round_number": round_number} ) func _on_defense_round_resolved(raid_id: StringName, resolution: Dictionary) -> void: var entity_ids: Array[StringName] = [raid_id] var resolution_id := StringName(resolution.get(&"resolution_id", "")) var wave_id := StringName(resolution.get(&"wave_id", "")) if not resolution_id.is_empty(): entity_ids.append(resolution_id) if not wave_id.is_empty(): entity_ids.append(wave_id) _publish_actor_event( &"DEFENSE_ROUND_RESOLVED", &"defense_service", entity_ids, resolution.duplicate(true) ) func _on_defense_raid_resolved(raid_id: StringName, outcome: Dictionary) -> void: var entity_ids: Array[StringName] = [raid_id] var resolution_id := StringName(outcome.get(&"resolution_id", "")) if not resolution_id.is_empty(): entity_ids.append(resolution_id) _publish_actor_event( &"DEFENSE_RAID_RESOLVED", &"defense_service", entity_ids, outcome.duplicate(true) ) func _publish_authority_evaluation(details: Dictionary) -> void: var entity_ids: Array[StringName] = [] var inspection_id := StringName(details.get(&"inspection_id", "")) if not inspection_id.is_empty(): entity_ids.append(inspection_id) _publish_actor_event( &"AUTHORITY_INSPECTION_EVALUATED", &"authority_service", entity_ids, details.duplicate(true) ) func _on_relationship_registered(state: RelationshipState) -> void: _publish_actor_event( &"RELATIONSHIP_REGISTERED", &"relationship_service", [state.actor_a_id, state.actor_b_id, state.relationship_id], state.create_snapshot() ) func _on_relationship_value_changed( relationship_id: StringName, metric_id: StringName, old_value: int, new_value: int ) -> void: var state := relationship_service.get_relationship(relationship_id) var actor_ids: Array[StringName] = [relationship_id] if state != null: actor_ids = [state.actor_a_id, state.actor_b_id, relationship_id] _publish_actor_event(&"RELATIONSHIP_VALUE_CHANGED", &"relationship_service", actor_ids, { &"metric_id": metric_id, &"old_value": old_value, &"new_value": new_value, }) func _on_relationship_shield_changed( relationship_id: StringName, old_value: int, new_value: int ) -> void: var state := relationship_service.get_relationship(relationship_id) var actor_ids: Array[StringName] = [relationship_id] if state != null: actor_ids = [state.actor_a_id, state.actor_b_id, relationship_id] _publish_actor_event(&"RELATIONSHIP_SHIELD_CHANGED", &"relationship_service", actor_ids, { &"old_value": old_value, &"new_value": new_value, }) func _on_relationship_route_lock_changed( relationship_id: StringName, old_lock: int, new_lock: int, route_id: StringName ) -> void: var state := relationship_service.get_relationship(relationship_id) var actor_ids: Array[StringName] = [relationship_id] if state != null: actor_ids = [state.actor_a_id, state.actor_b_id, relationship_id] _publish_actor_event(&"RELATIONSHIP_ROUTE_LOCK_CHANGED", &"relationship_service", actor_ids, { &"old_lock": old_lock, &"new_lock": new_lock, &"route_id": route_id, }) func _on_event_effect_transaction_started() -> void: economy_service.begin_notification_scope() func _on_event_effect_transaction_finished() -> void: # A nested action batch ends before the enclosing choice cost commits. # Keep the authoritative mirror current without releasing observer receipts. if economy_service.notification_scope_depth() == 1: _state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT)) economy_service.end_notification_scope() func _on_event_enqueued(instance: EventInstance) -> void: _publish_actor_event( &"EVENT_ENQUEUED", &"event_service", instance.actor_ids, instance.create_snapshot() ) func _on_event_started(instance: EventInstance) -> void: _publish_actor_event( &"EVENT_STARTED", &"event_service", instance.actor_ids, instance.create_snapshot() ) func _on_event_finished(instance: EventInstance) -> void: _resolve_relationship_threshold_event(instance) _publish_actor_event( &"EVENT_FINISHED", &"event_service", instance.actor_ids, instance.create_snapshot() ) func _on_event_cancelled(instance: EventInstance, reason: StringName) -> void: _consume_relationship_candidate_from_event(instance) # Placement rejection occurs before an Event starts and never owns a pause. if reason != &"PLACEMENT_PROTECTION" and event_service.get_foreground_instance() == null and not event_service.pause_requested: _clock.set_paused(false) _publish_actor_event(&"EVENT_CANCELLED", &"event_service", instance.actor_ids, { &"reason": reason, &"event": instance.create_snapshot(), }) func _on_event_choice_resolved( instance_id: StringName, choice_id: StringName, result: Dictionary ) -> void: var instance := event_service.get_instance(instance_id) var actor_ids: Array[StringName] = [] if instance != null: actor_ids = instance.actor_ids.duplicate() _publish_actor_event(&"EVENT_CHOICE_RESOLVED", &"event_service", actor_ids, { &"event_instance_id": instance_id, &"choice_id": choice_id, &"result": result.duplicate(true), }) func _resolve_relationship_threshold_event(instance: EventInstance) -> void: if instance.source_event_id != &"relationship.threshold": return var raw_candidate: Variant = instance.context.get(&"relationship_candidate", null) if not raw_candidate is Dictionary: return var candidate: Dictionary = raw_candidate if instance.selected_choice_id == &"acknowledge": var relationship_id := StringName(candidate.get(&"relationship_id", "")) var metric_id := StringName(candidate.get(&"metric_id", "")) var state := relationship_service.get_relationship(relationship_id) if state != null and RelationshipState.METRIC_IDS.has(metric_id): var threshold := int(candidate.get(&"threshold", state.get_metric(metric_id))) var delta := maxi(threshold - state.get_metric(metric_id), 0) if delta > 0: relationship_service.apply_value_change( relationship_id, metric_id, delta, _runtime_event_minute(), {&"important": true, &"allow_threshold_crossing": true} ) _consume_relationship_candidate_from_event(instance) func _consume_relationship_candidate_from_event(instance: EventInstance) -> void: var raw_candidate: Variant = instance.context.get(&"relationship_candidate", null) if not raw_candidate is Dictionary: return var candidate_id := StringName(raw_candidate.get(&"candidate_id", "")) if not candidate_id.is_empty(): relationship_service.consume_threshold_candidate(candidate_id) func _publish_actor_event( event_type: StringName, source_system: StringName, entity_ids: Array[StringName], payload: Dictionary ) -> void: _publish_domain_event(SimulationDomainEvent.new( id_generator.next_id(&"domain_event"), event_type, _runtime_event_minute(), source_system, entity_ids, payload, 0 )) func _service_need_effects(facility_id: StringName) -> Dictionary: var effects := { &"hunger": 0, &"sleep": 0, &"hygiene": 0, &"medical": 0, &"leisure": 0, &"intimacy": 0, } var facility := facility_service.get_instance(facility_id) if facility == null: return effects var definition := definition_registry.get_facility(facility.definition_id) if definition == null: return effects if &"dining" in definition.tags: effects[&"hunger"] = -70 effects[&"leisure"] = -10 if &"lodging" in definition.tags: effects[&"sleep"] = -80 effects[&"hygiene"] = mini(int(effects[&"hygiene"]), -10) if &"social" in definition.tags or &"leisure" in definition.tags: effects[&"leisure"] = mini(int(effects[&"leisure"]), -40) if &"hygiene" in definition.tags or &"bath" in definition.tags: effects[&"hygiene"] = -75 if &"medical" in definition.tags: effects[&"medical"] = -85 elif &"wellness" in definition.tags: effects[&"medical"] = -20 if &"adult_only" in definition.tags or &"restricted_service" in definition.tags: effects[&"intimacy"] = -75 return effects func _parse_world_cell(value: Variant) -> Variant: if value is Vector3i: return value if value is Array and value.size() == 3: return Vector3i(int(value[0]), int(value[1]), int(value[2])) return null func _guest_state_index(value: Variant) -> int: if value is int: return value if value >= 0 and value < GuestRuntime.State.size() else -1 var normalized := String(value).to_upper() return GuestRuntime.State.keys().find(normalized) func _publish_snapshot(_absolute_minute: int) -> void: latest_snapshot = create_snapshot() _latest_digest_dirty = true if not snapshot_published.get_connections().is_empty(): snapshot_published.emit(latest_snapshot, latest_digest) state_updated.emit(latest_snapshot) func _execute_purchase_supplies(command: SimulationCommand) -> CommandResult: return supply_management.purchase(self, command) func _execute_set_supply_auto_purchase(command: SimulationCommand) -> CommandResult: return supply_management.set_auto_purchase(self, command) func _execute_set_kitchen_production(command: SimulationCommand) -> CommandResult: return supply_management.set_production(self, command) func get_supply_management_report() -> Dictionary: return supply_management.get_report(self) func preview_supply_purchase(warehouse_id: StringName, item_id: StringName, quantity: int) -> Dictionary: return supply_management.preview_purchase(self, warehouse_id, item_id, quantity) # Existing saves retain each facility's stored base rate; reception never quotes # a new direct sale. Already queued/in-service requests retain their old quote. func _is_anri_manager(staff: StaffRuntime) -> bool: return staff.staff_id == &"heroine_anri" and staff.employment_kind == StaffRuntime.EmploymentKind.UNIQUE and staff.role_id == &"innkeeper" func get_floor_price_multiplier(floor_y: int) -> int: var anri := staff_service.get_staff(&"heroine_anri") if anri == null or not _is_anri_manager(anri): return 10000 var assignment := staff_service.get_work_assignment(anri.staff_id) var facility := facility_service.get_instance(StringName(assignment.get(&"facility_id", &""))) if facility == null or facility.origin.y != floor_y: return 10000 var definition := mvp_content_catalog.get_entry(&"unique_characters", anri.staff_id) return int(definition.get(&"mechanical_effects", {}).get(&"assigned_floor_revenue_multiplier_bp", 10000)) ## Original 6.2 / 6.4: Amelia's assigned chapel receives triple donations. ## This is a placement passive; attendance does not remove it. Existing service ## quotes remain frozen by ServiceRequest/ServiceInstance as for Anri. func get_donation_multiplier(facility: FacilityInstance) -> int: if facility.definition_id != &"chapel": return 1 var amelia := staff_service.get_staff(&"heroine_amelia") if amelia == null or amelia.employment_kind != StaffRuntime.EmploymentKind.UNIQUE or amelia.role_id != &"cleric": return 1 var assignment := staff_service.get_work_assignment(amelia.staff_id) return 3 if StringName(assignment.get(&"facility_id", &"")) == facility.instance_id else 1 func get_facility_business_price(facility: FacilityInstance) -> int: var donation := FacilityPricing.quote(_business_base_price(facility), facility.policy_current) * get_donation_multiplier(facility) return FacilityPricing.apply_multiplier(donation, get_floor_price_multiplier(facility.origin.y)) ## Owned presentation data; accepted request/service quotations stay frozen. ## Matching-surcharge charging remains pending until its inputs are specified. func get_facility_service_pricing_readmodel(facility: FacilityInstance) -> Dictionary: return FacilityPricing.pending_service_readmodel( get_facility_business_price(facility), facility.service_attribute ) func _business_base_price(facility: FacilityInstance) -> int: return 0 if facility.definition_id == &"reception" else facility.base_price func get_business_management_report() -> Dictionary: var minute: int = _clock.absolute_minute var start := (minute / SimulationKernel.MINUTES_PER_DAY) * SimulationKernel.MINUTES_PER_DAY var totals: Dictionary = {} for entry: LedgerEntry in economy_service.get_entries(): if entry.absolute_minute < start or entry.absolute_minute > minute: continue if not totals.has(entry.source_entity_id): totals[entry.source_entity_id] = {&"revenue": 0, &"expenses": 0} if entry.category == &"SERVICE_SALE": totals[entry.source_entity_id][&"revenue"] += entry.amount elif entry.category in [&"OPERATING_EXPENSE", &"STAFF_SIGNING_COST", &"EVENT_ACTION", &"EVENT_COST_COMMITTED", &"DEFENSE_SKILL_COST", &"AUTHORITY_FINE", &"REFUND"]: totals[entry.source_entity_id][&"expenses"] += entry.amount var rows: Array[Dictionary] = [] var kitchens: Array[Dictionary] = [] for facility: FacilityInstance in facility_service.get_instances(): var definition := definition_registry.get_facility(facility.definition_id) if definition == null: continue if facility.definition_id == &"kitchen" and facility.origin.z == WorldService.LAYER_BACK: kitchens.append({&"facility_id": facility.instance_id, &"display_name": definition.display_name, &"origin": Vector2i(facility.origin.x, facility.origin.y), &"layer": facility.origin.z, &"operation_state": StringName(FacilityInstance.OperationState.keys()[facility.operation_state])}) var amounts: Dictionary = totals.get(facility.instance_id, {}) var base := _business_base_price(facility) var service_pricing := get_facility_service_pricing_readmodel(facility) var queued_customers: Dictionary = {} for request: ServiceRequest in facility_service.get_queue(facility.instance_id): for guest_id: StringName in request.guest_ids: queued_customers[guest_id] = true rows.append({ &"facility_id": facility.instance_id, &"definition_id": facility.definition_id, &"category": definition.category, &"display_name": definition.display_name, &"origin": Vector2i(facility.origin.x, facility.origin.y), &"layer": facility.origin.z, &"state": StringName(FacilityInstance.State.keys()[facility.state]), &"kitchen_dependency": facility_service.get_required_kitchen_report(facility.instance_id), &"business_open": facility.operator_open, &"price_mode": StringName(facility.policy_current.get(&"price_mode", &"standard")), &"base_price": base, &"price": service_pricing[&"normal_price"], &"service_pricing": service_pricing, &"floor_price_multiplier_bp": get_floor_price_multiplier(facility.origin.y), &"donation_multiplier": get_donation_multiplier(facility), &"faith_per_service": definition.faith_per_service, &"can_price": base > 0, &"queued_count": facility.queue_entries.size(), &"active_count": facility.active_services.size(), &"queued_customer_count": queued_customers.size(), &"active_customer_count": facility.customer_count(), &"customer_capacity": facility.customer_capacity, &"cleanliness": facility_cleanliness.get_facility(facility.instance_id).get("current", -1), &"cleaning_status": cleaning_bridge.status(facility.instance_id), &"today_revenue": int(amounts.get(&"revenue", 0)), &"today_expenses": int(amounts.get(&"expenses", 0)), }) return {&"minute": minute, &"modes": FacilityPricing.modes(), &"facilities": rows, &"kitchens": kitchens} func _guest_has_checked_in(guest: GuestRuntime) -> bool: for visited_id: StringName in guest.visited_facility_ids: var visited := facility_service.get_instance(visited_id) if visited != null and visited.definition_id == &"reception": return true return false func _service_satisfaction_sample(service: ServiceInstance, _guest: GuestRuntime) -> int: # P1 uses a common expectation of 50 until archetypes have an explicit # expected-quality field. Quality sensitivity is a separate concept. var expectation := 50 return clampi(service.quality_snapshot + FacilityPricing.satisfaction_modifier( service.policy_snapshot, service.quality_snapshot, expectation ), 0, 100) func _release_closed_business_queues(absolute_minute: int) -> void: for facility: FacilityInstance in facility_service.get_instances(): if facility.operator_open and facility.is_within_opening_hours(absolute_minute): continue for request: ServiceRequest in facility_service.get_queue(facility.instance_id): _release_closed_business_request(facility, request, absolute_minute) for guest: GuestRuntime in guest_service.get_guests(): if guest.state != GuestRuntime.State.MOVING: continue var facility := facility_service.get_instance(guest.target_facility_id) if facility == null or (facility.operator_open and facility.is_within_opening_hours(absolute_minute)): continue var transition := guest_service.transition_guest(guest.guest_id, GuestRuntime.State.REPLANNING, absolute_minute, &"FACILITY_CLOSED") if bool(transition.get(&"ok", false)): guest.target_facility_id = &"" guest.path_handle = null func _release_closed_business_request(facility: FacilityInstance, request: ServiceRequest, minute: int) -> void: # Validate the entire group before any queue/state signals become observable. for guest_id: StringName in request.guest_ids: var guest := guest_service.get_guest(guest_id) if guest == null: continue if guest.state != GuestRuntime.State.QUEUING or guest.queue_request_id != request.request_id or guest.target_facility_id != facility.instance_id: return if guest.transition_tick == minute and guest.transitions_this_tick >= GuestService.MAX_TRANSITIONS_PER_TICK: return # A group releases its one reservation and queue atomically. A transition-budget # failure retries next minute, without taking patience or dropping any stock. var before_inventory := inventory_service.create_snapshot() var before_facility := facility_service.create_snapshot() var before_guest := guest_service.create_snapshot() var before_events := domain_events.create_snapshot() var before_ids := id_generator.create_snapshot() var released := false var release := func(_guest_id: StringName = &"", _facility_id: StringName = &"", _request_id: StringName = &"") -> Dictionary: for reservation_id: StringName in request.reserved_inventory_ids: var result := inventory_service.release_reservation(reservation_id, StringName("business.close.%s.%s" % [request.request_id, reservation_id]), minute) if not bool(result.get(&"ok", false)): return result var result := facility_service.leave_queue(facility.instance_id, request.request_id, &"FACILITY_CLOSED") return {&"ok": result.is_success()} var ok := true for guest_id: StringName in request.guest_ids: if not guest_service.has_guest(guest_id): continue var result: Dictionary = guest_service.release_queue_for_replanning(guest_id, minute, release if not released else Callable()) if not bool(result.get(&"ok", false)): ok = false break released = true if ok and not released: ok = bool(release.call().get(&"ok", false)) if not ok: inventory_service.restore_snapshot(before_inventory) facility_service.restore_snapshot(before_facility) guest_service.restore_snapshot(before_guest) domain_events.restore_snapshot(before_events) id_generator.restore_snapshot(before_ids) ## Commute and elevator movement share the authoritative Path projection and ## fixed-point walking credit used by errands and guests. func advance_staff_commute(staff_id: StringName, target_cell: Vector3i, minute: int, reason: StringName = &"") -> Dictionary: var staff := staff_service.get_staff(staff_id) if staff == null: return {&"ok": false, &"reason": &"STAFF_NOT_FOUND"} var actor_id := _staff_path_actor_id(staff_id) if not _path_projection_matches(actor_id, staff.current_cell): _report_path_integration_failure(actor_id, staff_id, &"staff", staff.current_cell) return {&"ok": false} if not reason.is_empty() or staff.current_cell == target_cell or not elevators.get_actor_trip(actor_id).is_empty(): _record_expected_actor_wait(actor_id, staff.current_cell, minute) return {&"ok": true, &"moved": false, &"reason": reason} var path := path_service.find_path(staff.current_cell, target_cell, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]) if path.size() < 2: _record_expected_actor_wait(actor_id, staff.current_cell, minute) return {&"ok": true, &"moved": false, &"reason": &"COMMUTE_ROUTE_BLOCKED"} if _request_elevator_step(actor_id, path[0], path[1], minute, [&"staff"]): return {&"ok": true, &"moved": false, &"reason": &"ELEVATOR_WAIT"} var required_credit := _step_movement_credit(path_service, path[0], path[1], [&"staff"]) var credit := _movement_credit_after_one_minute(actor_id, staff.current_cell, minute) credit = mini(credit, required_credit + MOVEMENT_CREDIT_SCALE - 1) var commute_warp := _warp_step_connector(path[0], path[1], [&"staff"]) if credit < required_credit or not _warp_fare_affordable(commute_warp, 1): _record_expected_actor_wait(actor_id, staff.current_cell, minute) _movement_credits[actor_id] = {&"credit": credit, &"updated_at": minute} return {&"ok": true, &"moved": false} var previous := staff.current_cell if not path_service.project_actor_position(actor_id, path[1], minute): _report_path_integration_failure(actor_id, staff_id, &"staff", previous) return {&"ok": false} staff.current_cell = path[1] _charge_warp_fare(commute_warp, [actor_id], previous, staff.current_cell) _movement_credits[actor_id] = {&"credit": credit - required_credit, &"updated_at": minute} _publish_actor_event(&"STAFF_MOVED", &"staff_commute", [staff_id, actor_id], { &"path_actor_id": actor_id, &"from_cell": previous, &"cell": staff.current_cell, &"target_cell": target_cell, &"reason": &"COMMUTE"}) return {&"ok": true, &"moved": true, &"cell": staff.current_cell} func _request_elevator_step(actor_id: StringName, from: Vector3i, to: Vector3i, minute: int, access: Array[StringName]) -> bool: var connector_id := path_service.get_step_connector(from, to, access) if not elevators.owns_connector(connector_id): return false var shaft_id := elevators.get_shaft_for_connector(connector_id) if not _elevator_extension_pending(shaft_id): var result := elevators.request_trip(actor_id, from, to, minute, access) if bool(result.get(&"ok", false)) and not bool(result.get(&"replay", false)): _publish_actor_event(&"ELEVATOR_TRIP_REQUESTED", &"elevators", [actor_id, shaft_id], result[&"trip"]) # Waiting for a cabin or a construction extension is intentional, never a # walk across floors and never a stuck-actor timeout. _record_expected_actor_wait(actor_id, from, minute) return true func _advance_elevators(minute: int) -> void: var queued_before: Dictionary = {} for trip: Dictionary in elevators.get_report()[&"trips"]: if trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: queued_before[trip[&"request_id"]] = true for shaft: Dictionary in elevators.get_report()[&"shafts"]: var powered := int(_state.get_resource(&"mana")) > 0 for module_id: StringName in shaft[&"module_ids"]: var state := mana_network_service.get_consumer_state(module_id) if state not in [ManaNetworkService.ConsumerState.ACTIVE, ManaNetworkService.ConsumerState.DEGRADED]: powered = false _apply_elevator_arrivals(elevators.set_powered(shaft[&"shaft_id"], powered, minute), minute) _apply_elevator_arrivals(elevators.advance_one_minute(minute), minute) for trip: Dictionary in elevators.get_report()[&"trips"]: if trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED and queued_before.has(trip[&"request_id"]): var payload := trip.duplicate(true) payload[&"boarded_at"] = minute payload[&"wait_minutes"] = minute - int(trip[&"enqueued_at"]) _publish_actor_event(&"ELEVATOR_BOARDED", &"elevators", [trip[&"actor_id"], trip[&"shaft_id"]], payload) func _apply_elevator_arrivals(arrivals: Array[Dictionary], minute: int) -> void: for arrival: Dictionary in arrivals: var actor_id := StringName(arrival[&"actor_id"]) if not path_service.has_actor_projection(actor_id): continue var previous: Vector3i = path_service.get_actor_projected_cell(actor_id) var cell: Vector3i = arrival[&"cell"] var entity_id := StringName(String(actor_id).trim_prefix("guest:").trim_prefix("staff:")) var guest := guest_service.get_guest(entity_id) if String(actor_id).begins_with("guest:") else null var staff := staff_service.get_staff(entity_id) if String(actor_id).begins_with("staff:") else null if guest == null and staff == null: _report_path_integration_failure(actor_id, entity_id, &"elevator", previous) continue if not path_service.project_actor_position(actor_id, cell, minute): _report_path_integration_failure(actor_id, entity_id, &"elevator", previous) continue if guest != null: guest_service.record_path_success(guest.guest_id, cell, minute) else: staff.current_cell = cell _movement_credits[actor_id] = {&"credit": 0, &"updated_at": minute} _publish_actor_event(&"ELEVATOR_ARRIVED", &"elevators", [actor_id, entity_id], arrival) func _execute_set_elevator_operation(command: SimulationCommand) -> CommandResult: var shaft_id := StringName(command.payload.get(&"shaft_id", &"")) var open_value: Variant = command.payload.get(&"operator_open", null) if typeof(open_value) != TYPE_BOOL: return CommandResult.failed(CommandResult.Code.INVALID_PAYLOAD, command.command_id, "operator_open must be a bool.") var found := false for shaft: Dictionary in elevators.get_report()[&"shafts"]: if shaft[&"shaft_id"] == shaft_id: found = true if not found: return CommandResult.failed(CommandResult.Code.NOT_FOUND, command.command_id, "Elevator shaft was not found.") _apply_elevator_arrivals(elevators.set_operator_open(shaft_id, open_value, _runtime_event_minute()), _runtime_event_minute()) _publish_actor_event(&"ELEVATOR_OPERATION_CHANGED", &"elevators", [shaft_id], {&"operator_open": open_value}) return CommandResult.succeeded(command.command_id, {&"shaft_id": shaft_id, &"operator_open": open_value}) ## A07: warp connectors exist for every completed warp pair and are usable only ## while both warps are powered and the reserve can pay one fare. func _synchronize_warps() -> Dictionary: var available := {} var reserve_ok := int(_state.get_resource(&"mana")) > 0 for warp: Dictionary in WarpService.warps_from_facilities(construction_service.get_facilities()): var state := mana_network_service.get_consumer_state(warp[&"instance_id"]) available[warp[&"instance_id"]] = reserve_ok and state in [ManaNetworkService.ConsumerState.ACTIVE, ManaNetworkService.ConsumerState.DEGRADED] return WarpService.synchronize(path_service, construction_service.get_facilities(), available, int(_state.get_resource(&"mana")) >= WarpService.MANA_PER_TRIP) func _warp_step_connector(from: Vector3i, to: Vector3i, access: Array[StringName]) -> StringName: var connector_id := path_service.get_step_connector(from, to, access) return connector_id if WarpService.is_warp_connector(connector_id) else &"" func _warp_fare_affordable(connector_id: StringName, riders: int) -> bool: return connector_id.is_empty() or int(_state.get_resource(&"mana")) >= WarpService.MANA_PER_TRIP * riders ## Charged once, in the same tick as the committed step; affordability was ## checked before the step, so the deduction cannot fail here. func _charge_warp_fare(connector_id: StringName, actor_ids: Array, from: Vector3i, to: Vector3i) -> void: if connector_id.is_empty(): return var fare := WarpService.MANA_PER_TRIP * actor_ids.size() _state.apply_resource_deltas({&"mana": -fare}) var subjects: Array[StringName] = [connector_id] for actor_id: Variant in actor_ids: subjects.append(StringName(actor_id)) _publish_actor_event(&"WARP_TRAVELED", &"warp", subjects, { &"connector_id": connector_id, &"from_cell": from, &"cell": to, &"riders": actor_ids.size(), &"mana": fare}) func _synchronize_elevators(minute: int) -> Dictionary: return elevators.synchronize(ElevatorService.configurations_from_facilities( construction_service.get_facilities(), definition_registry), minute) func _elevator_shaft_busy(shaft: Dictionary) -> bool: return shaft.get(&"phase", &"IDLE") != &"IDLE" or int(shaft.get(&"waiting_count", 0)) > 0 \ or int(shaft.get(&"passenger_count", 0)) > 0 func _elevator_touches_module(shaft: Dictionary, definition: FacilityDefinition, origin: Vector3i) -> bool: if definition == null or definition.id not in [&"guest_elevator", &"service_elevator"]: return false var stops: Array = shaft[&"stops"] var bottom: Vector3i = stops.front() var top: Vector3i = stops.back() return origin.x == bottom.x and origin.z == bottom.z and definition.footprint.x == int(shaft[&"width"]) \ and origin.y <= top.y + 1 and origin.y + definition.footprint.y >= bottom.y func _elevator_extension_pending(shaft_id: StringName) -> bool: for shaft: Dictionary in elevators.get_report()[&"shafts"]: if shaft[&"shaft_id"] != shaft_id: continue for job: ConstructionJob in construction_service.get_jobs(): if job.state in [ConstructionJob.State.COMPLETED, ConstructionJob.State.CANCELLED]: continue if _elevator_touches_module(shaft, definition_registry.get_facility(job.definition_id), job.origin): return true return false func _validate_elevator_crosslinks(service: ElevatorService, paths: PathService, construction: ConstructionService, guests: GuestService, staff: StaffService, minute: int) -> bool: if service.current_minute != minute: return false var expected := ElevatorService.configurations_from_facilities(construction.get_facilities(), definition_registry) var saved_configs: Array = [] for car: Dictionary in service.create_snapshot()[&"shafts"]: saved_configs.append(car[&"config"]) # Construction uses numeric spatial order while snapshots use lexical IDs. # Compare canonical shaft identity order (x=-2/10, floors=2/10 otherwise differ). var by_id := func(a: Dictionary, b: Dictionary) -> bool: return String(a[&"shaft_id"]) < String(b[&"shaft_id"]) expected.sort_custom(by_id) saved_configs.sort_custom(by_id) if expected != saved_configs: return false for trip: Dictionary in service.get_report()[&"trips"]: var actor_id := StringName(trip[&"actor_id"]) var entity_id := StringName(String(actor_id).trim_prefix("guest:").trim_prefix("staff:")) var guest := guests.get_guest(entity_id) if String(actor_id).begins_with("guest:") else null var worker := staff.get_staff(entity_id) if String(actor_id).begins_with("staff:") else null if guest == null and worker == null: return false if guest != null and (guest.current_cell != trip[&"from"] or trip[&"from"].z != WorldService.LAYER_FRONT): return false if worker != null and worker.current_cell != trip[&"from"]: return false if not paths.has_actor_projection(actor_id) or paths.get_actor_projected_cell(actor_id) != trip[&"from"]: return false return true func is_staff_in_transit(staff_id: StringName) -> bool: return not elevators.get_actor_trip(_staff_path_actor_id(staff_id)).is_empty() func _estimated_actor_path_minutes(path: Array, access: Array[StringName]) -> int: for index: int in range(1, path.size()): var connector_id := path_service.get_step_connector(path[index - 1], path[index], access) if elevators.owns_connector(connector_id) \ or _step_movement_credit_for_connector(path_service, path[index - 1], path[index], connector_id) > MOVEMENT_CREDIT_SCALE: var cost: int if access.is_empty() and path_service.get_script().resource_path == "res://scripts/world/path_service.gd" \ and path[0] is Vector3i and path[path.size() - 1] is Vector3i: cost = path_service.calculate_guest_batch_path_cost(path[0], path[path.size() - 1], path) else: cost = path_service.calculate_path_cost(path, access) return ceili(float(cost) / PathService.ORTHOGONAL_MOVE_COST) return maxi(path.size() - 1, 0) func _prune_expired_staff_elevator_waits(minute: int) -> void: for trip: Dictionary in elevators.get_report()[&"trips"]: if trip.has(&"companion_id"): continue var actor_id := StringName(trip[&"actor_id"]) if trip[&"state"] != PathService.ELEVATOR_REQUEST_QUEUED or not String(actor_id).begins_with("staff:"): continue var staff := staff_service.get_staff(StringName(String(actor_id).trim_prefix("staff:"))) if staff == null: elevators.cancel_actor(actor_id) continue if staff.state == StaffRuntime.State.OFF_DUTY and get_staff_rest_report(staff.staff_id).get(&"reachable", false): continue if not staff_service.requires_attendance(staff.staff_id): continue var task := staff_service.get_task(staff.current_task_id) if task != null and task.state == StaffTask.State.MOVING and task.assigned_staff_id == staff.staff_id: continue var shift := staff_service.get_shift(staff.shift_id) if not staff.can_take_shift() or shift == null or (not shift.contains_minute(minute) and not shift.is_commute_window(minute)): elevators.cancel_actor(actor_id) _publish_actor_event(&"ELEVATOR_COMMUTE_CANCELLED", &"staff_commute", [staff.staff_id, actor_id], {&"reason": &"SHIFT_ENDED_OR_UNAVAILABLE"}) ## Stairs carry half-minute progress between ticks. Doors keep their established ## one-step walking behavior, and elevator time is owned by the cabin service. static func _step_movement_credit(path: PathService, from: Vector3i, to: Vector3i, access: Array[StringName]) -> int: return _step_movement_credit_for_connector(path,from,to,path.get_step_connector(from,to,access)) static func _step_movement_credit_for_connector(path: PathService, from: Vector3i, to: Vector3i, connector_id: StringName) -> int: if String(connector_id).begins_with("infra.connector:") and from.x == to.x \ and from.z == to.z and absi(from.y - to.y) == 1: var connector := path.get_connector(connector_id) if connector.get(&"movement_type", &"") == PathService.MOVEMENT_CONNECTOR \ and int(connector.get(&"cost", 0)) == 150: return STAIR_MOVEMENT_CREDIT return MOVEMENT_CREDIT_SCALE static func _cell_movement_credit_limit(path: PathService, cell: Vector3i) -> int: # Temporary blockades must not invalidate progress saved on a staircase. # Inspect structural edges here instead of querying currently usable routes. for edge: Dictionary in path.get_neighbors(cell): var next: Vector3i = edge[&"to"] if String(edge[&"connector_id"]).begins_with("infra.connector:") \ and edge[&"movement_type"] == PathService.MOVEMENT_CONNECTOR \ and int(edge[&"base_cost"]) == 150 and cell.x == next.x \ and cell.z == next.z and absi(cell.y - next.y) == 1: return STAIR_MOVEMENT_CREDIT return MOVEMENT_CREDIT_SCALE func _prune_legacy_stairs_and_movement() -> bool: if not legacy_stairs.prune_invalid(): return false # A removed staircase discards only progress that can no longer be used. for actor_id: StringName in _movement_credits: if path_service.has_actor_projection(actor_id): var cell: Vector3i = path_service.get_actor_projected_cell(actor_id) _movement_credits[actor_id][&"credit"] = mini(int(_movement_credits[actor_id][&"credit"]), _cell_movement_credit_limit(path_service, cell) - 1) return true func get_elevator_wait_statistics(shaft_id: StringName) -> Dictionary: var samples: Array[int] = [] var oldest_wait := 0 var minute := _runtime_event_minute() for event: SimulationDomainEvent in domain_events.recent_events(): if event.event_type != &"ELEVATOR_BOARDED" or event.absolute_minute < minute - 60 \ or event.absolute_minute > minute or event.payload.get(&"shaft_id", &"") != shaft_id: continue var wait: int = int(event.payload.get(&"wait_minutes", -1)) if wait >= 0: samples.append(wait) for trip: Dictionary in elevators.get_report()[&"trips"]: if trip[&"shaft_id"] == shaft_id and trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: oldest_wait = maxi(oldest_wait, minute - int(trip[&"enqueued_at"])) samples.sort() var total := 0 for wait: int in samples: total += wait return {&"sample_count": samples.size(), &"window_minutes": 60, &"event_history_limit": DomainEventStream.RECENT_EVENT_LIMIT, &"mean_wait_minutes": float(total) / samples.size() if not samples.is_empty() else 0.0, &"p95_wait_minutes": samples[ceili(samples.size() * 0.95) - 1] if not samples.is_empty() else 0, &"oldest_wait_minutes": oldest_wait} func _demolition_lost_route_cells(instance_id: StringName) -> Array[Vector3i]: var lost_cells: Array[Vector3i] = [] var projection := facility_infrastructure_projector.get_projection(instance_id) var base := route_subslot_path_adapter.get_base_walkable_cells() for cell: Vector3i in projection.get(&"cells", []): if cell not in base: lost_cells.append(cell) return lost_cells func _stair_demolition_dependents(instance_id: StringName) -> Array[StringName]: var lost_cells := _demolition_lost_route_cells(instance_id) var dependents := facility_infrastructure_projector.get_stair_dependents(lost_cells) dependents.erase(instance_id) # A staircase under construction already relies on its lower landing too. for job: ConstructionJob in construction_service.get_jobs(): if not job.is_terminal() and job.definition_id in [&"guest_stairs", &"service_stairs"] \ and job.origin + Vector3i(0, -1, 0) in lost_cells: dependents.append(job.job_id) return dependents ## Use the same entrance and horizontal access cells as ordinary Guest AI. ## Facility access_available is an operating override, not a topology cache. func is_facility_reachable(facility_id: StringName) -> bool: var facility := facility_service.get_instance(facility_id) var entrance: Variant = _operations_entrance_cell() if facility == null or entrance == null or not facility.access_available: return false # Read-only UI queries need existence, not the cheapest destination. # Share Guest AI's candidates and path rules without caching topology state. for candidate: Vector3i in _facility_access_candidates(facility): if not path_service.find_path(entrance, candidate).is_empty(): return true return false func get_facility_access_report(facility_id: StringName) -> Dictionary: var facility := facility_service.get_instance(facility_id) var entrance: Variant = _operations_entrance_cell() if facility == null or entrance == null or not facility.access_available: return {&"reachable": false, &"path": []} return _find_facility_access_path(entrance, facility) func _staff_is_being_carried(patient_id: StringName) -> bool: for task: StaffTask in staff_service.get_tasks(): if task.task_type == &"rescue_transport" and not task.is_terminal() and not task.assigned_staff_id.is_empty() and task.target_entity_id == patient_id: return true return false func _rescue_destination(patient_cell: Vector3i) -> Dictionary: var best := {} var distance := 2147483647 for clinic: FacilityInstance in facility_service.get_instances(): if clinic.definition_id != &"infirmary" or clinic.operation_state != FacilityInstance.OperationState.OPERATING: continue var cells := {clinic.origin: true} for cell: Vector3i in clinic.occupied_cells: for delta in [Vector3i.LEFT, Vector3i.RIGHT, Vector3i.UP, Vector3i.DOWN]: cells[cell + delta] = true var ordered := cells.keys() ordered.sort_custom(func(a: Vector3i, b: Vector3i): return a.y < b.y if a.y != b.y else a.x < b.x) for cell: Vector3i in ordered: if cell.z != WorldService.LAYER_BACK: continue var route := path_service.find_backyard_path(patient_cell, cell, true) if not route.is_empty() and route.size() < distance: distance = route.size() best = {&"facility_id": clinic.instance_id, &"cell": cell} return best func _handle_rescue_arrival(task: StaffTask, carrier: StaffRuntime, minute: int) -> bool: if task.task_type == CleaningBridge.TASK_TYPE: return carrier.current_cell == task.target_cell and cleaning_bridge.task_can_work(task, carrier) if task.task_type not in [&"rescue_pickup", &"rescue_transport"]: return true var patient := staff_service.get_staff(task.target_entity_id) if patient == null or patient.state != StaffRuntime.State.INCAPACITATED or carrier.current_cell != task.target_cell: return false if task.task_type == &"rescue_transport": var clinic := facility_service.get_instance(task.rescue_facility_id) return clinic != null and clinic.operation_state == FacilityInstance.OperationState.OPERATING and patient.current_cell == carrier.current_cell if patient.current_cell != carrier.current_cell: return false var destination := _rescue_destination(patient.current_cell) if destination.is_empty(): return false task.task_type = &"rescue_transport" task.rescue_facility_id = destination[&"facility_id"] task.target_cell = destination[&"cell"] task.state = StaffTask.State.MOVING task.state_changed_at = minute _publish_actor_event(&"STAFF_RESCUE_PICKED_UP", &"staff_service", [patient.staff_id, carrier.staff_id], {&"facility_id": task.rescue_facility_id}) return false func _advance_staff_rescue(minute: int) -> void: for task: StaffTask in staff_service.get_tasks(): if task.task_type not in [&"rescue_pickup", &"rescue_transport"] or task.is_terminal(): continue var patient := staff_service.get_staff(task.target_entity_id) var carrier := staff_service.get_staff(task.assigned_staff_id) var invalid := patient == null or patient.state != StaffRuntime.State.INCAPACITATED if not task.assigned_staff_id.is_empty(): invalid = invalid or carrier == null or not carrier.can_take_shift() or carrier.current_task_id != task.task_id if task.task_type == &"rescue_transport": var clinic := facility_service.get_instance(task.rescue_facility_id) invalid = invalid or carrier == null or clinic == null or clinic.operation_state != FacilityInstance.OperationState.OPERATING if patient != null and carrier != null: invalid = invalid or patient.current_cell != carrier.current_cell if invalid: if carrier != null and elevators.get_actor_trip(_staff_path_actor_id(carrier.staff_id)).get(&"state", &"") == PathService.ELEVATOR_REQUEST_BOARDED: continue # Keep the pair aboard until a real or emergency landing. staff_service.cancel_task(task.task_id, &"RESCUE_INTERRUPTED", minute) if patient != null: elevators.cancel_actor(_staff_path_actor_id(patient.staff_id)) if carrier != null: elevators.cancel_actor(_staff_path_actor_id(carrier.staff_id)) for patient: StaffRuntime in staff_service.get_staff_members(): if not _defense_lock_owner(patient.staff_id, &"DEFENDER").is_empty(): continue if patient.health > 0 and patient.state != StaffRuntime.State.INCAPACITATED: continue if not patient.current_task_id.is_empty(): staff_service.cancel_task(patient.current_task_id, &"STAFF_COLLAPSED", minute) if patient.state != StaffRuntime.State.INCAPACITATED or patient.state_reason.is_empty(): patient.transition_to(StaffRuntime.State.INCAPACITATED, &"STAFF_COLLAPSED", minute) _publish_actor_event(&"STAFF_COLLAPSED", &"staff_service", [patient.staff_id], {&"cell": patient.current_cell}) if patient.current_cell.z == WorldService.LAYER_FRONT and patient.state_reason != &"COLLAPSE_WITNESSED": for guest: GuestRuntime in guest_service.get_guests(): if _authority_line_of_sight(guest.current_cell, patient.current_cell, {}): if progression_service.update_metrics({&"reputation": maxi(int(progression_service.metrics[&"reputation"]) - 1, 0)}): patient.state_reason = &"COLLAPSE_WITNESSED" _publish_actor_event(&"STAFF_COLLAPSE_WITNESSED", &"staff_service", [patient.staff_id, guest.guest_id], {}) break var pending := false for task: StaffTask in staff_service.get_tasks(): if task.task_type in [&"rescue_pickup", &"rescue_transport"] and task.target_entity_id == patient.staff_id and not task.is_terminal(): pending = true if not elevators.get_actor_trip(_staff_path_actor_id(patient.staff_id)).is_empty(): continue if pending or _rescue_destination(patient.current_cell).is_empty(): continue staff_service.create_task(&"rescue_pickup", 95, minute, { &"target_entity_id": patient.staff_id, &"target_cell": patient.current_cell, &"required_role_tags": [&"medic"], &"task_tags": [&"rescue"], &"estimated_minutes": 30, &"interrupt_level": 0, &"emergency": true, }) func get_staff_rescue_report() -> Dictionary: var report := {&"waiting": 0, &"transporting": 0, &"treating": 0} for patient: StaffRuntime in staff_service.get_staff_members(): if patient.state != StaffRuntime.State.INCAPACITATED: continue var category: StringName = &"waiting" for task: StaffTask in staff_service.get_tasks(): if task.target_entity_id == patient.staff_id and task.task_type == &"rescue_transport" and not task.is_terminal(): category = &"treating" if task.state == StaffTask.State.WORKING else &"transporting" report[category] += 1 return report func _staff_rescue_status(staff_id: StringName) -> String: var staff := staff_service.get_staff(staff_id) if staff == null or staff.state != StaffRuntime.State.INCAPACITATED: return "" for task: StaffTask in staff_service.get_tasks(): if task.target_entity_id != staff_id or task.is_terminal(): continue if task.task_type == &"rescue_transport": return "治療中" if task.state == StaffTask.State.WORKING else "搬送中" if task.task_type == &"rescue_pickup" and not task.assigned_staff_id.is_empty(): return "救護班が向かっています" return "救護待ち(医療職・医務室・裏経路を確認)" ## Rest destinations derive from saved facilities and position, with no hidden timer. func get_staff_rest_report(staff_id: StringName) -> Dictionary: var staff := staff_service.get_staff(staff_id) if staff == null or staff.state != StaffRuntime.State.OFF_DUTY or staff.health <= 0 or not staff.current_task_id.is_empty(): return {} if not _defense_lock_owner(staff_id, &"DEFENDER").is_empty() or event_service.get_actor_locks().has(staff_id): return {} var best := {&"reachable": false, &"resting": false, &"label": "休憩先なし(稼働中の待機室・宿舎と経路を確認)"} var best_cost := 2147483647 for room: FacilityInstance in facility_service.get_instances(): if room.definition_id not in [&"staff_waiting_room", &"staff_dormitory"] or room.operation_state != FacilityInstance.OperationState.OPERATING: continue var route := staff_commute.find_facility_access_path(staff.current_cell, room) if not route.get(&"reachable", false): continue var cost := path_service.calculate_path_cost(route[&"path"], [&"staff"]) if cost >= best_cost: continue best_cost = cost var resting := StaffCommuteService.is_at_facility(staff, room) and not is_staff_in_transit(staff_id) best = {&"reachable": true, &"resting": resting, &"cell": route[&"cell"], &"facility_id": room.instance_id, &"label": ("宿舎で休憩中" if room.definition_id == &"staff_dormitory" else "待機室で休憩中") if resting else "休憩先へ移動中"} return best func _advance_weather(absolute_minute: int) -> void: weather.advance_to(absolute_minute) func get_weather_report() -> Dictionary: return {&"kind": weather.get_kind(), &"minute": weather.get_minute()} ## Read-only migration preview; applying dimensions remains a separate transaction. func preview_facility_dimension_migration(instance_id: StringName) -> Dictionary: var instance := construction_service.get_facility(instance_id) if instance.is_empty(): return {&"geometry_ok": false, &"ready_to_apply": false, &"errors": [&"COMPLETED_FACILITY_REQUIRED"]} var weights := {} for built: Dictionary in construction_service.get_facilities(): var definition := definition_registry.get_facility(built[&"definition_id"]) if definition == null: return {&"geometry_ok": false, &"ready_to_apply": false, &"errors": [&"MISSING_FACILITY_DEFINITION"]} weights[built[&"instance_id"]] = definition.weight_per_cell var preview: Dictionary = preload("res://scripts/construction/facility_resize_preview.gd").inspect(world_service, instance, weights[instance_id], weights) if preview.has(&"remaining_gates"): preview[&"remaining_gates"].erase(&"DEFINITION_WEIGHT_VALIDATION") preview[&"definition_weights"] = weights if preview.get(&"geometry_ok", false): preview[&"construction_candidate"] = preload("res://scripts/construction/facility_resize_preview.gd").prepare_construction_candidate(world_service, construction_service, instance_id, weights[instance_id], weights) preview[&"actors_on_removed_cells"] = [] for guest: GuestRuntime in guest_service.get_guests(): if guest.current_cell in preview.get(&"removed", []): preview[&"actors_on_removed_cells"].append({&"kind": &"guest", &"id": guest.guest_id, &"cell": guest.current_cell}) for worker: StaffRuntime in staff_service.get_staff_members(): if worker.current_cell in preview.get(&"removed", []): preview[&"actors_on_removed_cells"].append({&"kind": &"staff", &"id": worker.staff_id, &"cell": worker.current_cell}) preview[&"construction_jobs"] = [] for job: ConstructionJob in construction_service.get_jobs(): preview[&"construction_jobs"].append({&"job_id": job.job_id, &"definition_id": job.definition_id, &"state": job.state}) return preview ## Assemble and validate a snapshot in an isolated Runtime, including access ## preservation. Disk backup, actor relocation and atomic commit remain separate. func prepare_facility_dimension_snapshot(instance_id: StringName) -> Dictionary: if construction_service.get_facility(instance_id).is_empty() and construction_service.get_job(instance_id) != null: return _prepare_construction_dimension_snapshot(instance_id) var preview := preview_facility_dimension_migration(instance_id) var result := {&"ok": false, &"ready_to_apply": false, &"errors": [], &"preview": preview} if not preview.get(&"construction_candidate", {}).get(&"ok", false): result[&"errors"].append(&"CONSTRUCTION_CANDIDATE_REQUIRED") return result if not preview[&"actors_on_removed_cells"].is_empty(): result[&"errors"].append(&"ACTOR_RELOCATION_REQUIRED") return result var candidate := create_snapshot() var data: Dictionary = candidate[&"systems"][&"runtime"] data[&"world"] = preview[&"construction_candidate"][&"world"].duplicate(true) data[&"construction"] = preview[&"construction_candidate"][&"construction"].duplicate(true) var found := false for instance: Dictionary in data[&"facilities"][&"instances"]: if StringName(instance[&"instance_id"]) != instance_id: continue found = true var cells: Array = [] for cell: Vector3i in preview[&"target_cells"]: cells.append([cell.x, cell.y, cell.z]) instance[&"occupied_cells"] = cells if not found: result[&"errors"].append(&"OPERATIONAL_FACILITY_REQUIRED") return result var isolated: Variant = get_script().new() var candidate_clock: Variant = load("res://autoload/game_clock.gd").new() var candidate_state: Variant = load("res://autoload/game_state.gd").new() candidate_clock.reset(1, 6, 0) candidate_state.reset() isolated.initialize(candidate_clock, candidate_state, world_seed) # The immutable definition resources must match the source registry, # including any explicitly registered expansion definitions. isolated.definition_registry = definition_registry var restored: bool = isolated.restore_snapshot(candidate) if restored: result[&"routes"] = preload("res://scripts/construction/facility_resize_routes.gd").inspect(self, isolated, instance_id) if result[&"routes"][&"errors"].is_empty() and not result[&"routes"][&"guest_replans"].is_empty(): result[&"retargeting"] = preload("res://scripts/construction/facility_resize_routes.gd").retarget_guests(isolated, result[&"routes"]) if result[&"retargeting"][&"ok"]: candidate = isolated.create_snapshot() restored = isolated.restore_snapshot(candidate) if restored: result[&"routes"] = preload("res://scripts/construction/facility_resize_routes.gd").inspect(self, isolated, instance_id) isolated.free() candidate_clock.free() candidate_state.free() if not restored: result[&"errors"].append(&"RUNTIME_CANDIDATE_REJECTED") return result if not result[&"routes"][&"ok"]: result[&"errors"].append(&"ROUTE_MIGRATION_REQUIRED") return result result[&"ok"] = true result[&"snapshot"] = candidate return result ## Explicit disk migration. Source Runtime stays untouched; the formal package ## is replaced only after isolated domain/route validation and verified backup. func migrate_facility_dimensions_at_path(save_path: String, instance_id: StringName = &"", expected_source_digest: String = "") -> Dictionary: var loaded := SaveService.load_snapshot(save_path) if not loaded[&"ok"]: return loaded if not expected_source_digest.is_empty() and SimulationDigest.compute(loaded[&"snapshot"]) != expected_source_digest: return {&"ok": false, &"code": &"MIGRATION_SOURCE_CHANGED"} var isolated: Variant = get_script().new() var migration_clock: Variant = load("res://autoload/game_clock.gd").new() var migration_state: Variant = load("res://autoload/game_state.gd").new() migration_clock.reset(1, 6, 0); migration_state.reset() isolated.initialize(migration_clock, migration_state, world_seed) isolated.definition_registry = definition_registry var prepared := {&"ok": false, &"errors": [&"SOURCE_RUNTIME_REJECTED"]} if isolated.restore_snapshot(loaded[&"snapshot"]): if instance_id.is_empty(): prepared = isolated.prepare_all_facility_dimension_snapshots() else: prepared = isolated.prepare_facility_dimension_snapshot(instance_id) isolated.free(); migration_clock.free(); migration_state.free() if not prepared[&"ok"]: return {&"ok": false, &"code": &"DIMENSION_MIGRATION_REJECTED", &"details": prepared} return SaveService.replace_snapshot_with_backup(save_path, loaded[&"snapshot"], prepared[&"snapshot"]) ## Runs on an isolated migration Runtime; caller commits only the final snapshot. func prepare_all_facility_dimension_snapshots() -> Dictionary: var original := create_snapshot() var targets: Array[StringName] = [] var shapes: Dictionary = preload("res://scripts/construction/facility_resize_preview.gd").DIMENSIONS for job: ConstructionJob in construction_service.get_jobs(): if job.is_terminal() or not shapes.has(job.definition_id): continue var size: Vector2i = shapes[job.definition_id][&"new"] if job.rotation in [90,270]: size = Vector2i(size.y,size.x) if not preload("res://scripts/construction/facility_resize_preview.gd")._same_cells(job.reserved_cells, world_service.cells_for_footprint(job.origin,size)): targets.append(job.job_id) for facility: Dictionary in construction_service.get_facilities(): if not shapes.has(facility[&"definition_id"]): continue var size: Vector2i = shapes[facility[&"definition_id"]][&"new"] if facility[&"rotation"] in [90,270]: size = Vector2i(size.y,size.x) if not preload("res://scripts/construction/facility_resize_preview.gd")._same_cells(facility[&"occupied_cells"], world_service.cells_for_footprint(facility[&"origin"],size)): targets.append(facility[&"instance_id"]) for id: StringName in targets: var prepared := prepare_facility_dimension_snapshot(id) if not prepared[&"ok"] or not restore_snapshot(prepared[&"snapshot"]): var rolled_back := restore_snapshot(original) return {&"ok": false, &"errors": [&"BATCH_DIMENSION_REJECTED"], &"facility_id": id, &"details": prepared, &"rolled_back": rolled_back} return {&"ok": true, &"snapshot": create_snapshot(), &"facility_ids": targets} func _prepare_construction_dimension_snapshot(job_id: StringName) -> Dictionary: var weights := {} for facility: Dictionary in construction_service.get_facilities(): var definition := definition_registry.get_facility(facility[&"definition_id"]) if definition == null: return {&"ok": false, &"errors": [&"MISSING_FACILITY_DEFINITION"]} weights[facility[&"instance_id"]] = definition.weight_per_cell var job := construction_service.get_job(job_id) var definition := definition_registry.get_facility(job.definition_id) if job.is_terminal() or definition == null: return {&"ok": false, &"errors": [&"ACTIVE_JOB_REQUIRED"]} weights[job_id] = definition.weight_per_cell var prepared: Dictionary = preload("res://scripts/construction/facility_resize_preview.gd").prepare_construction_candidate(world_service, construction_service, job_id, definition.weight_per_cell, weights) if not prepared[&"ok"]: return prepared var candidate := create_snapshot() candidate[&"systems"][&"runtime"][&"world"] = prepared[&"world"] candidate[&"systems"][&"runtime"][&"construction"] = prepared[&"construction"] var isolated: Variant = get_script().new() var candidate_clock: Variant = load("res://autoload/game_clock.gd").new() var candidate_state: Variant = load("res://autoload/game_state.gd").new() candidate_clock.reset(1,6,0); candidate_state.reset() isolated.initialize(candidate_clock, candidate_state, world_seed) isolated.definition_registry = definition_registry var valid: bool = isolated.restore_snapshot(candidate) isolated.free(); candidate_clock.free(); candidate_state.free() if not valid: return {&"ok": false, &"errors": [&"RUNTIME_CANDIDATE_REJECTED"]} return {&"ok": true, &"ready_to_apply": false, &"snapshot": candidate} ## Read-only saved-game preflight used by the load menu before confirmation. func preview_facility_dimensions_at_path(save_path: String) -> Dictionary: var loaded := SaveService.inspect_snapshot(save_path) if not loaded[&"ok"]: return loaded var isolated: Variant = get_script().new() var preview_clock: Variant = load("res://autoload/game_clock.gd").new() var preview_state: Variant = load("res://autoload/game_state.gd").new() preview_clock.reset(1,6,0); preview_state.reset() isolated.initialize(preview_clock,preview_state,world_seed) isolated.definition_registry=definition_registry var result := {&"ok": false, &"code": &"SOURCE_RUNTIME_REJECTED"} if isolated.restore_snapshot(loaded[&"snapshot"]): result=isolated.prepare_all_facility_dimension_snapshots() result[&"source_digest"]=SimulationDigest.compute(loaded[&"snapshot"]) result[&"changes"]=[] if result[&"ok"]: for id: StringName in result[&"facility_ids"]: var facility: Dictionary=isolated.construction_service.get_facility(id) var job: ConstructionJob=isolated.construction_service.get_job(id) var definition_id: StringName=facility.get(&"definition_id",&"") if job==null else job.definition_id var definition: FacilityDefinition=definition_registry.get_facility(definition_id) result[&"changes"].append({&"id":id,&"name":definition.display_name,&"size":definition.footprint,&"building":job!=null}) result.erase(&"snapshot") isolated.free();preview_clock.free();preview_state.free() return result func _on_cleanliness_facility_registered(facility: FacilityInstance) -> void: var values := cleaning_policy.create_snapshot() if facility_cleanliness.get_facility(facility.instance_id).is_empty(): facility_cleanliness.register_facility(facility.instance_id, values["initial_cleanliness"], values["maximum_cleanliness"]) func _on_cleanliness_facility_unregistered(facility_id: StringName) -> void: for task: StaffTask in staff_service.get_tasks(): if task.task_type == CleaningBridge.TASK_TYPE and task.target_entity_id == facility_id and not task.is_terminal(): staff_service.cancel_task(task.task_id, &"CLEANING_TARGET_REMOVED", _runtime_event_minute()) if not facility_cleanliness.get_facility(facility_id).is_empty(): facility_cleanliness.unregister_facility(facility_id) _sync_facility_staffing(_runtime_event_minute()) func _validate_cleanliness_crosslinks(cleanliness: RefCounted, facilities: FacilityService, staff: StaffService, path: PathService) -> bool: var snapshot: Dictionary = cleanliness.create_snapshot() var instances := facilities.get_instances() var completed_cleanings: Dictionary = {} if snapshot["facilities"].size() != instances.size(): return false for facility: FacilityInstance in instances: var row: Dictionary = cleanliness.get_facility(facility.instance_id) if row.is_empty() or row["maximum"] != 100 or row["initial"] != 100: return false for receipt: Dictionary in snapshot["receipts"]: if receipt["kind"] == "clean": var task := staff.get_task(StringName(receipt["source_id"])) if task == null or task.task_type != CleaningBridge.TASK_TYPE or task.state != StaffTask.State.COMPLETED or task.target_entity_id != StringName(receipt["facility_id"]) or receipt["amount"] != cleaning_policy.create_snapshot()["recovery_per_completed_task"]: return false if task.progress_minutes != task.estimated_minutes: return false completed_cleanings[task.task_id] = true continue if receipt["kind"] != "use": return false var service := facilities.get_service(StringName(receipt["source_id"])) if service == null or service.state != ServiceInstance.State.COMPLETED or service.facility_id != StringName(receipt["facility_id"]): return false var facility := facilities.get_instance(service.facility_id) var definition := definition_registry.get_facility(facility.definition_id) if definition == null or receipt["amount"] != definition.cleanliness_decay_per_service: return false var pending: Dictionary = {} for task: StaffTask in staff.get_tasks(): if task.task_type != CleaningBridge.TASK_TYPE: continue if task.estimated_minutes != cleaning_policy.create_snapshot()["work_minutes"] or task.required_role_tags != [&"cleaner"]: return false if task.state == StaffTask.State.COMPLETED and facilities.has_instance(task.target_entity_id) and not completed_cleanings.has(task.task_id): return false if task.is_terminal(): continue var facility := facilities.get_instance(task.target_entity_id) if facility == null or task.target_cell not in CleaningBridge.access_cells(facility, path, false) or pending.has(task.target_entity_id): return false pending[task.target_entity_id] = true if not task.assigned_staff_id.is_empty(): var worker := staff.get_staff(task.assigned_staff_id) if not CleaningBridge.is_deployed(worker, facilities.get_instance(worker.assigned_facility_id) if worker != null else null, staff): return false return true func _automatic_staff_work_units(task_id: StringName) -> int: var task := staff_service.get_task(task_id) # Cleaning is progressed after attendance, dependency and cancellation checks. return 0 if task != null and task.task_type == CleaningBridge.TASK_TYPE else 1 ## This is a configured event sink, not a thief/riot/loan predicate. Producers ## must supply a cause identity and a complete, approved threat template. func _build_event_defense_request(action: EventAction, context: Dictionary) -> Dictionary: var instance_value: Variant = context.get(&"event_instance_id", context.get("event_instance_id", null)) var source_value: Variant = context.get(&"threat_source_id", context.get("threat_source_id", null)) var template_value: Variant = action.payload.get(&"template_id", action.payload.get("template_id", null)) var kind_value: Variant = action.payload.get(&"source_kind", action.payload.get("source_kind", null)) for raw: Variant in [instance_value, source_value, template_value, kind_value]: if typeof(raw) not in [TYPE_STRING, TYPE_STRING_NAME] or String(raw).is_empty(): return {&"ok": false, &"error": "Configured threat requires event, source, template and source-kind identities."} var source_kind := String(kind_value).to_upper() if source_kind not in ["THIEF", "BANKRUPTCY_RIOT", "BANK_DELINQUENCY", "AUTHORITY_ESCALATION"]: return {&"ok": false, &"error": "Configured threat source kind is unknown."} var identity := JSON.stringify([String(instance_value), String(action.action_id)], "", true, true) var raid_id := StringName("event_defense_raid." + identity.sha256_text()) var prepared := _prepare_event_threat_template(action, StringName(source_value), raid_id) if not bool(prepared.get(&"ok", false)): return prepared var definition: Dictionary = prepared[&"definition"] var digest := String(prepared[&"digest"]) var event_source := { "event_instance_id": String(instance_value), "event_action_id": String(action.action_id), "template_id": String(template_value), "template_digest": digest, "source_kind": source_kind, } definition[&"event_source"] = event_source var existing := defense_service.get_raid(raid_id) if not existing.is_empty(): if existing.get(&"event_source", {}) != event_source or not _event_threat_template_matches_raid(existing, prepared[&"canonical_raid"]): return {&"ok": false, &"error": "Event threat identity conflicts with an already-created incident."} return {&"ok": true, &"raid_id": raid_id, &"definition": definition, &"is_replay": true} for objective_id: StringName in prepared[&"objective_ids"]: var objective := facility_service.get_instance(objective_id) if objective == null or objective.durability <= 0: return {&"ok": false, &"error": "Configured threat objective is unavailable."} if not _defense_lock_owner(objective_id, &"ASSET", raid_id).is_empty(): return {&"ok": false, &"error": "Configured threat objective already belongs to another active incident."} return {&"ok": true, &"raid_id": raid_id, &"definition": definition, &"is_replay": false} func _prepare_event_threat_template(action: EventAction, source_id: StringName, raid_id: StringName) -> Dictionary: var raw_definition: Variant = action.payload.get(&"definition", action.payload.get("definition", null)) if not raw_definition is Dictionary: return {&"ok": false, &"error": "Configured threat requires its complete definition."} var definition: Dictionary = raw_definition.duplicate(true) # Do not silently turn service defaults into newly approved enemy values. for field: StringName in [&"round_minutes", &"max_duration_minutes", &"defender_retreat_threshold", &"terrain_bonus", &"command_bonus", &"surprise_penalty", &"multiple_entry_penalty"]: if typeof(definition.get(field, definition.get(String(field), null))) != TYPE_INT: return {&"ok": false, &"error": "Configured threat requires an explicit integer: %s." % field} var retreat_value: Variant = definition.get(&"retreat_policy", definition.get("retreat_policy", null)) if typeof(retreat_value) not in [TYPE_STRING, TYPE_STRING_NAME]: return {&"ok": false, &"error": "Configured threat requires an explicit retreat policy."} for field: StringName in [&"entry_points", &"objective_ids"]: var raw_ids: Variant = definition.get(field, definition.get(String(field), null)) if not raw_ids is Array or raw_ids.is_empty(): return {&"ok": false, &"error": "Configured threat requires explicit %s." % field} for raw_id: Variant in raw_ids: if typeof(raw_id) not in [TYPE_STRING, TYPE_STRING_NAME] or String(raw_id).is_empty(): return {&"ok": false, &"error": "Configured threat entry/objective IDs must be nonempty names."} var raw_waves: Variant = definition.get(&"waves", definition.get("waves", null)) if not raw_waves is Array or raw_waves.is_empty(): return {&"ok": false, &"error": "Configured threat requires explicit waves."} for raw_wave: Variant in raw_waves: if not raw_wave is Dictionary: return {&"ok": false, &"error": "Configured threat wave must be a dictionary."} for field: StringName in [&"threat_strength", &"max_rounds", &"retreat_threshold"]: if typeof(raw_wave.get(field, raw_wave.get(String(field), null))) != TYPE_INT: return {&"ok": false, &"error": "Configured threat wave requires an explicit integer: %s." % field} definition[&"source_id"] = source_id # An isolated aggregate validates and canonicalizes without touching live # clocks, RNG, IDs, facilities, events or the queue. Time zero is only the # canonical probe's timestamp, never the actual incident creation time. definition.erase(&"event_source") definition.erase("event_source") var probe := DefenseService.new() var valid := probe.create_raid(raid_id, 0, definition) if not valid.is_success(): return {&"ok": false, &"error": valid.message, &"details": valid.details} var canonical: Dictionary = (probe.create_snapshot()[&"raids"] as Array)[0] var digest := JSON.stringify(canonical, "", true, true).sha256_text() var canonical_raid := probe.get_raid(raid_id) return {&"ok": true, &"definition": definition, &"digest": digest, &"objective_ids": canonical_raid[&"objective_ids"], &"canonical_raid": canonical_raid} func _apply_event_defense_raid(action: EventAction, context: Dictionary) -> Dictionary: var request := _build_event_defense_request(action, context) if not bool(request.get(&"ok", false)): return request if bool(request.get(&"is_replay", false)): return {&"ok": true, &"raid_id": request[&"raid_id"], &"is_replay": true} var advanced := defense_service.advance_time(_defense_operation_minute()) if not advanced.is_success(): return {&"ok": false, &"error": advanced.message} var created := defense_service.create_raid(request[&"raid_id"], _defense_operation_minute(), request[&"definition"]) if not created.is_success(): return {&"ok": false, &"error": created.message} if not _validate_live_defense_crosslinks(): return {&"ok": false, &"error": "Configured event threat violates live Defense crosslinks."} return {&"ok": true, &"raid_id": request[&"raid_id"], &"is_replay": false} ## Historical provenance is checked against the saved event definition and ## cause context, without requiring a departed guest or repaid loan to exist. func _validate_event_defense_crosslinks(events: EventService, defense: DefenseService) -> bool: for raid_id: StringName in defense.get_raid_ids(): var raid := defense.get_raid(raid_id) var source: Dictionary = raid.get(&"event_source", {}) if source.is_empty(): continue var instance_id := StringName(source["event_instance_id"]) var action_id := StringName(source["event_action_id"]) var identity := JSON.stringify([String(instance_id), String(action_id)], "", true, true) if raid_id != StringName("event_defense_raid." + identity.sha256_text()): return false var instance := events.get_instance(instance_id) if instance == null: return false var source_value: Variant = instance.context.get(&"threat_source_id", instance.context.get("threat_source_id", null)) if typeof(source_value) not in [TYPE_STRING, TYPE_STRING_NAME] or StringName(source_value) != raid[&"source_id"]: return false var definition := events.get_definition(instance.resolved_event_id) if definition == null: return false var actions: Array[EventAction] = [] for collection: Array in [definition.automatic_actions, definition.fallback_actions, definition.failure_actions]: actions.append_array(collection) for choice: EventChoice in definition.choices: actions.append_array(choice.actions) var matches := false for action: EventAction in actions: if action.action_id != action_id or action.type != EventAction.Type.CREATE_DEFENSE_RAID: continue if String(action.payload.get(&"template_id", action.payload.get("template_id", ""))) != source["template_id"] or String(action.payload.get(&"source_kind", action.payload.get("source_kind", ""))).to_upper() != source["source_kind"]: continue var prepared := _prepare_event_threat_template(action, StringName(source_value), raid_id) if bool(prepared.get(&"ok", false)) and String(prepared[&"digest"]) == source["template_digest"] and _event_threat_template_matches_raid(raid, prepared[&"canonical_raid"]): matches = true break if not matches: return false return true ## Compare the immutable creation configuration to its original template. ## Combat progress, damage, roster, timestamps and player retreat-policy edits ## remain mutable and must not be compared to a newly-created probe. func _event_threat_template_matches_raid(raid: Dictionary, canonical: Dictionary) -> bool: for field: StringName in [&"raid_id", &"source_id", &"max_duration_minutes", &"round_minutes", &"terrain_bonus", &"command_bonus", &"surprise_penalty", &"multiple_entry_penalty", &"entry_points", &"objective_ids"]: if not _strict_variants_equal(raid.get(field, null), canonical.get(field, null)): return false var waves: Array = raid.get(&"waves", []) var template_waves: Array = canonical.get(&"waves", []) if waves.size() != template_waves.size(): return false for index: int in range(waves.size()): var wave: Dictionary = waves[index] var template_wave: Dictionary = template_waves[index] for field: StringName in [&"wave_id", &"threat_strength", &"retreat_threshold", &"max_rounds", &"entry_points", &"objective_ids"]: if not _strict_variants_equal(wave.get(field, null), template_wave.get(field, null)): return false return true