feat: deliver emergent Jajce commitments

This commit is contained in:
Rijad Zuzo
2026-08-12 21:15:58 +02:00
parent dc0f59fcf0
commit 6b50580598
28 changed files with 2145 additions and 206 deletions
+121 -26
View File
@@ -2,7 +2,7 @@ class_name SimulationStateRecord
extends RefCounted
const SCHEMA_NAME := "the_steward.simulation"
const SCHEMA_VERSION := 14
const SCHEMA_VERSION := 15
const LEGACY_SCHEMA_VERSION := 1
const EVENT_LEGACY_SCHEMA_VERSION := 2
const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3
@@ -15,6 +15,7 @@ const ROUTINE_LEGACY_SCHEMA_VERSION := 10
const PLAYER_RELATIONSHIP_SCHEMA_VERSION := 11
const PLAYER_NEEDS_SCHEMA_VERSION := 12
const CONFLICT_SCHEMA_VERSION := 13
const PRE_EMERGENT_SCHEMA_VERSION := 14
const PREVIOUS_SCHEMA_VERSION := 7
var simulation: Dictionary
@@ -27,6 +28,10 @@ var economic_events: Array[EconomicEventRecord] = []
var relationships: Array[RelationshipStateRecord] = []
var event_knowledge: Array[KnownEventStateRecord] = []
var opportunities: Array[OpportunityStateRecord] = []
var situations: Array[SituationStateRecord] = []
var quest_journal: Array[QuestJournalEntryStateRecord] = []
var commitments: Array[CommitmentStateRecord] = []
var conversation_history: Array[ConversationActStateRecord] = []
var player: PlayerStateRecord
var combatants: Array[CombatantStateRecord] = []
var factions: Array[FactionStateRecord] = []
@@ -58,6 +63,18 @@ func to_dictionary() -> Dictionary:
var opportunity_data: Array[Dictionary] = []
for opportunity_record in opportunities:
opportunity_data.append(opportunity_record.to_dictionary())
var situation_data: Array[Dictionary] = []
for situation_record in situations:
situation_data.append(situation_record.to_dictionary())
var journal_data: Array[Dictionary] = []
for journal_record in quest_journal:
journal_data.append(journal_record.to_dictionary())
var commitment_data: Array[Dictionary] = []
for commitment_record in commitments:
commitment_data.append(commitment_record.to_dictionary())
var conversation_history_data: Array[Dictionary] = []
for conversation_act in conversation_history:
conversation_history_data.append(conversation_act.to_dictionary())
var combatant_data: Array[Dictionary] = []
for combatant_record in combatants:
combatant_data.append(combatant_record.to_dictionary())
@@ -78,6 +95,10 @@ func to_dictionary() -> Dictionary:
"relationships": relationship_data,
"event_knowledge": knowledge_data,
"opportunities": opportunity_data,
"situations": situation_data,
"quest_journal": journal_data,
"commitments": commitment_data,
"conversation_history": conversation_history_data,
"player": player.to_dictionary(),
"combatants": combatant_data,
"factions": faction_data
@@ -115,6 +136,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
PLAYER_RELATIONSHIP_SCHEMA_VERSION,
PLAYER_NEEDS_SCHEMA_VERSION,
CONFLICT_SCHEMA_VERSION,
PRE_EMERGENT_SCHEMA_VERSION,
]
):
record_data = _migrate_legacy(record_data, version)
@@ -134,6 +156,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
"relationships",
"event_knowledge",
"opportunities",
"situations",
"quest_journal",
"commitments",
"conversation_history",
"player",
"combatants",
"factions",
@@ -157,6 +183,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
"wander_random_streams",
"next_event_id",
"next_opportunity_id",
"next_situation_id",
"next_journal_entry_id",
"next_commitment_id",
"next_conversation_act_id",
]
)
):
@@ -166,6 +196,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
var saved_clock_accumulator := float(simulation_data["clock_accumulator"])
var saved_clock_elapsed_ticks := int(simulation_data["clock_elapsed_ticks"])
var saved_next_opportunity_id := int(simulation_data["next_opportunity_id"])
var saved_next_situation_id := int(simulation_data["next_situation_id"])
var saved_next_journal_entry_id := int(simulation_data["next_journal_entry_id"])
var saved_next_commitment_id := int(simulation_data["next_commitment_id"])
var saved_next_conversation_act_id := int(simulation_data["next_conversation_act_id"])
if (
not is_finite(saved_tick_interval)
or saved_tick_interval <= 0.0
@@ -174,6 +208,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
or saved_clock_accumulator < 0.0
or saved_clock_elapsed_ticks < 0
or saved_next_opportunity_id < 0
or saved_next_situation_id < 0
or saved_next_journal_entry_id < 0
or saved_next_commitment_id < 0
or saved_next_conversation_act_id < 0
):
return null
if simulation_data.has("cycle_duration_seconds"):
@@ -204,6 +242,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
var relationship_data = record_data["relationships"]
var knowledge_data = record_data["event_knowledge"]
var opportunity_data = record_data["opportunities"]
var situation_data = record_data["situations"]
var journal_data = record_data["quest_journal"]
var commitment_data = record_data["commitments"]
var conversation_history_data = record_data["conversation_history"]
if (
not npc_data is Array
or not animal_data is Array
@@ -213,6 +255,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
or not relationship_data is Array
or not knowledge_data is Array
or not opportunity_data is Array
or not situation_data is Array
or not journal_data is Array
or not commitment_data is Array
or not conversation_history_data is Array
):
return null
@@ -425,7 +471,10 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
return null
var knower_id := known_event_record.get_knower_id()
var known_event_id := known_event_record.get_event_id()
if not npc_ids.has(knower_id) or not event_ids.has(known_event_id):
if (
(knower_id != SimulationIds.PLAYER_ACTOR_ID and not npc_ids.has(knower_id))
or not event_ids.has(known_event_id)
):
return null
var knowledge_key := "%d:%d" % [knower_id, known_event_id]
if knowledge_keys.has(knowledge_key):
@@ -448,29 +497,28 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
return null
var observer_id := relationship_record.get_observer_id()
var subject_id := relationship_record.get_subject_id()
var is_player_observer := observer_id == SimulationIds.PLAYER_ACTOR_ID
var is_player_subject := subject_id == SimulationIds.PLAYER_ACTOR_ID
if not npc_ids.has(observer_id):
if not is_player_observer and not npc_ids.has(observer_id):
return null
if not is_player_subject and not npc_ids.has(subject_id):
return null
var relationship_key := "%d:%d" % [observer_id, subject_id]
if relationship_keys.has(relationship_key):
return null
var cause_event_id := relationship_record.get_last_trust_cause_event_id()
if (
cause_event_id != RelationshipStateRecord.NO_CAUSE_EVENT
and not event_ids.has(cause_event_id)
):
return null
if (
cause_event_id != RelationshipStateRecord.NO_CAUSE_EVENT
and not knowledge_keys.has("%d:%d" % [observer_id, cause_event_id])
):
return null
if cause_event_id != RelationshipStateRecord.NO_CAUSE_EVENT:
var cause_event := event_records_by_id[cause_event_id] as EconomicEventRecord
if not _is_valid_relationship_cause(cause_event, subject_id, is_player_subject):
for dimension in RelationshipStateRecord.DIMENSIONS:
var cause_event_id := relationship_record.get_last_cause_event_id(dimension)
if cause_event_id == RelationshipStateRecord.NO_CAUSE_EVENT:
continue
if (
not event_ids.has(cause_event_id)
or not knowledge_keys.has("%d:%d" % [observer_id, cause_event_id])
):
return null
if dimension == RelationshipStateRecord.DIMENSION_TRUST:
var cause_event := event_records_by_id[cause_event_id] as EconomicEventRecord
if not _is_valid_relationship_cause(cause_event, subject_id, is_player_subject):
return null
relationship_keys[relationship_key] = true
record.relationships.append(relationship_record)
@@ -515,6 +563,36 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
if int(record.simulation["next_opportunity_id"]) <= highest_opportunity_id:
return null
var emergent_records: Variant = EmergentStateValidator.parse(
situation_data, journal_data, commitment_data, simulation_data, npc_ids, event_ids
)
if emergent_records == null:
return null
record.situations.assign(emergent_records["situations"])
record.quest_journal.assign(emergent_records["quest_journal"])
record.commitments.assign(emergent_records["commitments"])
var conversation_act_ids: Dictionary = {}
var highest_conversation_act_id := -1
for item in conversation_history_data:
if not item is Dictionary:
return null
var conversation_act := ConversationActStateRecord.from_dictionary(item)
if (
conversation_act == null
or conversation_act_ids.has(conversation_act.get_act_id())
or conversation_act.get_tick() > saved_tick_count
or not EmergentStateValidator.is_valid_actor(conversation_act.get_speaker(), npc_ids)
or not EmergentStateValidator.is_valid_actor(conversation_act.get_listener(), npc_ids)
):
return null
conversation_act_ids[conversation_act.get_act_id()] = true
highest_conversation_act_id = maxi(
highest_conversation_act_id, conversation_act.get_act_id()
)
record.conversation_history.append(conversation_act)
if saved_next_conversation_act_id <= highest_conversation_act_id:
return null
return record
@@ -579,16 +657,21 @@ static func _is_valid_knowledge_provenance(
SimulationIds.KNOWLEDGE_ACQUISITION_COMMUNICATED:
if knower_id == actor_id:
return false
var source_npc_id := known_event.get_source_npc_id()
if not npc_ids.has(source_npc_id):
var source_actor_id := known_event.get_source_actor_id()
if (
source_actor_id != SimulationIds.PLAYER_ACTOR_ID
and not npc_ids.has(source_actor_id)
):
return false
var source_method := known_event.get_source_acquisition_method()
if (
(source_method == SimulationIds.KNOWLEDGE_ACQUISITION_PERFORMED)
!= (source_npc_id == actor_id)
!= (source_actor_id == actor_id)
):
return false
return true
if source_method not in KnownEventStateRecord.DIRECT_ACQUISITION_METHODS:
return false
return known_event.get_hop_count() == 1
return false
@@ -774,12 +857,13 @@ static func _is_valid_home_damaged_trigger(
static func _is_valid_relationship_cause(
cause_event: EconomicEventRecord, subject_id: int, is_player_subject: bool
) -> bool:
var event_type := StringName(cause_event.data["event_type"])
if EmergentStateValidator.is_valid_commitment_relationship_cause(cause_event, subject_id):
return true
if not is_player_subject:
return (
int(cause_event.data["actor_id"]) == subject_id
and (
StringName(cause_event.data["event_type"]) == SimulationIds.EVENT_STORAGE_DEPOSITED
)
and (event_type == SimulationIds.EVENT_STORAGE_DEPOSITED)
and StringName(cause_event.data["item_id"]) == SimulationIds.RESOURCE_FOOD
and float(cause_event.data["amount"]) > 0.0
)
@@ -789,7 +873,7 @@ static func _is_valid_relationship_cause(
return false
if float(cause_event.data["amount"]) <= 0.0:
return false
if StringName(cause_event.data["event_type"]) == SimulationIds.EVENT_STORAGE_DEPOSITED:
if event_type == SimulationIds.EVENT_STORAGE_DEPOSITED:
return (
StringName(cause_event.data["source_id"]) == SimulationIds.PLAYER_INVENTORY_ID
and (
@@ -798,7 +882,7 @@ static func _is_valid_relationship_cause(
)
)
return (
StringName(cause_event.data["event_type"]) == SimulationIds.EVENT_RESOURCE_EXTRACTED
event_type == SimulationIds.EVENT_RESOURCE_EXTRACTED
and StringName(cause_event.data["destination_id"]) == SimulationIds.STORAGE_VILLAGE_PANTRY
)
@@ -848,6 +932,16 @@ static func _is_valid_supply_resolution(
static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary:
var migrated := legacy_data.duplicate(true)
migrated["schema_version"] = SCHEMA_VERSION
migrated["situations"] = []
migrated["quest_journal"] = []
migrated["commitments"] = []
migrated["conversation_history"] = []
var emergent_simulation_data: Dictionary = migrated.get("simulation", {})
emergent_simulation_data["next_situation_id"] = 0
emergent_simulation_data["next_journal_entry_id"] = 0
emergent_simulation_data["next_commitment_id"] = 0
emergent_simulation_data["next_conversation_act_id"] = 0
migrated["simulation"] = emergent_simulation_data
if not migrated.has("player"):
migrated["player"] = PlayerStateRecord.create_default().to_dictionary()
if version < CONFLICT_SCHEMA_VERSION:
@@ -927,6 +1021,7 @@ static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary
PLAYER_RELATIONSHIP_SCHEMA_VERSION,
PLAYER_NEEDS_SCHEMA_VERSION,
CONFLICT_SCHEMA_VERSION,
PRE_EMERGENT_SCHEMA_VERSION,
]
):
migrated["opportunities"] = []