feat: add bounded npc memory retention

This commit is contained in:
Rijad Zuzo
2026-07-12 01:10:10 +02:00
parent 9a479ba182
commit 99b8146f75
17 changed files with 945 additions and 126 deletions
+67 -5
View File
@@ -17,6 +17,7 @@ signal npc_decision_recorded(npc: SimNPC, decision: ActionSelectionResult)
signal relationship_changed(relationship: RelationshipStateRecord, cause_event: EconomicEventRecord)
signal event_knowledge_changed(knower_id: int, event: EconomicEventRecord)
signal event_knowledge_transferred(speaker_id: int, listener_id: int, event: EconomicEventRecord)
signal event_knowledge_forgotten(knower_id: int, event: EconomicEventRecord)
var village := SimVillage.new()
@@ -158,6 +159,8 @@ func simulate_tick() -> void:
if village_was_changed:
village_changed.emit(village)
if tick_count % get_knowledge_review_interval() == 0:
_maintain_event_knowledge(true)
if debug_logs:
print(village.get_summary())
@@ -600,13 +603,15 @@ func try_communicate_at_shared_activity(speaker_id: int, listener_id: int) -> bo
var listener := _find_npc_by_id(listener_id)
if not _can_communicate_at_shared_activity(speaker, listener):
return false
var known_event_ids := event_knowledge_system.get_known_event_ids(speaker.id, 0)
for index in range(known_event_ids.size() - 1, -1, -1):
var event := event_log.get_by_id(known_event_ids[index])
var known_event_ids := event_knowledge_system.get_communicable_event_ids(
speaker.id, _get_lasting_event_ids(speaker.id)
)
for event_id in known_event_ids:
var event := event_log.get_by_id(event_id)
if event == null:
continue
var learned_record := event_knowledge_system.communicate_event(
event, speaker.id, listener.id
event, speaker.id, listener.id, tick_count
)
if learned_record == null:
continue
@@ -648,13 +653,51 @@ func _can_communicate_at_shared_activity(speaker: SimNPC, listener: SimNPC) -> b
func _apply_new_event_knowledge(
known_event: KnownEventStateRecord, event: EconomicEventRecord
) -> void:
event_knowledge_changed.emit(known_event.get_knower_id(), event)
var newly_informed_ids: Array[int] = [known_event.get_knower_id()]
var changed_relationships: Array[RelationshipStateRecord] = relationship_system.apply_event(
event, npcs, newly_informed_ids
)
event_knowledge_changed.emit(known_event.get_knower_id(), event)
for relationship in changed_relationships:
relationship_changed.emit(relationship, event)
_maintain_event_knowledge(false)
func _maintain_event_knowledge(expire_by_age: bool) -> void:
var forgotten := event_knowledge_system.maintain_retention(
tick_count, get_knowledge_review_interval(), _get_lasting_knowledge_records(), expire_by_age
)
for known_event in forgotten:
var event := event_log.get_by_id(known_event.get_event_id())
if event != null:
event_knowledge_forgotten.emit(known_event.get_knower_id(), event)
func _get_lasting_knowledge_records() -> Array[KnownEventStateRecord]:
var lasting: Array[KnownEventStateRecord] = []
var seen := {}
for relationship in relationship_system.get_all_sorted():
var event_id := relationship.get_last_trust_cause_event_id()
if event_id == RelationshipStateRecord.NO_CAUSE_EVENT:
continue
var record := event_knowledge_system.get_record(relationship.get_observer_id(), event_id)
if record == null:
continue
var key := "%d:%d" % [record.get_knower_id(), record.get_event_id()]
if seen.has(key):
continue
seen[key] = true
lasting.append(record)
return lasting
func _get_lasting_event_ids(npc_id: int) -> Array[int]:
var event_ids: Array[int] = []
for record in _get_lasting_knowledge_records():
if record.get_knower_id() == npc_id:
event_ids.append(record.get_event_id())
event_ids.sort()
return event_ids
func _find_npc_by_id(npc_id: int) -> SimNPC:
@@ -695,6 +738,24 @@ func get_known_event_record(npc_id: int, event_id: int) -> KnownEventStateRecord
return event_knowledge_system.get_record(npc_id, event_id)
func is_known_event_lasting(npc_id: int, event_id: int) -> bool:
return event_id in _get_lasting_event_ids(npc_id)
func get_memory_summary(npc_id: int) -> Dictionary:
var total := event_knowledge_system.get_known_event_ids(npc_id, 0).size()
var lasting := _get_lasting_event_ids(npc_id).size()
return {"lasting": lasting, "recent": maxi(total - lasting, 0)}
func get_knowledge_review_interval() -> int:
var active_cycle_duration := (
clock.cycle_duration_seconds if clock != null else cycle_duration_seconds
)
var active_tick_interval := clock.tick_interval if clock != null else tick_interval
return maxi(roundi(active_cycle_duration / maxf(active_tick_interval, 0.0001)), 1)
func get_current_speed() -> String:
return "%.2fx" % SPEED_LEVELS[speed_index]
@@ -1018,6 +1079,7 @@ func restore_state(record: SimulationStateRecord) -> bool:
npcs.append(npc_record.restore(debug_logs))
relationship_system.restore(record.relationships)
event_knowledge_system.restore(record.event_knowledge)
_maintain_event_knowledge(tick_count % get_knowledge_review_interval() == 0)
wander_random_sources.clear()
var wander_streams: Array = record.simulation["wander_random_streams"]