feat: scale village queries and enrich Jajce center
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user