feat: scale village queries and enrich Jajce center

This commit is contained in:
Rijad Zuzo
2026-07-16 23:51:08 +02:00
parent 4be72639d8
commit 2c88ed6ea8
48 changed files with 2059 additions and 280 deletions
+25
View File
@@ -31,6 +31,11 @@ func _run() -> void:
await process_frame
_check(simulation_manager.npcs.size() == 6, "Baseline should create six NPCs")
var runtime_player := main_scene.get_node("Player") as Node3D
_check(
runtime_player.is_in_group("grass_interactors"),
"The real player transform should drive nearby grass response"
)
var inspector_label := (
main_scene.get_node("UI/NpcInspectorPanel/MarginContainer/NpcInspectorLabel") as Label
)
@@ -60,6 +65,26 @@ func _run() -> void:
"Runtime knowledge UI check should use a completed simulation transaction"
)
var world_view := main_scene.get_node("WorldViewManager")
var all_npc_visuals_drive_grass := true
for npc_visual in world_view.active_npc_visuals.values():
all_npc_visuals_drive_grass = (
all_npc_visuals_drive_grass and (npc_visual as Node3D).is_in_group("grass_interactors")
)
_check(
all_npc_visuals_drive_grass,
"Every active villager visual should drive local grass response"
)
var grass_material := (
main_scene.get_node("JajceWorld/TerrainRoot/Terrain3D/CozyGrassField").get(
"mesh_material_override"
)
as ShaderMaterial
)
var grass_interactor_count := int(grass_material.get_shader_parameter("interactor_count"))
_check(
grass_interactor_count in range(1, 9),
"Grass shader should receive a bounded set of real nearby actor transforms"
)
var contributor_visual: Node3D = world_view.active_npc_visuals[contributor.id]
var witness_visual: Node3D = world_view.active_npc_visuals[witness.id]
var listener_visual: Node3D = world_view.active_npc_visuals[listener.id]
+61 -1
View File
@@ -63,10 +63,48 @@ func _run() -> void:
_check(world.has_node("WaterRoot"), "JajceWorld should expose WaterRoot")
_check(world.has_node("VillageRoot"), "JajceWorld should expose VillageRoot")
_check(world.has_node("FoliageRoot"), "JajceWorld should expose FoliageRoot")
_check(world.has_node("WildlifeRoot"), "JajceWorld should expose ambient WildlifeRoot")
_check(
world.get_node("FoliageRoot").get_child_count() >= 10,
"JajceWorld should include restrained authored foliage clusters"
)
var conifer_count := 0
for foliage in world.get_node("FoliageRoot").get_children():
if foliage.name.begins_with("Conifer_"):
conifer_count += 1
_check(conifer_count >= 6, "Citadel and river edges should include conifer silhouettes")
_check(
world.get_node("WildlifeRoot").get_child_count() >= 3,
"Village center should include a few oversized ambient butterflies"
)
var grass_field := world.get_node("TerrainRoot/Terrain3D/CozyGrassField")
var grass_material := grass_field.get("mesh_material_override") as ShaderMaterial
var grass_process := grass_field.get("process_material") as ShaderMaterial
_check(
int(grass_field.get("particle_count")) in range(9000, 15001),
"Camera-local Terrain3D grass should stay inside its bounded particle budget"
)
_check(
(
grass_material != null
and grass_material.shader == load("res://world/jajce/materials/cozy_grass.gdshader")
),
"Terrain grass should use the cozy wind-and-interaction shader"
)
var grass_max_scale := Vector3.ZERO
var grass_clod_boost := INF
if grass_process != null:
grass_max_scale = grass_process.get_shader_parameter("max_scale") as Vector3
grass_clod_boost = float(grass_process.get_shader_parameter("clod_scale_boost"))
_check(
grass_process != null and grass_max_scale.y <= 0.5 and grass_clod_boost <= 0.2,
"Grass placement should stay short enough to preserve villagers and paths"
)
var grass_controller := world.get_node("TerrainRoot/GrassInteractionController")
_check(
grass_controller.get("grass_material") == grass_material,
"Grass interaction should update the same material rendered by Terrain3D particles"
)
_check(
(
not world.has_node("WindStrokes_Valley")
@@ -156,7 +194,15 @@ func _run() -> void:
)
_check(tree_wind_is_bounded, "Tree wind should stay within the restrained cozy-motion budget")
_check(world.has_node("FortressBlockout"), "JajceWorld should include the fortress landmark")
_check(
world.has_node("FortressBlockout/Keep/CitadelKeep"),
"Fortress silhouette should expose a distinct summit citadel"
)
_check(world.has_node("WaterRoot/WaterfallBody"), "WaterRoot should include a waterfall drop")
_check(
world.has_node("WaterRoot/WaterfallBody/RockFrame"),
"Waterfall should be framed by the rocky river threshold"
)
_check(
world.has_node("WaterRoot/WaterfallMist"),
"WaterRoot should include waterfall mist particles"
@@ -167,10 +213,19 @@ func _run() -> void:
"Waterfall mist particles should have visible billboard geometry"
)
_check(
waterfall_mist.position.distance_to(Vector3(25.0, 0.8, -24.0)) < 0.01,
waterfall_mist.position.distance_to(Vector3(25.0, 2.05, -24.0)) < 0.01,
"Waterfall mist should stay at the waterfall base instead of obscuring the village"
)
_check(world.has_node("WaterRoot/RiverSurface"), "WaterRoot should include a river surface")
var river_ribbon := world.get_node("WaterRoot/RiverSurface/RiverRibbon") as MeshInstance3D
_check(
river_ribbon.mesh is ArrayMesh,
"River should build a lightweight ribbon from the current Terrain3D surface"
)
_check(
world.has_node("WaterRoot/RiverSurface/PlungePool"),
"River should widen into a readable plunge pool beneath the waterfall"
)
var environment: Environment = (
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
)
@@ -190,6 +245,11 @@ func _run() -> void:
world.has_node("VillageRoot/Mill") and world.has_node("VillageRoot/Bridge"),
"VillageRoot should include the mill and bridge blockouts"
)
var house_count := 0
for village_child in world.get_node("VillageRoot").get_children():
if village_child.name.begins_with("House_"):
house_count += 1
_check(house_count >= 8, "Roofs should form a small residential amphitheater below the citadel")
var smoke_count := 0
var smoke_is_translucent := true
var smoke_follows_wind := true
+77
View File
@@ -0,0 +1,77 @@
extends SceneTree
const RelationshipSystemScript := preload("res://simulation/relationships/RelationshipSystem.gd")
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var first := _create_npc(0, "First")
var second := _create_npc(1, "Second")
var observer := _create_npc(2, "Observer")
first.is_starving = true
var npcs: Array[SimNPC] = [first, second, observer]
var view := SimulationPopulationView.new(npcs)
_check(view.living_count() == 3, "The initial view should index every living NPC")
_check(view.starving_count() == 1, "The initial view should index the starving NPC once")
_check(view.get_starving(first.id) == first, "The starving lookup should preserve identity")
second.is_starving = true
view.refresh_npc(second)
_check(view.starving_count() == 2, "Refreshing should add a newly starving NPC")
first.die_from_starvation()
view.refresh_npc(first)
_check(
view.get_living(first.id) == null and view.get_starving(first.id) == null,
"Refreshing should remove a dead NPC from both indexes"
)
_check(view.get_any(first.id) == first, "The identity index should retain a dead NPC record")
var relationships := RelationshipSystemScript.new()
var records: Array[RelationshipStateRecord] = [
RelationshipStateRecord.create(observer.id, first.id, 0.8, 0.9),
RelationshipStateRecord.create(observer.id, second.id, 0.7, 0.7),
]
relationships.restore(records)
_check(
relationships.get_trusted_starving_subject(observer.id, npcs, view) == second,
"The relationship query should ignore the stronger dead subject through the shared view"
)
_check(
relationships.get_most_familiar_living(observer.id, npcs, view) == second,
"The living-subject query should use the same refreshed view"
)
first.is_dead = false
first.is_starving = true
view.refresh_npc(first)
_check(
relationships.get_trusted_starving_subject(observer.id, npcs, view) == first,
"A refreshed living subject should regain eligibility without rebuilding the view"
)
_finish()
func _create_npc(npc_id: int, npc_name: String) -> SimNPC:
var random_source := RandomNumberGenerator.new()
random_source.seed = 7000 + npc_id
return SimNPC.new(npc_id, npc_name, SimulationIds.PROFESSION_FARMER, 5.0, 5.0, random_source)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
func _finish() -> void:
if failures.is_empty():
print("[TEST] Simulation population view passed: refresh -> shared stable-ID queries")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
@@ -0,0 +1 @@
uid://ervmgkik5q2x