feat: add witnessed knowledge and wind ambience

This commit is contained in:
Rijad Zuzo
2026-07-11 11:27:40 +02:00
parent 2ed93de6d1
commit 81409650df
72 changed files with 3001 additions and 606 deletions
+26 -11
View File
@@ -1,7 +1,8 @@
class_name EconomicEventRecord
extends RefCounted
const SCHEMA_VERSION := 1
const SCHEMA_VERSION := 2
const LEGACY_SCHEMA_VERSION := 1
var data: Dictionary
@@ -18,7 +19,8 @@ static func create(
source_id: StringName,
destination_id: StringName,
item_id: StringName,
amount: float
amount: float,
world_position: Vector3 = Vector3.ZERO
) -> EconomicEventRecord:
return EconomicEventRecord.new(
{
@@ -30,7 +32,8 @@ static func create(
"source_id": String(source_id),
"destination_id": String(destination_id),
"item_id": String(item_id),
"amount": amount
"amount": amount,
"world_position": [world_position.x, world_position.y, world_position.z]
}
)
@@ -41,7 +44,8 @@ static func create_narrative(
tick: int,
actor_id: int,
source_id: StringName,
action_display: String = ""
action_display: String = "",
world_position: Vector3 = Vector3.ZERO
) -> EconomicEventRecord:
return EconomicEventRecord.new(
{
@@ -54,13 +58,15 @@ static func create_narrative(
"destination_id": "",
"item_id": "",
"amount": 0.0,
"action_name": action_display
"action_name": action_display,
"world_position": [world_position.x, world_position.y, world_position.z]
}
)
static func from_dictionary(record_data: Dictionary) -> EconomicEventRecord:
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
var version := int(record_data.get("schema_version", -1))
if version not in [LEGACY_SCHEMA_VERSION, SCHEMA_VERSION]:
return null
if not record_data.has_all(
[
@@ -75,6 +81,8 @@ static func from_dictionary(record_data: Dictionary) -> EconomicEventRecord:
]
):
return null
if version == SCHEMA_VERSION and not record_data.has("world_position"):
return null
var normalized := record_data.duplicate(true)
normalized["schema_version"] = SCHEMA_VERSION
normalized["event_id"] = int(record_data["event_id"])
@@ -85,11 +93,13 @@ static func from_dictionary(record_data: Dictionary) -> EconomicEventRecord:
normalized["destination_id"] = String(record_data["destination_id"])
normalized["item_id"] = String(record_data["item_id"])
normalized["amount"] = float(record_data["amount"])
if (
normalized["event_id"] < 0
or normalized["tick"] < 0
or normalized["event_type"].is_empty()
):
var saved_position = record_data.get("world_position", [0.0, 0.0, 0.0])
if not saved_position is Array or saved_position.size() != 3:
return null
normalized["world_position"] = [
float(saved_position[0]), float(saved_position[1]), float(saved_position[2])
]
if normalized["event_id"] < 0 or normalized["tick"] < 0 or normalized["event_type"].is_empty():
return null
return EconomicEventRecord.new(normalized)
@@ -98,6 +108,11 @@ func to_dictionary() -> Dictionary:
return data.duplicate(true)
func get_world_position() -> Vector3:
var saved_position: Array = data["world_position"]
return Vector3(float(saved_position[0]), float(saved_position[1]), float(saved_position[2]))
func description(npc_names: Dictionary = {}) -> String:
var actor_name: String = npc_names.get(int(data["actor_id"]), "Someone")
var item: String = str(data["item_id"])
+47
View File
@@ -0,0 +1,47 @@
class_name KnownEventStateRecord
extends RefCounted
const SCHEMA_VERSION := 1
var data: Dictionary
func _init(record_data: Dictionary = {}) -> void:
data = record_data.duplicate(true)
static func create(knower_id: int, event_id: int) -> KnownEventStateRecord:
return (
KnownEventStateRecord
. new(
{
"schema_version": SCHEMA_VERSION,
"knower_id": knower_id,
"event_id": event_id,
}
)
)
static func from_dictionary(record_data: Dictionary) -> KnownEventStateRecord:
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
return null
if not record_data.has_all(["knower_id", "event_id"]):
return null
var knower_id := int(record_data["knower_id"])
var event_id := int(record_data["event_id"])
if knower_id < 0 or event_id < 0:
return null
return create(knower_id, event_id)
func get_knower_id() -> int:
return int(data["knower_id"])
func get_event_id() -> int:
return int(data["event_id"])
func to_dictionary() -> Dictionary:
return data.duplicate(true)
@@ -0,0 +1 @@
uid://n65oueyvbeoi
+10 -26
View File
@@ -1,9 +1,10 @@
class_name NPCStateRecord
extends RefCounted
const SCHEMA_VERSION := 3
const SCHEMA_VERSION := 4
const LEGACY_SCHEMA_VERSION := 1
const PREVIOUS_SCHEMA_VERSION := 2
const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3
var data: Dictionary
@@ -46,7 +47,6 @@ static func capture(npc: SimNPC) -> NPCStateRecord:
"last_task": String(npc.last_task),
"random_seed": str(npc.random_source.seed),
"random_state": str(npc.random_source.state),
"familiarity": _sorted_familiarity(npc.familiarity),
"mourning_ticks": npc.mourning_ticks
}
)
@@ -54,7 +54,10 @@ static func capture(npc: SimNPC) -> NPCStateRecord:
static func from_dictionary(record_data: Dictionary) -> NPCStateRecord:
var version := int(record_data.get("schema_version", -1))
if version in [LEGACY_SCHEMA_VERSION, PREVIOUS_SCHEMA_VERSION]:
if (
version
in [LEGACY_SCHEMA_VERSION, PREVIOUS_SCHEMA_VERSION, RELATIONSHIP_LEGACY_SCHEMA_VERSION]
):
record_data = _migrate_legacy(record_data, version)
elif version != SCHEMA_VERSION:
return null
@@ -116,7 +119,9 @@ static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary
if version == LEGACY_SCHEMA_VERSION:
migrated["travel_target_position"] = legacy_data.get("position", [0.0, 0.0, 0.0])
migrated["has_travel_target"] = false
migrated["inventory"] = {}
if version in [LEGACY_SCHEMA_VERSION, PREVIOUS_SCHEMA_VERSION]:
migrated["inventory"] = {}
migrated.erase("familiarity")
return migrated
@@ -141,9 +146,7 @@ func restore(debug_logs: bool) -> SimNPC:
float(saved_position[0]), float(saved_position[1]), float(saved_position[2])
)
var saved_home: Array = data.get("home_position", saved_position)
npc.home_position = Vector3(
float(saved_home[0]), float(saved_home[1]), float(saved_home[2])
)
npc.home_position = Vector3(float(saved_home[0]), float(saved_home[1]), float(saved_home[2]))
npc.is_starving = bool(data["is_starving"])
npc.starvation_ticks = int(data["starvation_ticks"])
npc.starvation_death_threshold = int(data["starvation_death_threshold"])
@@ -162,28 +165,9 @@ func restore(debug_logs: bool) -> SimNPC:
npc.last_task = StringName(data["last_task"])
npc.random_source.state = String(data["random_state"]).to_int()
npc.debug_logs = debug_logs
var saved_familiarity = data.get("familiarity", {})
if saved_familiarity is Array:
for pair in saved_familiarity:
var pair_dict: Dictionary = pair
npc.familiarity[int(pair_dict["id"])] = float(pair_dict["score"])
elif saved_familiarity is Dictionary:
npc.familiarity = saved_familiarity.duplicate(true)
npc.mourning_ticks = int(data.get("mourning_ticks", 0))
return npc
func to_dictionary() -> Dictionary:
return data.duplicate(true)
static func _sorted_familiarity(familiarity: Dictionary) -> Array:
var pairs: Array[Dictionary] = []
for key in familiarity:
pairs.append({"id": int(key), "score": float(familiarity[key])})
pairs.sort_custom(_familiarity_sort)
return pairs
static func _familiarity_sort(a: Dictionary, b: Dictionary) -> bool:
return int(a["id"]) < int(b["id"])
@@ -0,0 +1,94 @@
class_name RelationshipStateRecord
extends RefCounted
const SCHEMA_VERSION := 1
const NO_CAUSE_EVENT := -1
const NEUTRAL_TRUST := 0.5
var data: Dictionary
func _init(record_data: Dictionary = {}) -> void:
data = record_data.duplicate(true)
static func create(
observer_id: int,
subject_id: int,
familiarity: float,
trust: float = NEUTRAL_TRUST,
last_trust_cause_event_id: int = NO_CAUSE_EVENT
) -> RelationshipStateRecord:
return (
RelationshipStateRecord
. new(
{
"schema_version": SCHEMA_VERSION,
"observer_id": observer_id,
"subject_id": subject_id,
"familiarity": clampf(familiarity, 0.0, 1.0),
"trust": clampf(trust, 0.0, 1.0),
"last_trust_cause_event_id": last_trust_cause_event_id,
}
)
)
static func from_dictionary(record_data: Dictionary) -> RelationshipStateRecord:
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
return null
if not record_data.has_all(
["observer_id", "subject_id", "familiarity", "trust", "last_trust_cause_event_id"]
):
return null
var observer_id := int(record_data["observer_id"])
var subject_id := int(record_data["subject_id"])
var familiarity := float(record_data["familiarity"])
var trust := float(record_data["trust"])
var cause_event_id := int(record_data["last_trust_cause_event_id"])
if observer_id < 0 or subject_id < 0 or observer_id == subject_id:
return null
if familiarity < 0.0 or familiarity > 1.0 or trust < 0.0 or trust > 1.0:
return null
if cause_event_id < NO_CAUSE_EVENT:
return null
return create(observer_id, subject_id, familiarity, trust, cause_event_id)
func get_observer_id() -> int:
return int(data["observer_id"])
func get_subject_id() -> int:
return int(data["subject_id"])
func get_familiarity() -> float:
return float(data["familiarity"])
func get_trust() -> float:
return float(data["trust"])
func get_last_trust_cause_event_id() -> int:
return int(data["last_trust_cause_event_id"])
func increase_familiarity(amount: float) -> float:
var previous := get_familiarity()
data["familiarity"] = clampf(previous + maxf(amount, 0.0), 0.0, 1.0)
return get_familiarity() - previous
func increase_trust(amount: float, cause_event_id: int) -> float:
var previous := get_trust()
data["trust"] = clampf(previous + maxf(amount, 0.0), 0.0, 1.0)
var applied := get_trust() - previous
if applied > 0.0:
data["last_trust_cause_event_id"] = cause_event_id
return applied
func to_dictionary() -> Dictionary:
return data.duplicate(true)
@@ -0,0 +1 @@
uid://bxvc6flu3fdka
+186 -10
View File
@@ -2,9 +2,11 @@ class_name SimulationStateRecord
extends RefCounted
const SCHEMA_NAME := "the_steward.simulation"
const SCHEMA_VERSION := 3
const SCHEMA_VERSION := 5
const LEGACY_SCHEMA_VERSION := 1
const PREVIOUS_SCHEMA_VERSION := 2
const EVENT_LEGACY_SCHEMA_VERSION := 2
const RELATIONSHIP_LEGACY_SCHEMA_VERSION := 3
const PREVIOUS_SCHEMA_VERSION := 4
var simulation: Dictionary
var village: VillageStateRecord
@@ -12,6 +14,8 @@ var npcs: Array[NPCStateRecord] = []
var resources: Array[ResourceStateRecord] = []
var storages: Array[StorageStateRecord] = []
var economic_events: Array[EconomicEventRecord] = []
var relationships: Array[RelationshipStateRecord] = []
var event_knowledge: Array[KnownEventStateRecord] = []
func to_dictionary() -> Dictionary:
@@ -28,6 +32,12 @@ func to_dictionary() -> Dictionary:
var event_data: Array[Dictionary] = []
for event_record in economic_events:
event_data.append(event_record.to_dictionary())
var relationship_data: Array[Dictionary] = []
for relationship_record in relationships:
relationship_data.append(relationship_record.to_dictionary())
var knowledge_data: Array[Dictionary] = []
for known_event_record in event_knowledge:
knowledge_data.append(known_event_record.to_dictionary())
return {
"schema": SCHEMA_NAME,
@@ -37,7 +47,9 @@ func to_dictionary() -> Dictionary:
"npcs": npc_data,
"resources": resource_data,
"storages": storage_data,
"economic_events": event_data
"economic_events": event_data,
"relationships": relationship_data,
"event_knowledge": knowledge_data
}
@@ -56,12 +68,32 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
if record_data.get("schema", "") != SCHEMA_NAME:
return null
var version := int(record_data.get("schema_version", -1))
if version in [LEGACY_SCHEMA_VERSION, PREVIOUS_SCHEMA_VERSION]:
if (
version
in [
LEGACY_SCHEMA_VERSION,
EVENT_LEGACY_SCHEMA_VERSION,
RELATIONSHIP_LEGACY_SCHEMA_VERSION,
PREVIOUS_SCHEMA_VERSION,
]
):
record_data = _migrate_legacy(record_data, version)
elif version != SCHEMA_VERSION:
return null
if not record_data.has_all(
["simulation", "village", "npcs", "resources", "storages", "economic_events"]
if not (
record_data
. has_all(
[
"simulation",
"village",
"npcs",
"resources",
"storages",
"economic_events",
"relationships",
"event_knowledge",
]
)
):
return null
@@ -100,11 +132,15 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
var resource_data = record_data["resources"]
var storage_data = record_data["storages"]
var event_data = record_data["economic_events"]
var relationship_data = record_data["relationships"]
var knowledge_data = record_data["event_knowledge"]
if (
not npc_data is Array
or not resource_data is Array
or not storage_data is Array
or not event_data is Array
or not relationship_data is Array
or not knowledge_data is Array
):
return null
@@ -152,6 +188,7 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
record.storages.append(storage_record)
var event_ids := {}
var event_records_by_id := {}
var highest_event_id := -1
for item in event_data:
if not item is Dictionary:
@@ -163,11 +200,69 @@ static func from_dictionary(record_data: Dictionary) -> SimulationStateRecord:
if event_ids.has(event_id):
return null
event_ids[event_id] = true
event_records_by_id[event_id] = event_record
highest_event_id = maxi(highest_event_id, event_id)
record.economic_events.append(event_record)
if int(record.simulation["next_event_id"]) <= highest_event_id:
return null
var knowledge_keys := {}
for item in knowledge_data:
if not item is Dictionary:
return null
var known_event_record := KnownEventStateRecord.from_dictionary(item)
if known_event_record == null:
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):
return null
var knowledge_key := "%d:%d" % [knower_id, known_event_id]
if knowledge_keys.has(knowledge_key):
return null
knowledge_keys[knowledge_key] = true
record.event_knowledge.append(known_event_record)
var relationship_keys := {}
for item in relationship_data:
if not item is Dictionary:
return null
var relationship_record := RelationshipStateRecord.from_dictionary(item)
if relationship_record == null:
return null
var observer_id := relationship_record.get_observer_id()
var subject_id := relationship_record.get_subject_id()
if not npc_ids.has(observer_id) or 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 (
int(cause_event.data["actor_id"]) != subject_id
or (
StringName(cause_event.data["event_type"])
!= SimulationIds.EVENT_STORAGE_DEPOSITED
)
or StringName(cause_event.data["item_id"]) != SimulationIds.RESOURCE_FOOD
or float(cause_event.data["amount"]) <= 0.0
):
return null
relationship_keys[relationship_key] = true
record.relationships.append(relationship_record)
return record
@@ -196,8 +291,89 @@ static func _migrate_legacy(legacy_data: Dictionary, version: int) -> Dictionary
. to_dictionary()
)
]
migrated["economic_events"] = []
var simulation_data: Dictionary = migrated.get("simulation", {})
simulation_data["next_event_id"] = 0
migrated["simulation"] = simulation_data
if version in [LEGACY_SCHEMA_VERSION, EVENT_LEGACY_SCHEMA_VERSION]:
migrated["economic_events"] = []
var simulation_data: Dictionary = migrated.get("simulation", {})
simulation_data["next_event_id"] = 0
migrated["simulation"] = simulation_data
if (
version
in [LEGACY_SCHEMA_VERSION, EVENT_LEGACY_SCHEMA_VERSION, RELATIONSHIP_LEGACY_SCHEMA_VERSION]
):
migrated["relationships"] = _migrate_npc_familiarity(legacy_data.get("npcs", []))
migrated["event_knowledge"] = _migrate_relationship_causes(migrated.get("relationships", []))
return migrated
static func _migrate_npc_familiarity(npc_data: Variant) -> Array[Dictionary]:
var migrated_relationships: Array[Dictionary] = []
if not npc_data is Array:
return migrated_relationships
for npc_item in npc_data:
if not npc_item is Dictionary:
continue
var observer_id := int(npc_item.get("id", -1))
var familiarity = npc_item.get("familiarity", [])
if familiarity is Array:
for pair in familiarity:
if not pair is Dictionary:
continue
migrated_relationships.append(
(
RelationshipStateRecord
. create(
observer_id, int(pair.get("id", -1)), float(pair.get("score", 0.0))
)
. to_dictionary()
)
)
elif familiarity is Dictionary:
for subject_id in familiarity:
migrated_relationships.append(
(
RelationshipStateRecord
. create(observer_id, int(subject_id), float(familiarity[subject_id]))
. to_dictionary()
)
)
migrated_relationships.sort_custom(_relationship_dictionary_sort)
return migrated_relationships
static func _relationship_dictionary_sort(first: Dictionary, second: Dictionary) -> bool:
if int(first["observer_id"]) != int(second["observer_id"]):
return int(first["observer_id"]) < int(second["observer_id"])
return int(first["subject_id"]) < int(second["subject_id"])
static func _migrate_relationship_causes(relationship_data: Variant) -> Array[Dictionary]:
var migrated_knowledge: Array[Dictionary] = []
var known_keys := {}
if not relationship_data is Array:
return migrated_knowledge
for relationship_item in relationship_data:
if not relationship_item is Dictionary:
continue
var observer_id := int(relationship_item.get("observer_id", -1))
var cause_event_id := int(
relationship_item.get(
"last_trust_cause_event_id", RelationshipStateRecord.NO_CAUSE_EVENT
)
)
if observer_id < 0 or cause_event_id < 0:
continue
var knowledge_key := "%d:%d" % [observer_id, cause_event_id]
if known_keys.has(knowledge_key):
continue
known_keys[knowledge_key] = true
migrated_knowledge.append(
KnownEventStateRecord.create(observer_id, cause_event_id).to_dictionary()
)
migrated_knowledge.sort_custom(_knowledge_dictionary_sort)
return migrated_knowledge
static func _knowledge_dictionary_sort(first: Dictionary, second: Dictionary) -> bool:
if int(first["knower_id"]) != int(second["knower_id"]):
return int(first["knower_id"]) < int(second["knower_id"])
return int(first["event_id"]) < int(second["event_id"])