diff --git a/simulation/SimVillage.gd b/simulation/SimVillage.gd index 949a588..cee8926 100644 --- a/simulation/SimVillage.gd +++ b/simulation/SimVillage.gd @@ -27,7 +27,7 @@ func update_modifiers() -> void: food_modifier = 2.0 if wood <= 0: wood_modifier = 2.0 - if safety < 20: + if _strictly_below(safety, 20.0): safety_modifier = 2.0 if knowledge > 25: knowledge_modifier = 0.75 @@ -46,24 +46,24 @@ func update_priorities() -> void: safety_priority = 1.0 knowledge_priority = 1.0 - if food < 10.0: + if _strictly_below(food, 10.0): food_priority = 3.0 - elif food < 20.0: + elif _strictly_below(food, 20.0): food_priority = 2.0 - if wood < 5.0: + if _strictly_below(wood, 5.0): wood_priority = 2.5 - elif wood < 15.0: + elif _strictly_below(wood, 15.0): wood_priority = 1.75 - if safety < 25.0: + if _strictly_below(safety, 25.0): safety_priority = 3.0 - elif safety < 50.0: + elif _strictly_below(safety, 50.0): safety_priority = 1.5 - if knowledge < 5.0: + if _strictly_below(knowledge, 5.0): knowledge_priority = 1.75 - elif knowledge < 15.0: + elif _strictly_below(knowledge, 15.0): knowledge_priority = 1.25 if debug_logs: @@ -147,3 +147,9 @@ func get_priority_summary() -> String: func debug_log(message: String) -> void: if debug_logs: print("[Village] ", message) + + +static func _strictly_below(value: float, threshold: float) -> bool: + # JSON round trips can normalize a value a few ULPs below a threshold to the + # exact decimal. Treat that boundary identically before and after saving. + return value < threshold and not is_equal_approx(value, threshold) diff --git a/simulation/SimulationManager.gd b/simulation/SimulationManager.gd index 6483948..c3e03ec 100644 --- a/simulation/SimulationManager.gd +++ b/simulation/SimulationManager.gd @@ -1359,6 +1359,10 @@ func restore_state(record: SimulationStateRecord) -> bool: village = record.village.restore(debug_logs) economy.configure(village, debug_logs) economy.restore_storage(record.storages) + # Priorities and modifiers are deterministic derived state, not save payload. + # Rebuild them before the next decision so save/load cannot change outcomes. + village.update_modifiers() + village.update_priorities() event_log.restore(record.economic_events, int(record.simulation["next_event_id"])) latest_decisions.clear() npcs.clear() diff --git a/simulation/state/KnownEventStateRecord.gd b/simulation/state/KnownEventStateRecord.gd index 0b13100..d555b42 100644 --- a/simulation/state/KnownEventStateRecord.gd +++ b/simulation/state/KnownEventStateRecord.gd @@ -138,7 +138,19 @@ static func from_dictionary(record_data: Dictionary) -> KnownEventStateRecord: or hop_count != 0 ): return null - return KnownEventStateRecord.new(normalized) + return create( + knower_id, + event_id, + acquisition_method, + source_npc_id, + acquired_tick, + source_acquisition_method, + hop_count, + confidence, + salience, + source_actor_id, + bool(normalized["pinned"]) + ) func get_knower_id() -> int: diff --git a/simulation/state/RelationshipStateRecord.gd b/simulation/state/RelationshipStateRecord.gd index ac424aa..6cdf877 100644 --- a/simulation/state/RelationshipStateRecord.gd +++ b/simulation/state/RelationshipStateRecord.gd @@ -89,6 +89,11 @@ static func from_dictionary(record_data: Dictionary) -> RelationshipStateRecord: return null if cause_event_id < NO_CAUSE_EVENT: return null + normalized[String(dimension)] = value + normalized[_cause_key(dimension)] = cause_event_id + normalized["schema_version"] = SCHEMA_VERSION + normalized["observer_id"] = observer_id + normalized["subject_id"] = subject_id return RelationshipStateRecord.new(normalized) diff --git a/tests/unit/test_social_foundations.gd.uid b/tests/unit/test_social_foundations.gd.uid new file mode 100644 index 0000000..2434c2b --- /dev/null +++ b/tests/unit/test_social_foundations.gd.uid @@ -0,0 +1 @@ +uid://becxio7ec31yj