extends GutTest var registry: WorldTargetRegistry var village: SimVillage var revision := 0 var next_event_id := 0 var events: Array[Dictionary] = [] var player_position := Vector3.ZERO func before_each() -> void: registry = WorldTargetRegistry.new(&"jajce") village = SimVillage.new() revision = 0 next_event_id = 0 events.clear() player_position = Vector3.ZERO func test_player_and_npc_use_one_revalidated_activity_command_path() -> void: var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(2.0, 0.0, 0.0) ) var study := _register_activity( &"study_desk", SimulationIds.ACTION_STUDY, Vector3(2.0, 0.0, 1.0) ) var service := _service() var safety_before := village.safety var player_result := service.submit_command( _command(&"player_guard", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_true(player_result.did_succeed()) assert_eq(village.safety, safety_before + 3.0) assert_eq(player_result.get_payload()["event_id"], 0) assert_eq(player_result.get_payload()["state_revision"], 1) assert_eq(events[0]["actor"], _player_ref().index_key()) assert_eq(events[0]["target_id"], "guard_post") assert_eq(events[0]["metric_id"], "safety") var npc_result := service.submit_command( _command( &"npc_guard", WorldEntityRef.create(SimulationIds.ENTITY_PERSON, &"7"), SimulationIds.ACTION_PATROL, guard, 1 ) ) assert_true(npc_result.did_succeed()) assert_eq(village.safety, safety_before + 6.0) assert_eq( events[1]["actor"], WorldEntityRef.create(SimulationIds.ENTITY_PERSON, &"7").index_key() ) var study_result := service.submit_command( _command(&"player_study", _player_ref(), SimulationIds.ACTION_STUDY, study, 2) ) assert_true(study_result.did_succeed()) assert_eq(village.knowledge, 2.0) assert_eq(events[2]["metric_id"], "knowledge") func test_stale_revision_handle_capability_and_range_reject_without_mutation() -> void: var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(2.0, 0.0, 0.0) ) var study := _register_activity( &"study_desk", SimulationIds.ACTION_STUDY, Vector3(2.0, 0.0, 1.0) ) var service := _service() var before := _snapshot() var stale_revision := service.submit_command( _command(&"stale_revision", _player_ref(), SimulationIds.ACTION_PATROL, guard, 4) ) assert_eq(stale_revision.get_reason_code(), ActivityActionCommandService.REASON_STALE_REVISION) assert_eq(_snapshot(), before) var wrong_capability := service.submit_command( _command(&"wrong_capability", _player_ref(), SimulationIds.ACTION_PATROL, study, 0) ) assert_eq( wrong_capability.get_reason_code(), ActivityActionCommandService.REASON_TARGET_CAPABILITY ) assert_eq(_snapshot(), before) player_position = Vector3(20.0, 0.0, 0.0) var out_of_range := service.submit_command( _command(&"out_of_range", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_eq(out_of_range.get_reason_code(), ActivityActionCommandService.REASON_OUT_OF_RANGE) assert_eq(_snapshot(), before) player_position = Vector3.ZERO assert_true(registry.unregister_target(guard)) var stale_handle := service.submit_command( _command(&"stale_handle", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_eq(stale_handle.get_reason_code(), ActivityActionCommandService.REASON_STALE_TARGET) assert_eq(_snapshot(), before) func test_unsupported_action_definition_cannot_smuggle_a_metric_effect() -> void: var guard := _register_activity(&"guard_post", SimulationIds.ACTION_PATROL, Vector3.ZERO) var before := _snapshot() var result := _service().submit_command( _command(&"wrong_action", _player_ref(), SimulationIds.ACTION_REST, guard, 0) ) assert_eq(result.get_reason_code(), ActivityActionCommandService.REASON_UNSUPPORTED_ACTION) assert_eq(_snapshot(), before) func test_compound_effects_fail_closed_until_one_transactional_handler_owns_them() -> void: var guard := _register_activity(&"guard_post", SimulationIds.ACTION_PATROL, Vector3.ZERO) var service := ActivityActionCommandService.new( registry, _compound_definition, _actor_position, func() -> int: return revision, _complete_activity ) var before := _snapshot() var result := service.submit_command( _command(&"compound_action", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_eq(result.get_reason_code(), ActivityActionCommandService.REASON_INVALID_EFFECT) assert_eq(_snapshot(), before) func test_abstract_npc_target_does_not_require_a_loaded_presentation_provider() -> void: var abstract_guard := _register_activity( &"abstract_guard_post", SimulationIds.ACTION_PATROL, Vector3(1.0, 0.0, 0.0), false ) player_position = Vector3(100.0, 0.0, 0.0) var result := _service(false, false).submit_command( _command( &"abstract_npc_guard", WorldEntityRef.create(SimulationIds.ENTITY_PERSON, &"12"), SimulationIds.ACTION_PATROL, abstract_guard, 0 ) ) assert_true(result.did_succeed()) assert_eq(village.safety, 53.0) assert_true(registry.is_handle_valid(abstract_guard)) assert_false(registry.is_handle_valid(abstract_guard, true)) assert_has(result.get_payload()["reason_trace"], "abstract_completion_authorized") func test_loaded_service_rejects_a_dead_provider_even_before_registry_pruning() -> void: var provider := Node.new() var handle := registry.register_target( WorldTargetDescriptor.new( &"guard_post", &"activity", [WorldTargetCapability.new(SimulationIds.ACTION_PATROL, {"interaction_range": 3.0})], Vector3.ZERO ), provider ) provider.free() var result := _service(true).submit_command( _command(&"dead_provider", _player_ref(), SimulationIds.ACTION_PATROL, handle, 0) ) assert_eq(result.get_reason_code(), ActivityActionCommandService.REASON_STALE_TARGET) assert_true(events.is_empty()) func test_invalid_actor_position_rejects_in_loaded_and_abstract_modes() -> void: var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(2.0, 0.0, 0.0) ) var dead_position_lookup := func(_actor: WorldEntityRef) -> Variant: return null for enforce_spatial_range in [true, false]: var service := ActivityActionCommandService.new( registry, _definition, dead_position_lookup, func() -> int: return revision, _complete_activity, false, enforce_spatial_range ) var result := service.submit_command( _command( StringName("dead_%s" % enforce_spatial_range), WorldEntityRef.create(SimulationIds.ENTITY_PERSON, &"12"), SimulationIds.ACTION_PATROL, guard, 0 ) ) assert_eq(result.get_reason_code(), ActivityActionCommandService.REASON_UNSUPPORTED_ACTOR) assert_true(events.is_empty()) assert_eq(revision, 0) func test_non_activity_target_cannot_smuggle_an_activity_capability() -> void: var provider := Node.new() add_child_autofree(provider) var storage_handle := registry.register_target( WorldTargetDescriptor.new( &"false_guard", &"storage", [WorldTargetCapability.new(SimulationIds.ACTION_PATROL)], Vector3.ZERO ), provider ) var result := _service().submit_command( _command( &"wrong_kind", _player_ref(), SimulationIds.ACTION_PATROL, storage_handle, revision ) ) assert_eq(result.get_reason_code(), ActivityActionCommandService.REASON_TARGET_CAPABILITY) assert_true(events.is_empty()) func test_event_position_comes_from_authoritative_actor_not_presentation_target() -> void: player_position = Vector3(7.0, 0.0, -3.0) var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(200.0, 0.0, 200.0) ) var result := _service(false, false).submit_command( _command(&"abstract_position", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_true(result.did_succeed()) assert_eq(events[0]["world_position"], player_position) func test_rejected_atomic_completion_leaves_authority_unchanged() -> void: var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(1.0, 0.0, 0.0) ) var service := ActivityActionCommandService.new( registry, _definition, _actor_position, func() -> int: return revision, func( _actor: WorldEntityRef, _action_id: StringName, _target_id: StringName, _metric_id: StringName, _amount: float, _parameters: Dictionary, _world_position: Vector3 ) -> Dictionary: return {"succeeded": false, "event_id": -1, "state_revision": revision} ) var before := _snapshot() var result := service.submit_command( _command(&"rejected_completion", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_eq(result.get_status(), ActionResult.STATUS_FAILED) assert_eq(result.get_reason_code(), ActivityActionCommandService.REASON_COMPLETION_FAILED) assert_eq(_snapshot(), before) func test_completion_results_cannot_hide_or_forge_revision_changes() -> void: var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(1.0, 0.0, 0.0) ) var hidden_mutation_service := ActivityActionCommandService.new( registry, _definition, _actor_position, func() -> int: return revision, func( _actor: WorldEntityRef, _action_id: StringName, _target_id: StringName, _metric_id: StringName, _amount: float, _parameters: Dictionary, _world_position: Vector3 ) -> Dictionary: return { "succeeded": false, "reason_code": "rejected", "message": "Rejected", "event_id": -1, "state_revision": revision + 1, } ) var hidden_mutation := hidden_mutation_service.submit_command( _command(&"hidden_mutation", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_eq(hidden_mutation.get_status(), ActionResult.STATUS_FAILED) assert_eq( hidden_mutation.get_reason_code(), ActivityActionCommandService.REASON_COMPLETION_FAILED ) var forged_fact_service := ActivityActionCommandService.new( registry, _definition, _actor_position, func() -> int: return revision, func( _actor: WorldEntityRef, _action_id: StringName, _target_id: StringName, _metric_id: StringName, _amount: float, _parameters: Dictionary, _world_position: Vector3 ) -> Dictionary: return { "succeeded": true, "event_id": revision + 4, "state_revision": revision + 1, } ) var forged_fact := forged_fact_service.submit_command( _command(&"forged_fact", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_eq(forged_fact.get_status(), ActionResult.STATUS_FAILED) assert_eq(forged_fact.get_reason_code(), ActivityActionCommandService.REASON_COMPLETION_FAILED) func test_completion_accepts_ordered_causal_followup_facts() -> void: var guard := _register_activity( &"guard_post", SimulationIds.ACTION_PATROL, Vector3(1.0, 0.0, 0.0) ) var service := ActivityActionCommandService.new( registry, _definition, _actor_position, func() -> int: return revision, func( _actor: WorldEntityRef, _action_id: StringName, _target_id: StringName, _metric_id: StringName, _amount: float, _parameters: Dictionary, _world_position: Vector3 ) -> Dictionary: var completion_event_id := revision revision += 2 return { "succeeded": true, "event_id": completion_event_id, "state_revision": revision, "applied_amount": 3.0, } ) var result := service.submit_command( _command(&"causal_chain", _player_ref(), SimulationIds.ACTION_PATROL, guard, 0) ) assert_true(result.did_succeed()) assert_eq(int(result.get_payload()["event_id"]), 0) assert_eq(int(result.get_payload()["state_revision"]), 2) assert_has(result.get_payload()["reason_trace"], "causal_followup_facts_committed") func _service( require_target_provider: bool = false, enforce_spatial_range: bool = true ) -> ActivityActionCommandService: return ActivityActionCommandService.new( registry, _definition, _actor_position, func() -> int: return revision, _complete_activity, require_target_provider, enforce_spatial_range ) func _definition(action_id: StringName) -> ActionDefinition: if action_id not in [SimulationIds.ACTION_PATROL, SimulationIds.ACTION_STUDY]: return null var definition := ActionDefinition.new() definition.action_id = action_id definition.display_name = String(action_id) definition.completion_handler_id = ActivityActionCommandService.HANDLER_METRIC_DELTA var effect := ActionEffect.new() var is_patrol := action_id == SimulationIds.ACTION_PATROL effect.effect_id = &"patrol_safety" if is_patrol else &"study_knowledge" effect.effect_kind = ActionEffect.KIND_METRIC_DELTA effect.subject_key = &"safety" if is_patrol else &"knowledge" effect.value = 3.0 if is_patrol else 2.0 definition.effects = [effect] return definition func _compound_definition(action_id: StringName) -> ActionDefinition: var definition := _definition(action_id) var healing := ActionEffect.new() healing.effect_id = &"smuggled_healing" healing.effect_kind = ActionEffect.KIND_HEALING healing.amount = 2.0 definition.effects.append(healing) return definition func _actor_position(_actor: WorldEntityRef) -> Vector3: return player_position func _complete_activity( actor: WorldEntityRef, _action_id: StringName, target_id: StringName, metric_id: StringName, amount: float, _parameters: Dictionary, _world_position: Vector3 ) -> Dictionary: village.apply_metric_delta(metric_id, amount) revision += 1 var event_id := next_event_id next_event_id += 1 ( events . append( { "event_id": event_id, "actor": actor.index_key(), "target_id": String(target_id), "metric_id": String(metric_id), "amount": amount, "world_position": _world_position, } ) ) return {"succeeded": true, "event_id": event_id, "state_revision": revision} func _register_activity( target_id: StringName, action_id: StringName, position: Vector3, with_provider: bool = true ) -> WorldTargetHandle: var provider: Node if with_provider: provider = Node.new() add_child_autofree(provider) var capability := WorldTargetCapability.new(action_id, {"interaction_range": 3.0}) return registry.register_target( WorldTargetDescriptor.new(target_id, &"activity", [capability], position), provider ) func _command( command_id: StringName, actor: WorldEntityRef, action_id: StringName, target: WorldTargetHandle, expected_revision: int ) -> ActionCommand: return ActionCommand.new(command_id, actor, action_id, target, {}, expected_revision) func _player_ref() -> WorldEntityRef: return WorldEntityRef.create(SimulationIds.ENTITY_PLAYER, &"-1") func _snapshot() -> Dictionary: return { "safety": village.safety, "knowledge": village.knowledge, "revision": revision, "events": events.duplicate(true), }