extends GutTest const VillagerOpportunityNoteScript := preload("res://player/VillagerOpportunityNote.gd") func test_no_opportunity_produces_no_need() -> void: var note := VillagerOpportunityNote.derive(null, null, null, "", null, "", "") assert_not_null(note) assert_false(note.has_need) assert_eq(note.help_kind, &"") func test_player_route_wins_over_helper_when_derivable() -> void: var opportunity := _open_pantry_opportunity() var response := _player_response() var helper := _helper(7) var pantry := _pantry(0.0, 100.0) var note := VillagerOpportunityNote.derive( opportunity, response, helper, "Tarik", pantry, "Village Pantry", "food" ) assert_true(note.has_need) assert_eq(note.help_kind, VillagerOpportunityNote.HELP_PLAYER_ROUTE) assert_eq(note.need_text, "The Village Pantry needs food") assert_true(note.help_text.contains("You can help")) assert_true(note.help_text.contains("Village Pantry")) func test_helper_is_named_when_no_player_route() -> void: var opportunity := _open_pantry_opportunity() var pantry := _pantry(0.0, 100.0) var note := VillagerOpportunityNote.derive( opportunity, null, _helper(7), "Tarik", pantry, "Village Pantry", "food" ) assert_true(note.has_need) assert_eq(note.help_kind, VillagerOpportunityNote.HELP_HELPER) assert_true(note.help_text.contains("Tarik")) assert_true(note.help_text.contains("food")) func test_unavailable_reports_no_room_when_storage_is_full() -> void: var opportunity := _open_pantry_opportunity() var pantry := _pantry(0.0, 0.5) var note := VillagerOpportunityNote.derive( opportunity, null, null, "", pantry, "Village Pantry", "food" ) assert_true(note.has_need) assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE) assert_true(note.help_text.contains("no room")) func test_unavailable_reports_need_already_met() -> void: var opportunity := _open_pantry_opportunity() var pantry := _pantry(1.0, 100.0) var note := VillagerOpportunityNote.derive( opportunity, null, null, "", pantry, "Village Pantry", "food" ) assert_true(note.has_need) assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE) assert_true(note.help_text.contains("already has enough")) func test_unavailable_reports_no_stocked_source_when_storage_has_room() -> void: var opportunity := _open_pantry_opportunity() var pantry := _pantry(0.0, 100.0) var note := VillagerOpportunityNote.derive( opportunity, null, null, "", pantry, "Village Pantry", "food" ) assert_true(note.has_need) assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE) assert_true(note.help_text.contains("No stocked source")) func test_unavailable_reports_missing_storage() -> void: var opportunity := _open_pantry_opportunity() var note := VillagerOpportunityNote.derive( opportunity, null, null, "", null, "Village Pantry", "food" ) assert_true(note.has_need) assert_eq(note.help_kind, VillagerOpportunityNote.HELP_UNAVAILABLE) assert_true(note.help_text.contains("No storage")) func test_cache_key_uses_need_and_help_state() -> void: var opportunity := _open_pantry_opportunity() var pantry := _pantry(0.0, 100.0) var helper_note := VillagerOpportunityNote.derive( opportunity, null, _helper(7), "Tarik", pantry, "Village Pantry", "food" ) var unavailable_note := VillagerOpportunityNote.derive( opportunity, null, null, "", pantry, "Village Pantry", "food" ) var no_need := VillagerOpportunityNote.derive(null, null, null, "", null, "", "") assert_ne(helper_note.cache_key(), unavailable_note.cache_key()) assert_ne(no_need.cache_key(), helper_note.cache_key()) func _open_pantry_opportunity() -> OpportunityStateRecord: return OpportunityStateRecord.create( 0, 10, 5, 3, 1.0, SimulationIds.OPPORTUNITY_RESTOCK_EMPTY_PANTRY, SimulationIds.STORAGE_VILLAGE_PANTRY, SimulationIds.RESOURCE_FOOD ) func _player_response() -> OpportunityPlayerResponseResult: return ( OpportunityPlayerResponseResult . new( { "opportunity_id": 0, "trigger_event_id": 5, "action_id": SimulationIds.ACTION_GATHER_FOOD, "resource_id": SimulationIds.RESOURCE_FOOD, "target_id": SimulationIds.STORAGE_VILLAGE_PANTRY, "available_source_count": 2, "reason": "No capable helper; 2 player-usable finite Food sources can supply village_pantry", } ) ) func _helper(helper_id: int) -> OpportunityHelperResult: return ( OpportunityHelperResult . new( { "opportunity_id": 0, "helper_npc_id": helper_id, "action_id": SimulationIds.ACTION_GATHER_FOOD, "source_id": "", "resource_id": SimulationIds.RESOURCE_FOOD, "trigger_event_id": 5, "trust": 0.8, "familiarity": 0.5, "uses_inventory": false, "available_source_count": 1, "profession_match": true, "reason": "Knows the need; trust 0.80 toward Amina; has 1 available finite Food source", } ) ) func _pantry(amount: float, capacity: float) -> StorageStateRecord: return StorageStateRecord.create( SimulationIds.STORAGE_VILLAGE_PANTRY, {SimulationIds.RESOURCE_FOOD: amount}, capacity )