feat: add deterministic simulation foundation
Replace implicit randomness with seeded per-NPC RandomNumberGenerator streams so the same seed always produces the same scenario outcome. - Add SimulationClock (RefCounted): explicit fixed-step clock that converts frame delta into simulation ticks while preserving sub-tick remainder; replaces raw _process tick_timer accumulation - Add simulation_seed export (int=1337) to SimulationManager; drive per-NPC RNG streams via seed + npc_id + stream_id derivation (stream 0=init, stream 1=wander) - Add get_wander_offset(npc_id) to SimulationManager so wander targets are deterministic per seed; WorldViewManager calls it instead of local randf_range() - Add get_state_snapshot() and get_state_checksum() to SimulationManager for headless verification and future save/load - Convert SimNPC/SimVillage const DEBUG_LOGS to instance var debug_logs so headless tests can silence output without recompilation - Pass RandomNumberGenerator through SimNPC._init() instead of using global randf_range() for hunger/energy/position/scoring noise - Remove obsolete farm_zone and forest_zone exports from WorldViewManager and their marker/tree scene children from main.tscn; NPC gather tasks use ResourceNode exclusively - Rename TaskZone container to ActivityMarkers in main.tscn and update all scene paths (player, WVM, tests) to match - Add headless deterministic_simulation_test.gd: verifies clock accuracy, same-seed equality, different-seed divergence, and 24-tick scenario checksum without loading main.tscn
This commit is contained in:
@@ -6,8 +6,6 @@ const DEBUG_LOGS := true
|
||||
@export var simulation_manager: Node
|
||||
@export var active_npcs_parent: Node3D
|
||||
|
||||
@export var farm_zone: Marker3D
|
||||
@export var forest_zone: Marker3D
|
||||
@export var guard_zone: Marker3D
|
||||
@export var study_zone: Marker3D
|
||||
@export var rest_zone: Marker3D
|
||||
@@ -98,7 +96,7 @@ func update_npc_targets() -> void:
|
||||
continue
|
||||
|
||||
if task == "wander":
|
||||
var offset = Vector3(randf_range(-6.0, 6.0), 0.0, randf_range(-6.0, 6.0))
|
||||
var offset := _get_wander_offset(npc.id)
|
||||
visual.set_target_position(visual.global_position + offset)
|
||||
continue
|
||||
|
||||
@@ -117,10 +115,6 @@ func update_npc_targets() -> void:
|
||||
|
||||
func get_target_for_task(task: String) -> Marker3D:
|
||||
match task:
|
||||
"gather_food":
|
||||
return farm_zone
|
||||
"gather_wood":
|
||||
return forest_zone
|
||||
"patrol":
|
||||
return guard_zone
|
||||
"study":
|
||||
@@ -175,7 +169,7 @@ func _on_npc_task_changed(npc: SimNPC, old_task: String, new_task: String) -> vo
|
||||
return
|
||||
|
||||
if new_task == "wander":
|
||||
var offset := Vector3(randf_range(-6.0, 6.0), 0.0, randf_range(-6.0, 6.0))
|
||||
var offset := _get_wander_offset(npc.id)
|
||||
visual.set_target_position(visual.global_position + offset)
|
||||
return
|
||||
|
||||
@@ -190,6 +184,12 @@ func _on_npc_task_changed(npc: SimNPC, old_task: String, new_task: String) -> vo
|
||||
|
||||
if target != null:
|
||||
visual.set_target_position(target.global_position)
|
||||
|
||||
func _get_wander_offset(npc_id: int) -> Vector3:
|
||||
if simulation_manager != null and simulation_manager.has_method("get_wander_offset"):
|
||||
return simulation_manager.get_wander_offset(npc_id)
|
||||
push_error("WorldViewManager: SimulationManager cannot provide deterministic wander offsets")
|
||||
return Vector3.ZERO
|
||||
|
||||
func _on_npc_visual_arrived(sim_id: int) -> void:
|
||||
debug_log("NPC visual arrived. sim_id=%s" % sim_id)
|
||||
|
||||
Reference in New Issue
Block a user