feat: seed household familiarity from shared home proximity
NPCs whose home positions are within 4m of each other gain mutual familiarity at baseline 0.5. Stored per NPC as a Dictionary keyed by other npc_id, serialized through NPCStateRecord with backward-compatible fallback for old saves. Household pairs form naturally from the home_positions configuration in main.tscn. Includes save/load round-trip test verifying familiarity persists.
This commit is contained in:
@@ -32,6 +32,7 @@ var has_travel_target := false
|
|||||||
var inventory: Dictionary = {}
|
var inventory: Dictionary = {}
|
||||||
var last_task: StringName
|
var last_task: StringName
|
||||||
var random_source: RandomNumberGenerator
|
var random_source: RandomNumberGenerator
|
||||||
|
var familiarity: Dictionary = {}
|
||||||
var debug_logs := true
|
var debug_logs := true
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ func generate_npcs() -> void:
|
|||||||
for i in range(npcs.size()):
|
for i in range(npcs.size()):
|
||||||
npcs[i].home_position = home_positions[i % home_count]
|
npcs[i].home_position = home_positions[i % home_count]
|
||||||
|
|
||||||
|
for i in range(npcs.size()):
|
||||||
|
for j in range(i + 1, npcs.size()):
|
||||||
|
if npcs[i].home_position.distance_to(npcs[j].home_position) < 4.0:
|
||||||
|
npcs[i].familiarity[npcs[j].id] = 0.5
|
||||||
|
npcs[j].familiarity[npcs[i].id] = 0.5
|
||||||
|
|
||||||
|
|
||||||
func _create_random_source(npc_id: int, stream_id: int) -> RandomNumberGenerator:
|
func _create_random_source(npc_id: int, stream_id: int) -> RandomNumberGenerator:
|
||||||
var source := RandomNumberGenerator.new()
|
var source := RandomNumberGenerator.new()
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ static func capture(npc: SimNPC) -> NPCStateRecord:
|
|||||||
"inventory": npc.inventory.duplicate(true),
|
"inventory": npc.inventory.duplicate(true),
|
||||||
"last_task": String(npc.last_task),
|
"last_task": String(npc.last_task),
|
||||||
"random_seed": str(npc.random_source.seed),
|
"random_seed": str(npc.random_source.seed),
|
||||||
"random_state": str(npc.random_source.state)
|
"random_state": str(npc.random_source.state),
|
||||||
|
"familiarity": npc.familiarity.duplicate(true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,6 +161,9 @@ func restore(debug_logs: bool) -> SimNPC:
|
|||||||
npc.last_task = StringName(data["last_task"])
|
npc.last_task = StringName(data["last_task"])
|
||||||
npc.random_source.state = String(data["random_state"]).to_int()
|
npc.random_source.state = String(data["random_state"]).to_int()
|
||||||
npc.debug_logs = debug_logs
|
npc.debug_logs = debug_logs
|
||||||
|
var saved_familiarity = data.get("familiarity", {})
|
||||||
|
if saved_familiarity is Dictionary:
|
||||||
|
npc.familiarity = saved_familiarity.duplicate(true)
|
||||||
return npc
|
return npc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func _run() -> void:
|
|||||||
_test_sleep_restores_energy()
|
_test_sleep_restores_energy()
|
||||||
_test_schedule_serialization()
|
_test_schedule_serialization()
|
||||||
_test_sleep_targets_home()
|
_test_sleep_targets_home()
|
||||||
|
_test_household_familiarity()
|
||||||
|
|
||||||
if failures.is_empty():
|
if failures.is_empty():
|
||||||
print("[TEST] NPC schedule passed")
|
print("[TEST] NPC schedule passed")
|
||||||
@@ -181,6 +182,63 @@ func _test_sleep_targets_home() -> void:
|
|||||||
manager.free()
|
manager.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _test_household_familiarity() -> void:
|
||||||
|
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||||
|
manager.simulation_seed = 700
|
||||||
|
manager.debug_logs = false
|
||||||
|
manager.set_process(false)
|
||||||
|
manager.home_positions = [
|
||||||
|
Vector3(-15.0, 2.0, 7.0),
|
||||||
|
Vector3(-15.0, 2.0, 8.0),
|
||||||
|
Vector3(-8.5, 2.0, 3.0),
|
||||||
|
Vector3(-6.5, 2.0, 1.5),
|
||||||
|
Vector3(20.0, 0.0, 20.0),
|
||||||
|
Vector3(22.0, 0.0, 22.0)
|
||||||
|
]
|
||||||
|
root.add_child(manager)
|
||||||
|
|
||||||
|
var a: SimNPC = manager.npcs[0]
|
||||||
|
var b: SimNPC = manager.npcs[1]
|
||||||
|
var c: SimNPC = manager.npcs[2]
|
||||||
|
var d: SimNPC = manager.npcs[3]
|
||||||
|
var e: SimNPC = manager.npcs[4]
|
||||||
|
|
||||||
|
_check(
|
||||||
|
a.familiarity.has(b.id) and b.familiarity.has(a.id),
|
||||||
|
"NPCs with nearby homes should be mutual household members"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
is_equal_approx(float(a.familiarity[b.id]), 0.5),
|
||||||
|
"Household familiarity should start at baseline 0.5"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
c.familiarity.has(d.id) and d.familiarity.has(c.id),
|
||||||
|
"Second household pair should also be familiar"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
not a.familiarity.has(e.id),
|
||||||
|
"Distant NPCs should not have household familiarity"
|
||||||
|
)
|
||||||
|
|
||||||
|
var saved_json: String = manager.serialize_state()
|
||||||
|
var restored: Node = load("res://simulation/SimulationManager.gd").new()
|
||||||
|
restored.debug_logs = false
|
||||||
|
root.add_child(restored)
|
||||||
|
restored.set_process(false)
|
||||||
|
_check(
|
||||||
|
restored.restore_state_from_json(saved_json),
|
||||||
|
"Familiarity should survive save/load round-trip"
|
||||||
|
)
|
||||||
|
var ra: SimNPC = restored.npcs[0]
|
||||||
|
_check(
|
||||||
|
ra.familiarity.size() > 0,
|
||||||
|
"Restored NPC should retain household familiarity"
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.free()
|
||||||
|
restored.free()
|
||||||
|
|
||||||
|
|
||||||
func _check(condition: bool, message: String) -> void:
|
func _check(condition: bool, message: String) -> void:
|
||||||
if not condition:
|
if not condition:
|
||||||
failures.append(message)
|
failures.append(message)
|
||||||
|
|||||||
Reference in New Issue
Block a user