feat: add person history and trust reactions
This commit is contained in:
@@ -59,18 +59,38 @@ func _run() -> void:
|
||||
),
|
||||
"Runtime knowledge UI check should use a completed simulation transaction"
|
||||
)
|
||||
var world_view := main_scene.get_node("WorldViewManager")
|
||||
var contributor_visual: Node3D = world_view.active_npc_visuals[contributor.id]
|
||||
var witness_visual: Node3D = world_view.active_npc_visuals[witness.id]
|
||||
var listener_visual: Node3D = world_view.active_npc_visuals[listener.id]
|
||||
_check(
|
||||
(
|
||||
witness_visual.get_node("TrustReactionRoot").visible
|
||||
and not contributor_visual.get_node("TrustReactionRoot").visible
|
||||
),
|
||||
"A real trust consequence should blossom only above the reacting observer"
|
||||
)
|
||||
var reaction_checksum: String = simulation_manager.get_state_checksum()
|
||||
witness_visual.play_trust_gain_reaction()
|
||||
_check(
|
||||
simulation_manager.get_state_checksum() == reaction_checksum,
|
||||
"Restarting the transient trust blossom must not mutate simulation state"
|
||||
)
|
||||
var village_ui := main_scene.get_node("UI")
|
||||
village_ui.selected_npc_index = witness.id
|
||||
village_ui.call("_refresh_npc_inspector")
|
||||
_check(
|
||||
(
|
||||
"Known fact: %s deposited" % contributor.npc_name in inspector_label.text
|
||||
and "witnessed · lasting" in inspector_label.text
|
||||
and "Memory: 1 lasting · 0 recent" in inspector_label.text
|
||||
"Memories 1 lasting · 0 recent" in inspector_label.text
|
||||
and (
|
||||
"◆ lasting · %s stocked 1 food · witnessed" % contributor.npc_name
|
||||
in inspector_label.text
|
||||
)
|
||||
and "Relationship: %s" % contributor.npc_name in inspector_label.text
|
||||
and "Because:" in inspector_label.text
|
||||
and "Own history" in inspector_label.text
|
||||
),
|
||||
"NPC inspector should show the witnessed fact and its relationship consequence"
|
||||
"NPC history should show the lasting witnessed fact and its relationship consequence"
|
||||
)
|
||||
var guard_site := (
|
||||
main_scene.get_node("JajceWorld/WorldObjects/ActivitySites/GuardPost") as ActivitySite
|
||||
@@ -87,11 +107,17 @@ func _run() -> void:
|
||||
village_ui.call("_refresh_npc_inspector")
|
||||
_check(
|
||||
(
|
||||
"Known fact: %s deposited" % contributor.npc_name in inspector_label.text
|
||||
and "heard from %s · recent" % witness.npc_name in inspector_label.text
|
||||
and "Memory: 0 lasting · 1 recent" in inspector_label.text
|
||||
"Memories 0 lasting · 1 recent" in inspector_label.text
|
||||
and (
|
||||
"• recent · %s stocked 1 food · heard %s" % [contributor.npc_name, witness.npc_name]
|
||||
in inspector_label.text
|
||||
)
|
||||
),
|
||||
"NPC inspector should identify who communicated a known fact"
|
||||
"NPC history should identify who communicated a retained fact"
|
||||
)
|
||||
_check(
|
||||
not listener_visual.get_node("TrustReactionRoot").visible,
|
||||
"Learning a fact without a relationship consequence should not show a trust blossom"
|
||||
)
|
||||
_check(
|
||||
main_scene.has_node("JajceWorld/TerrainRoot/Terrain3D"),
|
||||
|
||||
@@ -84,6 +84,17 @@ func _run() -> void:
|
||||
),
|
||||
"Memory summaries should distinguish lasting and recent knowledge"
|
||||
)
|
||||
var lasting_history: Array = manager.get_retained_memory_events(lasting_listener.id, 4)
|
||||
var recent_history: Array = manager.get_retained_memory_events(recent_listener.id, 4)
|
||||
_check(
|
||||
(
|
||||
lasting_history.size() == 1
|
||||
and lasting_history[0] == deposit_event
|
||||
and recent_history.size() == 1
|
||||
and recent_history[0] == deposit_event
|
||||
),
|
||||
"Person-history queries should resolve each retained fact to objective history"
|
||||
)
|
||||
|
||||
var objective_event_count: int = manager.economic_events.size()
|
||||
var objective_next_event_id: int = manager.next_event_id
|
||||
@@ -148,6 +159,13 @@ func _run() -> void:
|
||||
),
|
||||
"Forgetting knowledge must not prune or replay objective event history"
|
||||
)
|
||||
_check(
|
||||
(
|
||||
manager.get_retained_memory_events(recent_listener.id, 4).is_empty()
|
||||
and deposit_event in manager.get_npc_events(contributor.id, 3)
|
||||
),
|
||||
"Forgotten subjective facts should leave the contributor's objective personal history intact"
|
||||
)
|
||||
var lasting_relationship: RelationshipStateRecord = (
|
||||
manager.relationship_system.get_relationship(lasting_listener.id, contributor.id)
|
||||
)
|
||||
|
||||
@@ -23,6 +23,8 @@ func _run() -> void:
|
||||
visual.set_physics_process(false)
|
||||
var profession_definition := SimulationDefinitions.get_profession(npc.profession)
|
||||
var body: MeshInstance3D = visual.get_node("MeshInstance3D")
|
||||
var trust_reaction_root: Node3D = visual.get_node("TrustReactionRoot")
|
||||
_check(not trust_reaction_root.visible, "A newly spawned visual should not replay reactions")
|
||||
_check(
|
||||
body.material_override.albedo_color == profession_definition.visual_color,
|
||||
"NPC body color should come from its profession definition"
|
||||
@@ -56,12 +58,56 @@ func _run() -> void:
|
||||
not visual.get_node("ProfessionLabel").visible and visual.get_node("TaskGlyphRoot").visible,
|
||||
"Cinematic mode should hide debug labels while preserving task glyphs"
|
||||
)
|
||||
visual.play_trust_gain_reaction()
|
||||
_check(trust_reaction_root.visible, "A direct trust reaction should reveal the blossom")
|
||||
visual.play_trust_gain_reaction()
|
||||
_check(
|
||||
trust_reaction_root.visible and trust_reaction_root.get_child_count() == 4,
|
||||
"Restarting a trust reaction should reuse one blossom without stacking visuals"
|
||||
)
|
||||
visual.set_debug_overlay_visible(false)
|
||||
_check(
|
||||
trust_reaction_root.visible,
|
||||
"The trust reaction should remain visible in cinematic presentation mode"
|
||||
)
|
||||
var active_reaction_tween: Tween = visual.get("trust_reaction_tween")
|
||||
await active_reaction_tween.finished
|
||||
_check(
|
||||
not trust_reaction_root.visible,
|
||||
"The trust reaction should fade away instead of becoming permanent world clutter"
|
||||
)
|
||||
visual.set_debug_overlay_visible(true)
|
||||
visual.set_task_presentation(SimulationIds.ACTION_WANDER, SimNPC.TASK_STATE_TRAVELING)
|
||||
_check(
|
||||
not visual.get_node("TaskGlyphRoot").visible,
|
||||
"Ambient wander should not display a task glyph"
|
||||
)
|
||||
var routed_observer: SimNPC = manager.npcs[1]
|
||||
var routed_subject: SimNPC = manager.npcs[2]
|
||||
var observer_visual: Node3D = view.active_npc_visuals[routed_observer.id]
|
||||
var subject_visual: Node3D = view.active_npc_visuals[routed_subject.id]
|
||||
var routed_relationship := RelationshipStateRecord.create(
|
||||
routed_observer.id, routed_subject.id, 0.5, 0.65, 9001
|
||||
)
|
||||
var routed_event := EconomicEventRecord.create(
|
||||
9001,
|
||||
SimulationIds.EVENT_STORAGE_DEPOSITED,
|
||||
manager.tick_count,
|
||||
routed_subject.id,
|
||||
&"test_source",
|
||||
SimulationIds.STORAGE_VILLAGE_PANTRY,
|
||||
SimulationIds.RESOURCE_FOOD,
|
||||
1.0
|
||||
)
|
||||
manager.emit_signal("relationship_changed", routed_relationship, routed_event)
|
||||
_check(
|
||||
observer_visual.get_node("TrustReactionRoot").visible,
|
||||
"The authoritative relationship signal should route a reaction to its observer"
|
||||
)
|
||||
_check(
|
||||
not subject_visual.get_node("TrustReactionRoot").visible,
|
||||
"A routed trust reaction should not play on the relationship subject"
|
||||
)
|
||||
var test_decision := ActionSelectionResult.new(
|
||||
SimulationIds.ACTION_GATHER_FOOD,
|
||||
-1.0,
|
||||
@@ -176,6 +222,16 @@ func _run() -> void:
|
||||
visual_after_restore.global_position.is_equal_approx(npc.position),
|
||||
"Rebuilt presentation should use the restored authoritative position"
|
||||
)
|
||||
_check(
|
||||
not visual_after_restore.get_node("TrustReactionRoot").visible,
|
||||
"Restoring state should rebuild the visual without replaying transient reactions"
|
||||
)
|
||||
visual_after_restore.play_trust_gain_reaction()
|
||||
visual_after_restore.apply_dead_visual_state()
|
||||
_check(
|
||||
not visual_after_restore.get_node("TrustReactionRoot").visible,
|
||||
"Death presentation should suppress an active trust reaction"
|
||||
)
|
||||
|
||||
if failures.is_empty():
|
||||
print("[TEST] NPC visual lifecycle passed")
|
||||
|
||||
@@ -4,6 +4,7 @@ const SimulationEventLogScript := preload("res://simulation/events/SimulationEve
|
||||
const VillageEconomyScript := preload("res://simulation/economy/VillageEconomy.gd")
|
||||
const RelationshipSystemScript := preload("res://simulation/relationships/RelationshipSystem.gd")
|
||||
const EventKnowledgeSystemScript := preload("res://simulation/knowledge/EventKnowledgeSystem.gd")
|
||||
const SimulationManagerScript := preload("res://simulation/SimulationManager.gd")
|
||||
|
||||
|
||||
func test_storage_never_accepts_more_than_its_capacity() -> void:
|
||||
@@ -211,9 +212,48 @@ func test_knowledge_retention_is_bounded_stable_and_importance_ranked() -> void:
|
||||
|
||||
var ranked_system := EventKnowledgeSystemScript.new()
|
||||
var ranked_records: Array[KnownEventStateRecord] = [
|
||||
KnownEventStateRecord.create(1, 1, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED, -1, 10),
|
||||
KnownEventStateRecord.create(1, 2, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED, -1, 20),
|
||||
KnownEventStateRecord.create(1, 10, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED, -1, 5),
|
||||
KnownEventStateRecord.create(1, 20, SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED, -1, 20),
|
||||
KnownEventStateRecord.create(
|
||||
1,
|
||||
30,
|
||||
SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED,
|
||||
2,
|
||||
20,
|
||||
SimulationIds.KNOWLEDGE_ACQUISITION_WITNESSED
|
||||
),
|
||||
KnownEventStateRecord.create(1, 40, SimulationIds.KNOWLEDGE_ACQUISITION_LEGACY, -1, 10),
|
||||
]
|
||||
ranked_system.restore(ranked_records)
|
||||
var lasting_event_ids: Array[int] = [1]
|
||||
assert_eq(ranked_system.get_communicable_event_ids(1, lasting_event_ids), [1, 2])
|
||||
var lasting_event_ids: Array[int] = [10]
|
||||
assert_eq(ranked_system.get_retained_event_ids(1, lasting_event_ids, 0), [10, 30, 20, 40])
|
||||
assert_eq(ranked_system.get_retained_event_ids(1, lasting_event_ids, 2), [10, 30])
|
||||
assert_eq(ranked_system.get_communicable_event_ids(1, lasting_event_ids), [10, 20])
|
||||
|
||||
var objective_events: Array[EconomicEventRecord] = []
|
||||
for event_id in [10, 20, 30, 40]:
|
||||
objective_events.append(
|
||||
EconomicEventRecord.create(
|
||||
event_id,
|
||||
SimulationIds.EVENT_STORAGE_DEPOSITED,
|
||||
0,
|
||||
0,
|
||||
SimulationIds.npc_inventory_id(0),
|
||||
SimulationIds.STORAGE_VILLAGE_PANTRY,
|
||||
SimulationIds.RESOURCE_FOOD,
|
||||
1.0
|
||||
)
|
||||
)
|
||||
var manager := SimulationManagerScript.new()
|
||||
manager.event_log.restore(objective_events, 41)
|
||||
manager.event_knowledge_system.restore(ranked_records)
|
||||
var relationships: Array[RelationshipStateRecord] = [
|
||||
RelationshipStateRecord.create(1, 0, 0.5, 0.65, 10)
|
||||
]
|
||||
manager.relationship_system.restore(relationships)
|
||||
var retained_events: Array[EconomicEventRecord] = manager.get_retained_memory_events(1, 3)
|
||||
var retained_ids: Array[int] = []
|
||||
for event in retained_events:
|
||||
retained_ids.append(int(event.data["event_id"]))
|
||||
assert_eq(retained_ids, [10, 30, 20])
|
||||
manager.free()
|
||||
|
||||
Reference in New Issue
Block a user