extends SceneTree const WorldTimeServiceType = preload("res://world/world_time_service.gd") const WorldTimeVisualControllerType = preload( "res://world/world_time_visual_controller.gd" ) const NetworkWorldTimeServiceType = preload( "res://network/network_world_time_service.gd" ) const FishingContextType = preload("res://fishing/fishing_context.gd") const FishDataType = preload("res://fish/fish_data.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const FishingSpotType = preload("res://fishing/fishing_spot.gd") const FishableWaterRegionType = preload( "res://world/fishable_water_region.gd" ) const WeatherIconType = preload("res://ui/weather_icon.gd") const CalendarSeasonType = preload("res://world/calendar_season.gd") const Catalog: FishPoolType = preload("res://fish/pools/fish_catalog.tres") const SaltWaterMaterial: ShaderMaterial = preload( "res://world/materials/stylized_water.tres" ) const FreshWaterMaterial: ShaderMaterial = preload( "res://world/materials/stylized_water_fresh.tres" ) func _initialize() -> void: call_deferred("_run") func _run() -> void: _validate_clock_boundaries_and_duration() _validate_system_clock_authority() _validate_persistent_host_clock() _validate_fishing_availability() _validate_fishing_spot_context() _validate_network_snapshot_bounds() _validate_environment_presentation() _validate_weather_clock_icon() print("World time validation: PASS") quit() func _validate_clock_boundaries_and_duration() -> void: assert(WorldTimeServiceType.REAL_SECONDS_PER_CYCLE == 86400.0) assert(is_equal_approx( WorldTimeServiceType.HOURS_PER_REAL_SECOND, 1.0 / 3600.0, )) assert(is_equal_approx( WorldTimeServiceType.time_hours_from_datetime({ "hour": 17, "minute": 30, "second": 18, }), 17.505, )) assert( WorldTimeServiceType.date_id_from_datetime({ "year": 2026, "month": 8, "day": 18, }) == "2026-08-18" ) assert(WorldTimeServiceType.is_valid_calendar_date_id("2026-08-18")) assert(WorldTimeServiceType.is_valid_calendar_date_id("2028-02-29")) assert(not WorldTimeServiceType.is_valid_calendar_date_id("2026-02-29")) assert(not WorldTimeServiceType.is_valid_calendar_date_id("2026-13-01")) assert(not WorldTimeServiceType.is_valid_calendar_date_id("2026-8-18")) assert( WorldTimeServiceType.format_calendar_date("2026-08-18") == "tuesday, august 18" ) assert( CalendarSeasonType.from_date_id("2026-03-01") == CalendarSeasonType.Season.SPRING ) assert( CalendarSeasonType.from_date_id("2026-06-01") == CalendarSeasonType.Season.SUMMER ) assert( CalendarSeasonType.from_date_id("2026-09-01") == CalendarSeasonType.Season.FALL ) assert( CalendarSeasonType.from_date_id("2026-12-01") == CalendarSeasonType.Season.WINTER ) assert( CalendarSeasonType.from_date_id("invalid") == CalendarSeasonType.UNKNOWN ) assert( WorldTimeServiceType.calendar_cycle_id_from_datetime({ "year": 2026, "month": 8, "day": 18, "hour": 7, "minute": 59, "second": 0, }, WorldTimeServiceType.DAY_START_HOUR) == "2026-08-17" ) assert( WorldTimeServiceType.calendar_cycle_id_from_datetime({ "year": 2026, "month": 8, "day": 18, "hour": 8, "minute": 0, "second": 0, }, WorldTimeServiceType.DAY_START_HOUR) == "2026-08-18" ) assert( WorldTimeServiceType.phase_for_hour(7.49) == WorldTimeServiceType.Phase.NIGHT ) assert( WorldTimeServiceType.phase_for_hour(7.5) == WorldTimeServiceType.Phase.DAWN ) assert( WorldTimeServiceType.phase_for_hour(8.49) == WorldTimeServiceType.Phase.DAWN ) assert( WorldTimeServiceType.phase_for_hour(8.5) == WorldTimeServiceType.Phase.DAY ) assert( WorldTimeServiceType.phase_for_hour(19.5) == WorldTimeServiceType.Phase.DUSK ) assert( WorldTimeServiceType.phase_for_hour(20.5) == WorldTimeServiceType.Phase.NIGHT ) assert(WorldTimeServiceType.format_clock_time(0.0) == "12:00 am") assert(WorldTimeServiceType.format_clock_time(8.0) == "8:00 am") assert(WorldTimeServiceType.format_clock_time(20.5) == "8:30 pm") var clock := WorldTimeServiceType.new() root.add_child(clock) clock.begin_test_session(8.0, "2026-08-18") assert(clock.get_calendar_date_id() == "2026-08-18") assert(clock.get_calendar_text() == "tuesday, august 18") assert(clock.get_season() == CalendarSeasonType.Season.SUMMER) assert(clock.get_season_text() == "summer") clock.advance_time(12.0 * 60.0 * 60.0) assert(is_equal_approx(clock.get_time_hours(), 20.0)) assert(clock.is_night_period()) assert(clock.is_transition()) clock.advance_time(12.0 * 60.0 * 60.0) assert(is_equal_approx(clock.get_time_hours(), 8.0)) assert(not clock.is_night_period()) assert(clock.is_transition()) # Small high-refresh deltas must still advance the displayed minute. Using # is_equal_approx() per frame used to suppress every clock notification. var emitted_times: Array[float] = [] clock.time_changed.connect( func(time_hours: float, _phase: WorldTimeService.Phase) -> void: emitted_times.append(time_hours) ) clock.begin_test_session(12.0 + 31.0 / 60.0) emitted_times.clear() for _frame: int in 7201: clock.advance_time(1.0 / 60.0) assert(clock.get_clock_text() == "12:33 pm") assert(emitted_times.size() >= 2) clock.queue_free() func _validate_persistent_host_clock() -> void: var clock := WorldTimeServiceType.new() root.add_child(clock) assert(clock.restore_persistent_time_hours(18.75)) clock.begin_test_session(18.75) assert(is_equal_approx(clock.get_time_hours(), 18.75)) clock.synchronize_time(19.25) assert(is_equal_approx(clock.get_persistent_time_hours(), 19.25)) clock.synchronize_time(6.5) assert(is_equal_approx(clock.get_time_hours(), 6.5)) assert(is_equal_approx(clock.get_persistent_time_hours(), 6.5)) assert(not clock.restore_persistent_time_hours(-1.0)) assert(not clock.restore_persistent_time_hours(24.0)) clock.queue_free() func _validate_system_clock_authority() -> void: var clock := WorldTimeServiceType.new() root.add_child(clock) clock.begin_authoritative_session() var system_datetime: Dictionary = Time.get_datetime_dict_from_system(false) var system_hour: float = WorldTimeServiceType.time_hours_from_datetime( system_datetime ) assert(clock.is_using_system_clock()) assert(_wrapped_time_difference( clock.get_time_hours(), system_hour ) < 2.0 / 3600.0) assert( clock.get_calendar_date_id() == WorldTimeServiceType.date_id_from_datetime(system_datetime) ) assert(clock.set_authoritative_time(12.0)) assert(not clock.is_using_system_clock()) clock.clear_editor_time_override() assert(clock.is_using_system_clock()) clock.queue_free() func _validate_fishing_availability() -> void: var day_context := FishingContextType.new() day_context.location_tags = [&"starter_pond"] day_context.is_night = false var night_context := FishingContextType.new() night_context.location_tags = [&"starter_pond"] night_context.is_night = true var transition_context := FishingContextType.new() transition_context.location_tags = [&"starter_pond"] transition_context.is_night = true transition_context.is_day_night_transition = true for fish_id: StringName in [&"bluegill", &"sunfish"]: var day_fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(day_fish.availability.is_available(day_context)) assert(not day_fish.availability.is_available(night_context)) assert(day_fish.availability.is_available(transition_context)) for fish_id: StringName in [ &"catfish_blue", &"catfish_channel", &"catfish_flathead", &"catfish_white", ]: var night_fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(not night_fish.availability.is_available(day_context)) assert(night_fish.availability.is_available(night_context)) assert(night_fish.availability.is_available(transition_context)) for fish_id: StringName in [ &"bass", &"carp", &"tuna_albacore", &"tuna_bigeye", &"tuna_bluefin", &"tuna_skipjack", &"tuna_yellowfin", &"goby_round", &"salmon_atlantic", &"salmon_chum", &"salmon_coho", &"salmon_pink", &"salmon_sockeye", ]: var all_time_fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(all_time_fish.availability.is_available(day_context)) assert(all_time_fish.availability.is_available(night_context)) assert(all_time_fish.availability.is_available(transition_context)) func _validate_fishing_spot_context() -> void: var clock := WorldTimeServiceType.new() clock.begin_test_session(20.25, "2026-12-18") var fishing_spot := FishingSpotType.new() fishing_spot.set("_world_time", clock) var region := FishableWaterRegionType.new() region.location_tags = [&"starter_pond"] region.water_type = WaterType.Type.FRESH_WATER var dusk_context: FishingContext = fishing_spot.build_network_context(region) assert(dusk_context.is_night) assert(dusk_context.is_day_night_transition) assert(dusk_context.season == CalendarSeasonType.Season.WINTER) clock.synchronize_time(14.0) var day_context: FishingContext = fishing_spot.build_network_context(region) assert(not day_context.is_night) assert(not day_context.is_day_night_transition) region.free() fishing_spot.free() clock.free() func _validate_network_snapshot_bounds() -> void: assert(NetworkWorldTimeServiceType.validate_snapshot({ "session_id": "session", "time_hours": 8.25, "calendar_date_id": "2026-08-18", "sequence": 1, })) assert(not NetworkWorldTimeServiceType.validate_snapshot({ "session_id": "session", "time_hours": 24.0, "calendar_date_id": "2026-08-18", "sequence": 1, })) assert(not NetworkWorldTimeServiceType.validate_snapshot({ "session_id": "", "time_hours": 8.0, "calendar_date_id": "2026-08-18", "sequence": 1, })) assert(not NetworkWorldTimeServiceType.validate_snapshot({ "session_id": "session", "time_hours": 8.0, "calendar_date_id": "2026-02-29", "sequence": 1, })) func _validate_environment_presentation() -> void: var world_root := Node3D.new() root.add_child(world_root) var world_environment := WorldEnvironment.new() var environment := Environment.new() var sky := Sky.new() var sky_material := ShaderMaterial.new() sky_material.shader = preload( "res://world/environment/netfishing_sky.gdshader" ) sky.sky_material = sky_material environment.sky = sky environment.adjustment_enabled = true environment.fog_enabled = true world_environment.environment = environment world_root.add_child(world_environment) var sun := DirectionalLight3D.new() world_root.add_child(sun) var clock := WorldTimeServiceType.new() world_root.add_child(clock) var visuals := WorldTimeVisualControllerType.new() world_root.add_child(visuals) visuals.setup(clock, world_environment, sun) visuals.apply_time_immediately(12.0) var runtime_environment: Environment = world_environment.environment var runtime_sky_material := ( runtime_environment.sky.sky_material as ShaderMaterial ) var day_horizon_value: Variant = runtime_sky_material.get_shader_parameter( "sky_horizon_color" ) assert(day_horizon_value is Color) var day_horizon: Color = day_horizon_value as Color var day_sun_visibility: float = float( runtime_sky_material.get_shader_parameter("sun_visibility") ) var day_sun_direction: Vector3 = ( runtime_sky_material.get_shader_parameter("sun_direction") as Vector3 ) assert(day_sun_visibility > 0.99) assert(day_sun_direction.y > 0.85) assert( float(runtime_sky_material.get_shader_parameter("moon_visibility")) < 0.01 ) assert( float(runtime_sky_material.get_shader_parameter("star_visibility")) < 0.01 ) var day_ambient_energy: float = runtime_environment.ambient_light_energy assert(not runtime_environment.fog_enabled) assert(is_equal_approx(runtime_environment.fog_sky_affect, 0.35)) var water_shader: Shader = preload( "res://world/materials/stylized_water.gdshader" ) var sky_shader: Shader = preload( "res://world/environment/netfishing_sky.gdshader" ) assert("uniform float fog_horizon_occlusion" in sky_shader.code) assert("fog_horizon_color.rgb" in sky_shader.code) assert("uniform float star_visibility" in sky_shader.code) assert("float procedural_star_field" in sky_shader.code) assert("upper_sky_fade" in sky_shader.code) assert("fog_disabled" in water_shader.code) assert("surface_view_position = view_vertex.xyz" in water_shader.code) assert("surface_view_distance = length(surface_view_position)" in water_shader.code) assert( "max(weather_fog_end, weather_fog_begin + 1.0),\n" + "\t\tsurface_view_distance" in water_shader.code ) assert("weather_fog_near_amount" in water_shader.code) assert("float weather_fog_alpha = mix(" in water_shader.code) assert("ALPHA = clamp(water_alpha, 0.1, weather_fog_alpha)" in water_shader.code) assert(is_zero_approx(float( SaltWaterMaterial.get_shader_parameter("weather_fog_amount") ))) assert(is_equal_approx(float( SaltWaterMaterial.get_shader_parameter("surface_highlight_night_floor") ), WorldTimeVisualController.DEFAULT_SURFACE_HIGHLIGHT_FLOOR)) var day_water_brightness := float( SaltWaterMaterial.get_shader_parameter("environment_brightness") ) assert(day_water_brightness > 0.99) assert(is_equal_approx( float(FreshWaterMaterial.get_shader_parameter("environment_brightness")), day_water_brightness, )) visuals.apply_time_immediately(0.0) var night_horizon: Color = runtime_sky_material.get_shader_parameter( "sky_horizon_color" ) as Color assert(night_horizon != day_horizon) assert( float(runtime_sky_material.get_shader_parameter("sun_visibility")) < 0.01 ) var night_moon_visibility: float = float( runtime_sky_material.get_shader_parameter("moon_visibility") ) var night_moon_direction: Vector3 = ( runtime_sky_material.get_shader_parameter("moon_direction") as Vector3 ) var night_sun_direction: Vector3 = ( runtime_sky_material.get_shader_parameter("sun_direction") as Vector3 ) assert(night_moon_visibility > 0.99) assert( float(runtime_sky_material.get_shader_parameter("star_visibility")) > 0.99 ) assert(( runtime_sky_material.get_shader_parameter("star_color") as Color ).is_equal_approx(WorldTimeVisualController.STAR_COLOR)) assert(is_equal_approx( float(runtime_sky_material.get_shader_parameter("star_strength")), WorldTimeVisualController.STAR_STRENGTH, )) assert(night_moon_direction.y > 0.85) assert(night_moon_direction.is_equal_approx(-night_sun_direction)) assert(runtime_environment.ambient_light_energy < day_ambient_energy) assert(is_zero_approx(float( runtime_sky_material.get_shader_parameter("fog_horizon_occlusion") ))) var night_water_brightness := float( SaltWaterMaterial.get_shader_parameter("environment_brightness") ) assert(night_water_brightness < day_water_brightness * 0.4) assert(is_equal_approx( float(FreshWaterMaterial.get_shader_parameter("environment_brightness")), night_water_brightness, )) var night_water_tint := ( SaltWaterMaterial.get_shader_parameter("environment_tint") as Color ) assert(night_water_tint.get_luminance() < 0.5) var night_background_energy := runtime_environment.background_energy_multiplier var night_ambient_energy := runtime_environment.ambient_light_energy var night_fog_light_energy := runtime_environment.fog_light_energy var night_adjustment_brightness := runtime_environment.adjustment_brightness var night_adjustment_saturation := runtime_environment.adjustment_saturation visuals.apply_weather_immediately(WorldWeatherService.Weather.FOGGY) visuals.apply_time_immediately(0.0) assert(runtime_environment.fog_enabled) assert(float( SaltWaterMaterial.get_shader_parameter("weather_fog_amount") ) > 0.99) assert(is_equal_approx(float( SaltWaterMaterial.get_shader_parameter("weather_fog_near_amount") ), WorldTimeVisualController.FOGGY_NIGHT_WATER_NEAR_FOG_AMOUNT)) assert(is_equal_approx(float( SaltWaterMaterial.get_shader_parameter("surface_highlight_night_floor") ), WorldTimeVisualController.FOGGY_NIGHT_SURFACE_HIGHLIGHT_FLOOR)) assert(is_equal_approx(float( runtime_sky_material.get_shader_parameter("fog_horizon_occlusion") ), 1.0)) assert(is_zero_approx(float( runtime_sky_material.get_shader_parameter("star_visibility") ))) assert(( runtime_sky_material.get_shader_parameter("fog_horizon_color") as Color ).is_equal_approx(WorldTimeVisualController.NIGHT_FOG)) var night_rendered_fog_color := ( runtime_environment.fog_light_color.srgb_to_linear() ) night_rendered_fog_color.r *= runtime_environment.fog_light_energy night_rendered_fog_color.g *= runtime_environment.fog_light_energy night_rendered_fog_color.b *= runtime_environment.fog_light_energy night_rendered_fog_color.a = 1.0 night_rendered_fog_color = night_rendered_fog_color.linear_to_srgb() var night_water_fog_color := ( SaltWaterMaterial.get_shader_parameter("weather_fog_color") as Color ) var corrected_night_fog_color := night_rendered_fog_color corrected_night_fog_color.r *= ( WorldTimeVisualController.FOGGY_DARK_WATER_FOG_CORRECTION.r ) corrected_night_fog_color.g *= ( WorldTimeVisualController.FOGGY_DARK_WATER_FOG_CORRECTION.g ) corrected_night_fog_color.b *= ( WorldTimeVisualController.FOGGY_DARK_WATER_FOG_CORRECTION.b ) assert(night_water_fog_color.is_equal_approx(corrected_night_fog_color)) var foggy_sky_horizon := ( runtime_sky_material.get_shader_parameter("sky_horizon_color") as Color ) assert(foggy_sky_horizon.is_equal_approx(night_horizon)) assert(is_equal_approx(runtime_environment.fog_sky_affect, 1.0)) assert(is_zero_approx(runtime_environment.fog_aerial_perspective)) assert(is_equal_approx(runtime_environment.fog_depth_begin, 3.0)) assert(is_equal_approx(runtime_environment.fog_depth_end, 18.0)) assert(is_equal_approx( float(SaltWaterMaterial.get_shader_parameter("environment_brightness")), night_water_brightness, )) assert(is_equal_approx( runtime_environment.background_energy_multiplier, night_background_energy, )) assert(is_equal_approx( runtime_environment.ambient_light_energy, night_ambient_energy, )) assert(is_equal_approx( runtime_environment.fog_light_energy, night_fog_light_energy, )) assert(is_equal_approx( runtime_environment.adjustment_brightness, night_adjustment_brightness, )) assert(is_equal_approx( runtime_environment.adjustment_saturation, night_adjustment_saturation, )) visuals.apply_time_immediately(8.0) assert(is_equal_approx(float( SaltWaterMaterial.get_shader_parameter("weather_fog_near_amount") ), WorldTimeVisualController.FOGGY_NIGHT_WATER_NEAR_FOG_AMOUNT)) assert(is_equal_approx(float( runtime_sky_material.get_shader_parameter("fog_horizon_occlusion") ), 1.0)) visuals.apply_time_immediately(20.0) assert(is_zero_approx(float( SaltWaterMaterial.get_shader_parameter("weather_fog_near_amount") ))) assert(is_zero_approx(float( runtime_sky_material.get_shader_parameter("fog_horizon_occlusion") ))) visuals.apply_weather_immediately(WorldWeatherService.Weather.SUNNY) visuals.apply_time_immediately(20.0) assert(not runtime_environment.fog_enabled) assert(is_zero_approx(float( SaltWaterMaterial.get_shader_parameter("weather_fog_amount") ))) assert(is_zero_approx(float( SaltWaterMaterial.get_shader_parameter("weather_fog_near_amount") ))) assert(is_equal_approx(float( SaltWaterMaterial.get_shader_parameter("surface_highlight_night_floor") ), WorldTimeVisualController.DEFAULT_SURFACE_HIGHLIGHT_FLOOR)) assert(is_zero_approx(float( runtime_sky_material.get_shader_parameter("fog_horizon_occlusion") ))) var dusk_star_visibility := float( runtime_sky_material.get_shader_parameter("star_visibility") ) assert(dusk_star_visibility > 0.45 and dusk_star_visibility < 0.55) var dusk_horizon: Color = runtime_sky_material.get_shader_parameter( "sky_horizon_color" ) as Color assert(dusk_horizon.r > day_horizon.r) world_root.queue_free() func _validate_weather_clock_icon() -> void: var weather_icon := WeatherIconType.new() root.add_child(weather_icon) assert(weather_icon.tooltip_text == "clear") assert(not weather_icon.is_nighttime()) weather_icon.set_nighttime(true) assert(weather_icon.is_nighttime()) weather_icon.set_weather(WorldWeatherService.Weather.CLOUDY) assert(weather_icon.tooltip_text == "cloudy") weather_icon.queue_free() static func _wrapped_time_difference(left: float, right: float) -> float: var difference: float = absf(left - right) return minf(difference, WorldTimeServiceType.HOURS_PER_DAY - difference)