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:
2026-07-05 11:23:12 +02:00
parent b50fae671a
commit 37610242bf
10 changed files with 212 additions and 100 deletions
+3 -4
View File
@@ -1,12 +1,11 @@
class_name SimVillage
extends RefCounted
const DEBUG_LOGS := true
var food := 20.0
var wood := 10.0
var safety := 50.0
var knowledge := 0.0
var debug_logs := true
var food_modifier := 1.0
var wood_modifier := 1.0
@@ -66,7 +65,7 @@ func update_priorities() -> void:
elif knowledge < 15.0:
knowledge_priority = 1.25
if DEBUG_LOGS:
if debug_logs:
print(get_priority_summary())
func apply_resource_delta(resource_id: StringName, amount: float) -> void:
@@ -160,5 +159,5 @@ func get_priority_summary() -> String:
]
func debug_log(message: String) -> void:
if DEBUG_LOGS:
if debug_logs:
print("[Village] ", message)