299 lines
10 KiB
GDScript
299 lines
10 KiB
GDScript
extends SceneTree
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
var camera := Camera3D.new()
|
|
camera.current = true
|
|
root.add_child(camera)
|
|
var world: JajceWorld = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
|
root.add_child(world)
|
|
await process_frame
|
|
await physics_frame
|
|
|
|
var viewport := root as Viewport
|
|
var environment: Environment = (
|
|
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
|
)
|
|
var light := world.get_node("DirectionalLight3D") as DirectionalLight3D
|
|
var grass := world.get_node("TerrainRoot/Terrain3D/CozyGrassField") as Node3D
|
|
var grass_controller := world.get_node("TerrainRoot/GrassInteractionController") as Node
|
|
var world_grass_material := grass.get("mesh_material_override") as ShaderMaterial
|
|
var world_process_material := grass.get("process_material") as ShaderMaterial
|
|
var world_sky: Sky = environment.sky
|
|
var world_sky_material: Material = world_sky.sky_material
|
|
var balanced_particle_count := int(grass.get("particle_count"))
|
|
var balanced_render_scale := viewport.scaling_3d_scale
|
|
|
|
_check(
|
|
world.get_active_presentation_quality() == JajceWorld.PresentationQuality.BALANCED,
|
|
"Balanced should be the default presentation tier"
|
|
)
|
|
_check(
|
|
balanced_render_scale <= JajceWorld.BALANCED_RENDER_SCALE + 0.001,
|
|
(
|
|
"Balanced should reduce 3D render resolution while leaving UI resolution untouched"
|
|
+ " (found %.2f)" % balanced_render_scale
|
|
)
|
|
)
|
|
_check(
|
|
(
|
|
light.directional_shadow_max_distance <= JajceWorld.BALANCED_SHADOW_DISTANCE
|
|
and light.directional_shadow_mode == DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS
|
|
and environment.fog_enabled
|
|
and not environment.volumetric_fog_enabled
|
|
),
|
|
"Balanced should bound shadows and use cheap valley fog instead of volumetric fog"
|
|
)
|
|
_check(
|
|
world_grass_material == grass_controller.get("grass_material"),
|
|
"Grass rendering and interaction should share this world's isolated material"
|
|
)
|
|
|
|
_check(
|
|
world.apply_presentation_quality(JajceWorld.PresentationQuality.HIGH),
|
|
"High presentation tier should be selectable"
|
|
)
|
|
await physics_frame
|
|
var high_particle_count := int(grass.get("particle_count"))
|
|
var high_render_scale := viewport.scaling_3d_scale
|
|
var high_shadow_distance := light.directional_shadow_max_distance
|
|
_check(
|
|
high_particle_count in range(9000, 15001),
|
|
"High should restore the authored grass population"
|
|
)
|
|
_check(
|
|
balanced_particle_count < high_particle_count,
|
|
"Balanced should materially reduce grass density relative to High"
|
|
)
|
|
_check(
|
|
(
|
|
environment.glow_enabled
|
|
and (environment.volumetric_fog_enabled == world.call("_supports_volumetric_fog"))
|
|
and environment.adjustment_enabled
|
|
),
|
|
"High should preserve supported authored effects without enabling unsupported fog"
|
|
)
|
|
_check(
|
|
is_equal_approx(high_shadow_distance, 180.0),
|
|
"High should preserve the authored 180 metre shadow range"
|
|
)
|
|
_check(_all_grass_particles_emitting(grass), "High should render every authored grass cell")
|
|
|
|
_check(
|
|
world.apply_presentation_quality(JajceWorld.PresentationQuality.LOW),
|
|
"Low presentation tier should be selectable"
|
|
)
|
|
await physics_frame
|
|
_check(
|
|
viewport.scaling_3d_scale <= JajceWorld.LOW_RENDER_SCALE + 0.001,
|
|
"Low should reduce 3D render resolution"
|
|
)
|
|
_check(
|
|
(
|
|
environment.fog_enabled
|
|
and not environment.glow_enabled
|
|
and not environment.volumetric_fog_enabled
|
|
and not environment.adjustment_enabled
|
|
),
|
|
"Low should retain cheap valley fog while disabling costly post-processing"
|
|
)
|
|
_check(
|
|
(
|
|
light.directional_shadow_max_distance <= JajceWorld.LOW_SHADOW_DISTANCE
|
|
and light.directional_shadow_mode == DirectionalLight3D.SHADOW_ORTHOGONAL
|
|
),
|
|
"Low should shorten shadows and use one orthogonal shadow region"
|
|
)
|
|
_check(
|
|
int(grass.get("particle_count")) <= high_particle_count / 4,
|
|
"Low should reduce the latent grass population by at least 75 percent"
|
|
)
|
|
_check(
|
|
not grass.visible and not grass.is_physics_processing(),
|
|
"Low should hide grass and stop its camera-grid physics work"
|
|
)
|
|
_check(not grass_controller.is_processing(), "Low should stop grass interaction scans")
|
|
_check(_not_any_grass_particle_emitting(grass), "Low should stop every grass particle emitter")
|
|
_check(
|
|
(
|
|
world_grass_material != null
|
|
and int(world_grass_material.get_shader_parameter("interactor_count")) == 0
|
|
),
|
|
"Low should clear grass shader interactors"
|
|
)
|
|
|
|
_check(
|
|
world.apply_presentation_quality(JajceWorld.PresentationQuality.HIGH),
|
|
"High should be restorable after Low"
|
|
)
|
|
await physics_frame
|
|
_check(
|
|
(
|
|
is_equal_approx(viewport.scaling_3d_scale, high_render_scale)
|
|
and environment.glow_enabled
|
|
and (environment.volumetric_fog_enabled == world.call("_supports_volumetric_fog"))
|
|
and environment.adjustment_enabled
|
|
and is_equal_approx(light.directional_shadow_max_distance, high_shadow_distance)
|
|
),
|
|
"Returning to High should restore the authored environment contract"
|
|
)
|
|
_check(
|
|
(
|
|
grass.visible
|
|
and grass.is_physics_processing()
|
|
and grass_controller.is_processing()
|
|
and int(grass.get("particle_count")) == high_particle_count
|
|
and _all_grass_particles_emitting(grass)
|
|
),
|
|
"Returning to High should restore grass rendering and processing"
|
|
)
|
|
|
|
var world_base_scale := float(world.get("_authored_render_scale"))
|
|
world.free()
|
|
_check(
|
|
is_equal_approx(viewport.scaling_3d_scale, world_base_scale),
|
|
"An exiting world should restore the viewport scale it originally owned"
|
|
)
|
|
_check(
|
|
(
|
|
world_grass_material.resource_local_to_scene
|
|
and world_process_material.resource_local_to_scene
|
|
and environment.resource_local_to_scene
|
|
and world_sky.resource_local_to_scene
|
|
and world_sky_material.resource_local_to_scene
|
|
),
|
|
"Every mutable environment, sky, and grass resource should be local to one world"
|
|
)
|
|
var fresh_world: JajceWorld = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
|
root.add_child(fresh_world)
|
|
await process_frame
|
|
await physics_frame
|
|
var fresh_environment: Environment = (
|
|
(fresh_world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
|
)
|
|
var fresh_grass := fresh_world.get_node("TerrainRoot/Terrain3D/CozyGrassField") as Node3D
|
|
var fresh_grass_material := fresh_grass.get("mesh_material_override") as ShaderMaterial
|
|
var fresh_process_material := fresh_grass.get("process_material") as ShaderMaterial
|
|
_check(
|
|
(
|
|
fresh_world.get_active_presentation_quality() == JajceWorld.PresentationQuality.BALANCED
|
|
and fresh_environment.fog_enabled
|
|
and not fresh_environment.volumetric_fog_enabled
|
|
and fresh_grass.visible
|
|
and fresh_grass.is_physics_processing()
|
|
and int(fresh_grass.get("particle_count")) == balanced_particle_count
|
|
),
|
|
"A fresh world after Low should start from an isolated Balanced presentation"
|
|
)
|
|
_check(
|
|
(
|
|
environment != fresh_environment
|
|
and environment.sky != fresh_environment.sky
|
|
and environment.sky.sky_material != fresh_environment.sky.sky_material
|
|
and world_grass_material != fresh_grass_material
|
|
and world_process_material != fresh_process_material
|
|
and _all_grass_particles_use_process_material(fresh_grass, fresh_process_material)
|
|
),
|
|
"Sequential Jajce worlds should not share mutable environment, sky, or grass resources"
|
|
)
|
|
var original_scale := float(fresh_world.get("_authored_render_scale"))
|
|
fresh_world.free()
|
|
_check(
|
|
is_equal_approx(viewport.scaling_3d_scale, original_scale),
|
|
"A sequential world should release its viewport-scale ownership"
|
|
)
|
|
|
|
var older_owner := _create_presentation_only_world(JajceWorld.PresentationQuality.LOW)
|
|
root.add_child(older_owner)
|
|
await process_frame
|
|
var newer_owner := _create_presentation_only_world(JajceWorld.PresentationQuality.BALANCED)
|
|
root.add_child(newer_owner)
|
|
await process_frame
|
|
_check(
|
|
is_equal_approx(float(newer_owner.get("_authored_render_scale")), original_scale),
|
|
"Concurrent owners should inherit the viewport base, not another owner's reduced scale"
|
|
)
|
|
older_owner.free()
|
|
_check(
|
|
is_equal_approx(viewport.scaling_3d_scale, JajceWorld.BALANCED_RENDER_SCALE),
|
|
"A stale owner exiting should not overwrite the active owner's scale"
|
|
)
|
|
newer_owner.free()
|
|
_check(
|
|
is_equal_approx(viewport.scaling_3d_scale, original_scale),
|
|
"The final viewport owner should restore the original scale on exit"
|
|
)
|
|
|
|
var main_scene: Node = load("res://main.tscn").instantiate()
|
|
var time_dial := main_scene.get_node("UI/TimeDial") as Control
|
|
_check(
|
|
(
|
|
is_equal_approx(time_dial.anchor_left, 0.5)
|
|
and is_equal_approx(time_dial.anchor_right, 0.5)
|
|
and is_equal_approx(time_dial.offset_left, -30.0)
|
|
and is_equal_approx(time_dial.offset_right, 30.0)
|
|
),
|
|
"Time dial should remain horizontally centered at weaker-PC window sizes"
|
|
)
|
|
main_scene.free()
|
|
|
|
camera.free()
|
|
if failures.is_empty():
|
|
print("Jajce presentation quality checks passed")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func _all_grass_particles_emitting(grass: Node3D) -> bool:
|
|
var particles: Array = grass.get("particle_nodes")
|
|
if particles.is_empty():
|
|
return false
|
|
for value in particles:
|
|
var particle := value as GPUParticles3D
|
|
if particle == null or not particle.emitting:
|
|
return false
|
|
return true
|
|
|
|
|
|
func _create_presentation_only_world(quality: int) -> JajceWorld:
|
|
var world: JajceWorld = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
|
# Stable simulation-facing IDs deliberately allow only one loaded authority
|
|
# world. The ownership test needs only the presentation subtree.
|
|
world.get_node("WorldObjects").free()
|
|
world.presentation_quality = quality
|
|
return world
|
|
|
|
|
|
func _not_any_grass_particle_emitting(grass: Node3D) -> bool:
|
|
var particles: Array = grass.get("particle_nodes")
|
|
for value in particles:
|
|
var particle := value as GPUParticles3D
|
|
if particle != null and particle.emitting:
|
|
return false
|
|
return true
|
|
|
|
|
|
func _all_grass_particles_use_process_material(grass: Node3D, material: ShaderMaterial) -> bool:
|
|
var particles: Array = grass.get("particle_nodes")
|
|
if particles.is_empty():
|
|
return false
|
|
for value in particles:
|
|
var particle := value as GPUParticles3D
|
|
if particle == null or particle.process_material != material:
|
|
return false
|
|
return true
|
|
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append(message)
|