37610242bf
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
164 lines
3.3 KiB
GDScript
164 lines
3.3 KiB
GDScript
class_name SimVillage
|
|
extends RefCounted
|
|
|
|
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
|
|
var safety_modifier := 1.0
|
|
var knowledge_modifier := 1.0
|
|
|
|
var food_priority := 1.0
|
|
var wood_priority := 1.0
|
|
var safety_priority := 1.0
|
|
var knowledge_priority := 1.0
|
|
|
|
func update_modifiers() -> void:
|
|
food_modifier = 1.0
|
|
wood_modifier = 1.0
|
|
safety_modifier = 1.0
|
|
knowledge_modifier = 1.0
|
|
if food <= 0:
|
|
food_modifier = 2.0
|
|
if wood <= 0:
|
|
wood_modifier = 2.0
|
|
if safety < 20:
|
|
safety_modifier = 2.0
|
|
if knowledge > 25:
|
|
knowledge_modifier = 0.75
|
|
|
|
debug_log("FoodMod=%s SafetyMod=%s KnowledgeMod=%s" %
|
|
[
|
|
food_modifier,
|
|
safety_modifier,
|
|
knowledge_modifier
|
|
]
|
|
)
|
|
|
|
func update_priorities() -> void:
|
|
food_priority = 1.0
|
|
wood_priority = 1.0
|
|
safety_priority = 1.0
|
|
knowledge_priority = 1.0
|
|
|
|
if food < 10.0:
|
|
food_priority = 3.0
|
|
elif food < 20.0:
|
|
food_priority = 2.0
|
|
|
|
if wood < 5.0:
|
|
wood_priority = 2.5
|
|
elif wood < 15.0:
|
|
wood_priority = 1.75
|
|
|
|
if safety < 25.0:
|
|
safety_priority = 3.0
|
|
elif safety < 50.0:
|
|
safety_priority = 1.5
|
|
|
|
if knowledge < 5.0:
|
|
knowledge_priority = 1.75
|
|
elif knowledge < 15.0:
|
|
knowledge_priority = 1.25
|
|
|
|
if debug_logs:
|
|
print(get_priority_summary())
|
|
|
|
func apply_resource_delta(resource_id: StringName, amount: float) -> void:
|
|
match resource_id:
|
|
&"food":
|
|
food += amount
|
|
&"wood":
|
|
wood += amount
|
|
&"safety":
|
|
safety = clamp(safety + amount, 0.0, 100.0)
|
|
&"knowledge":
|
|
knowledge += amount
|
|
|
|
food = max(food, 0.0)
|
|
wood = max(wood, 0.0)
|
|
safety = clamp(safety, 0.0, 100.0)
|
|
knowledge = max(knowledge, 0.0)
|
|
|
|
update_modifiers()
|
|
update_priorities()
|
|
|
|
func apply_npc_task(npc: SimNPC) -> void:
|
|
if npc.is_dead:
|
|
return
|
|
|
|
var productivity := 1.0
|
|
|
|
if npc.is_starving:
|
|
productivity = 0.35
|
|
elif npc.energy < 20.0:
|
|
productivity = 0.25
|
|
elif npc.energy < 40.0:
|
|
productivity = 0.5
|
|
|
|
match npc.current_task:
|
|
"gather_food":
|
|
food += 2.0 * productivity
|
|
"gather_wood":
|
|
wood += 2.0 * productivity
|
|
"patrol":
|
|
safety += 1.0 * productivity
|
|
"study":
|
|
knowledge += 1.0 * productivity
|
|
"eat":
|
|
if food > 0:
|
|
food -= 1.0
|
|
npc.hunger = max(npc.hunger - 55.0, 0.0)
|
|
npc.starvation_ticks = 0
|
|
npc.is_starving = npc.hunger >= 90.0
|
|
else:
|
|
npc.hunger = min(npc.hunger + 5.0, 100.0)
|
|
npc.is_starving = npc.hunger >= 90.0
|
|
"rest":
|
|
if npc.is_starving:
|
|
npc.energy = min(npc.energy + 2.0, 100.0)
|
|
elif npc.hunger > 75.0:
|
|
npc.energy = min(npc.energy + 12.0, 100.0)
|
|
else:
|
|
npc.energy = min(npc.energy + 35.0, 100.0)
|
|
"wander":
|
|
safety -= 0.2
|
|
|
|
food = max(food, 0.0)
|
|
wood = max(wood, 0.0)
|
|
safety = clamp(safety, 0.0, 100.0)
|
|
knowledge = max(knowledge, 0.0)
|
|
|
|
update_modifiers()
|
|
update_priorities()
|
|
|
|
func get_summary() -> String:
|
|
|
|
return "Village | food: %s | wood: %s | safety: %s | knowledge: %s | priorities F/W/S/K: %s/%s/%s/%s" % [
|
|
round(food),
|
|
round(wood),
|
|
round(safety),
|
|
round(knowledge),
|
|
food_priority,
|
|
wood_priority,
|
|
safety_priority,
|
|
knowledge_priority
|
|
]
|
|
|
|
|
|
func get_priority_summary() -> String:
|
|
return "Priorities | food: %s | wood: %s | safety: %s | knowledge: %s" % [
|
|
food_priority,
|
|
wood_priority,
|
|
safety_priority,
|
|
knowledge_priority
|
|
]
|
|
|
|
func debug_log(message: String) -> void:
|
|
if debug_logs:
|
|
print("[Village] ", message)
|