fix: harden recovery flow and clean diagnostics
This commit is contained in:
parent
0158b0215f
commit
ff78710303
64 changed files with 1163 additions and 418 deletions
|
|
@ -44,18 +44,20 @@ func _init() -> void:
|
|||
assert(_has_joypad_button(&"open_quick_actions", JOY_BUTTON_DPAD_DOWN))
|
||||
assert(_has_joypad_button(&"open_chat", JOY_BUTTON_BACK))
|
||||
assert(_has_joypad_button(&"focus_gameplay", JOY_BUTTON_LEFT_SHOULDER))
|
||||
assert(
|
||||
GameUIType.VIRTUAL_MOUSE_TRIGGER_AXIS == JOY_AXIS_TRIGGER_RIGHT
|
||||
)
|
||||
assert(
|
||||
var virtual_mouse_trigger_axis: int = GameUIType.VIRTUAL_MOUSE_TRIGGER_AXIS
|
||||
var virtual_mouse_shared_trigger_axis: int = (
|
||||
GameUIType.VIRTUAL_MOUSE_SHARED_TRIGGER_AXIS
|
||||
== JOY_AXIS_TRIGGER_LEFT
|
||||
)
|
||||
assert(
|
||||
var virtual_mouse_secondary_click_axis: int = (
|
||||
GameUIType.VIRTUAL_MOUSE_SECONDARY_CLICK_AXIS
|
||||
== JOY_AXIS_TRIGGER_LEFT
|
||||
)
|
||||
assert(PlayerType.CONTROLLER_ZOOM_TRIGGER_AXIS == JOY_AXIS_TRIGGER_LEFT)
|
||||
var controller_zoom_trigger_axis: int = (
|
||||
PlayerType.CONTROLLER_ZOOM_TRIGGER_AXIS
|
||||
)
|
||||
assert(virtual_mouse_trigger_axis == JOY_AXIS_TRIGGER_RIGHT)
|
||||
assert(virtual_mouse_shared_trigger_axis == JOY_AXIS_TRIGGER_LEFT)
|
||||
assert(virtual_mouse_secondary_click_axis == JOY_AXIS_TRIGGER_LEFT)
|
||||
assert(controller_zoom_trigger_axis == JOY_AXIS_TRIGGER_LEFT)
|
||||
assert(is_zero_approx(
|
||||
GameUIType.normalized_trigger_strength(-1.0, -1.0)
|
||||
))
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ func _validate_mesh_weights(mesh_instance: MeshInstance3D) -> int:
|
|||
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||
assert(not weights.is_empty())
|
||||
assert(not vertices.is_empty())
|
||||
var components_per_vertex := weights.size() / vertices.size()
|
||||
var components_per_vertex: int = int(
|
||||
float(weights.size()) / float(vertices.size())
|
||||
)
|
||||
assert(components_per_vertex == 4 or components_per_vertex == 8)
|
||||
for offset in range(0, weights.size(), components_per_vertex):
|
||||
var total := 0.0
|
||||
|
|
|
|||
|
|
@ -367,13 +367,13 @@ func _validate_auto_map(manager: ControllerMappingManagerType) -> String:
|
|||
if str(binding.get("kind", "")) == "button":
|
||||
var button := InputEventJoypadButton.new()
|
||||
button.device = manager.get_active_device_id()
|
||||
button.button_index = int(binding.get("button", -1))
|
||||
button.button_index = int(binding.get("button", -1)) as JoyButton
|
||||
button.pressed = true
|
||||
manager.controller_input_observed.emit(button)
|
||||
else:
|
||||
var motion := InputEventJoypadMotion.new()
|
||||
motion.device = manager.get_active_device_id()
|
||||
motion.axis = int(binding.get("axis", -1))
|
||||
motion.axis = int(binding.get("axis", -1)) as JoyAxis
|
||||
motion.axis_value = float(binding.get("direction", -1.0))
|
||||
manager.controller_input_observed.emit(motion)
|
||||
if panel._auto_map_active:
|
||||
|
|
|
|||
|
|
@ -2015,7 +2015,7 @@ func _assert_neighbor(
|
|||
% [
|
||||
origin.name,
|
||||
property,
|
||||
actual.name if actual != null else "nothing",
|
||||
str(actual.name) if actual != null else "nothing",
|
||||
expected.name,
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,10 +21,16 @@ func _run() -> void:
|
|||
assert(not bool(defaults.get("public_listing")))
|
||||
assert(not bool(defaults.get("chat_logging")))
|
||||
assert(str(defaults.get("chat_log_path")).is_empty())
|
||||
assert(NetworkChatService.BURST_COUNT == 3)
|
||||
assert(NetworkChatService.WINDOW_COUNT == 5)
|
||||
assert(is_equal_approx(NetworkChatService.WINDOW_SECONDS, 10.0))
|
||||
assert(NetworkChatService.CALL_COOLDOWN_MILLISECONDS == 90)
|
||||
var chat_burst_count: int = NetworkChatService.BURST_COUNT
|
||||
var chat_window_count: int = NetworkChatService.WINDOW_COUNT
|
||||
var chat_window_seconds: float = NetworkChatService.WINDOW_SECONDS
|
||||
var chat_call_cooldown_milliseconds: int = (
|
||||
NetworkChatService.CALL_COOLDOWN_MILLISECONDS
|
||||
)
|
||||
assert(chat_burst_count == 3)
|
||||
assert(chat_window_count == 5)
|
||||
assert(is_equal_approx(chat_window_seconds, 10.0))
|
||||
assert(chat_call_cooldown_milliseconds == 90)
|
||||
|
||||
var path: String = ProjectSettings.globalize_path(
|
||||
"user://dedicated-server-validation.cfg"
|
||||
|
|
@ -156,14 +162,17 @@ func _run() -> void:
|
|||
str(discovery.call("_default_room_name", "River"))
|
||||
== "River's Server"
|
||||
)
|
||||
assert(
|
||||
var upnp_renew_interval_seconds: float = (
|
||||
DiscoveryClient.UPNP_RENEW_INTERVAL_SECONDS
|
||||
< float(DiscoveryClient.UPNP_MAPPING_DURATION_SECONDS)
|
||||
)
|
||||
assert(
|
||||
var upnp_mapping_duration_seconds: float = float(
|
||||
DiscoveryClient.UPNP_MAPPING_DURATION_SECONDS
|
||||
)
|
||||
var upnp_retry_interval_seconds: float = (
|
||||
DiscoveryClient.UPNP_RETRY_INTERVAL_SECONDS
|
||||
< DiscoveryClient.UPNP_RENEW_INTERVAL_SECONDS
|
||||
)
|
||||
assert(upnp_renew_interval_seconds < upnp_mapping_duration_seconds)
|
||||
assert(upnp_retry_interval_seconds < upnp_renew_interval_seconds)
|
||||
var discovery_settings_path: String = ProjectSettings.globalize_path(
|
||||
DiscoveryClient.SETTINGS_PATH
|
||||
)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func _run() -> void:
|
|||
assert(session.set_host_open(false))
|
||||
|
||||
await _test_host_shop_purchase(main, player, shop_service)
|
||||
await _test_host_rod_purchase(player, shop_service)
|
||||
_test_host_rod_purchase(player, shop_service)
|
||||
await _test_host_art_shop_purchase(main, player, shop_service)
|
||||
await _test_fishing_shop_sale_ui(
|
||||
main, player, catalog, sale_service, reservations
|
||||
|
|
|
|||
|
|
@ -34,20 +34,20 @@ func _run() -> void:
|
|||
var root_controls: Array[Control] = (
|
||||
FileDialogControllerNavigationType.interactive_controls(root_scope)
|
||||
)
|
||||
var directory_list: ItemList
|
||||
var parent_button: Button
|
||||
var create_folder_button: Button
|
||||
var path_edit: LineEdit
|
||||
var drive_button: MenuButton
|
||||
var refresh_button: Button
|
||||
var favorite_button: Button
|
||||
var hidden_button: Button
|
||||
var grid_button: Button
|
||||
var list_button: Button
|
||||
var filter_button: Button
|
||||
var sort_button: MenuButton
|
||||
var cancel_button: Button
|
||||
var select_button: Button
|
||||
var directory_list: ItemList = null
|
||||
var parent_button: Button = null
|
||||
var create_folder_button: Button = null
|
||||
var path_edit: LineEdit = null
|
||||
var drive_button: MenuButton = null
|
||||
var refresh_button: Button = null
|
||||
var favorite_button: Button = null
|
||||
var hidden_button: Button = null
|
||||
var grid_button: Button = null
|
||||
var list_button: Button = null
|
||||
var filter_button: Button = null
|
||||
var sort_button: MenuButton = null
|
||||
var cancel_button: Button = null
|
||||
var select_button: Button = null
|
||||
for control: Control in root_controls:
|
||||
if (
|
||||
control is ItemList
|
||||
|
|
|
|||
|
|
@ -200,15 +200,17 @@ func _run() -> void:
|
|||
var invalid_state: Dictionary = valid_state.duplicate(true)
|
||||
invalid_state["display_scale"] = 1000.0
|
||||
assert(not NetworkFishShowcaseProtocol.validate_state(invalid_state))
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 12)
|
||||
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 18)
|
||||
var protocol_version: int = NetworkProtocol.PROTOCOL_VERSION
|
||||
var enet_channel_count: int = NetworkProtocol.ENET_CHANNEL_COUNT
|
||||
var fish_quality_capability: String = NetworkProtocol.FISH_QUALITY_CAPABILITY
|
||||
var showcase_capability: StringName = NetworkFishShowcaseProtocol.CAPABILITY
|
||||
assert(protocol_version == 12)
|
||||
assert(enet_channel_count == 18)
|
||||
assert(
|
||||
NetworkProtocol.FISH_QUALITY_CAPABILITY
|
||||
== "fish_quality_v1"
|
||||
fish_quality_capability == "fish_quality_v1"
|
||||
)
|
||||
assert(
|
||||
NetworkFishShowcaseProtocol.CAPABILITY
|
||||
== &"fish_showcase_v1"
|
||||
showcase_capability == &"fish_showcase_v1"
|
||||
)
|
||||
|
||||
print("Fish hotbar showcase validation: PASS")
|
||||
|
|
|
|||
|
|
@ -26,18 +26,28 @@ func _run() -> void:
|
|||
_validate_collection_mastery()
|
||||
_validate_version_four_migration()
|
||||
_validate_fishing_protocol_v2()
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 12)
|
||||
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 18)
|
||||
assert(NetworkProtocol.MOVEMENT_ANIMATION_CHANNEL == 11)
|
||||
assert(
|
||||
var protocol_version: int = NetworkProtocol.PROTOCOL_VERSION
|
||||
var enet_channel_count: int = NetworkProtocol.ENET_CHANNEL_COUNT
|
||||
var movement_animation_channel: int = (
|
||||
NetworkProtocol.MOVEMENT_ANIMATION_CHANNEL
|
||||
)
|
||||
var movement_reconciliation_capability: String = (
|
||||
NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY
|
||||
== "movement_reconciliation_v2"
|
||||
)
|
||||
var fishing_replication_capability: String = (
|
||||
NetworkProtocol.FISHING_REPLICATION_CAPABILITY
|
||||
)
|
||||
var fish_quality_capability: String = NetworkProtocol.FISH_QUALITY_CAPABILITY
|
||||
assert(protocol_version == 12)
|
||||
assert(enet_channel_count == 18)
|
||||
assert(movement_animation_channel == 11)
|
||||
assert(
|
||||
movement_reconciliation_capability == "movement_reconciliation_v2"
|
||||
)
|
||||
assert(
|
||||
NetworkProtocol.FISHING_REPLICATION_CAPABILITY
|
||||
== "fishing_replication_v2"
|
||||
fishing_replication_capability == "fishing_replication_v2"
|
||||
)
|
||||
assert(NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1")
|
||||
assert(fish_quality_capability == "fish_quality_v1")
|
||||
print("Fish quality validation: PASS")
|
||||
quit()
|
||||
|
||||
|
|
@ -71,11 +81,17 @@ func _validate_fishing_protocol_v2() -> void:
|
|||
NetworkFishingProtocol.validate_cast_request(invalid_sequence)
|
||||
== "Fishing request values are outside allowed limits."
|
||||
)
|
||||
assert(NetworkFishingProtocol.OBSERVER_SNAPSHOT_CHANNEL == 10)
|
||||
assert(NetworkWorldSpawnProtocol.SNAPSHOT_CHANNEL == 15)
|
||||
assert(
|
||||
var observer_snapshot_channel: int = (
|
||||
NetworkFishingProtocol.OBSERVER_SNAPSHOT_CHANNEL
|
||||
)
|
||||
var world_spawn_snapshot_channel: int = (
|
||||
NetworkWorldSpawnProtocol.SNAPSHOT_CHANNEL
|
||||
!= NetworkFishingProtocol.SNAPSHOT_CHANNEL
|
||||
)
|
||||
var fishing_snapshot_channel: int = NetworkFishingProtocol.SNAPSHOT_CHANNEL
|
||||
assert(observer_snapshot_channel == 10)
|
||||
assert(world_spawn_snapshot_channel == 15)
|
||||
assert(
|
||||
world_spawn_snapshot_channel != fishing_snapshot_channel
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -240,10 +256,14 @@ func _validate_barrier_challenge_curve() -> void:
|
|||
|
||||
|
||||
func _validate_fight_pacing_and_reel_upgrades() -> void:
|
||||
assert(is_equal_approx(CatchController.CHASE_SPEED, 0.07))
|
||||
assert(is_equal_approx(CatchController.CHASE_START_DELAY, 1.5))
|
||||
assert(is_equal_approx(CatchController.CHASE_START_OFFSET, 0.04))
|
||||
assert(is_equal_approx(Player.BASE_REEL_SPEED, 0.16))
|
||||
var chase_speed: float = CatchController.CHASE_SPEED
|
||||
var chase_start_delay: float = CatchController.CHASE_START_DELAY
|
||||
var chase_start_offset: float = CatchController.CHASE_START_OFFSET
|
||||
var base_reel_speed: float = Player.BASE_REEL_SPEED
|
||||
assert(is_equal_approx(chase_speed, 0.07))
|
||||
assert(is_equal_approx(chase_start_delay, 1.5))
|
||||
assert(is_equal_approx(chase_start_offset, 0.04))
|
||||
assert(is_equal_approx(base_reel_speed, 0.16))
|
||||
|
||||
var upgrades := PlayerFishingUpgrades.new()
|
||||
assert(is_equal_approx(upgrades.get_reel_speed_multiplier(), 1.0))
|
||||
|
|
|
|||
|
|
@ -277,6 +277,28 @@ func _run() -> void:
|
|||
water_recovery.call("_finish_recovery")
|
||||
assert(player.is_movement_enabled())
|
||||
|
||||
# Session/world teardown can interrupt the fade before its normal completion.
|
||||
# Recovery must still release FishingSpot's external input lock without
|
||||
# restoring movement that gameplay shutdown already disabled.
|
||||
water_recovery.call(
|
||||
"_on_recovery_requested",
|
||||
player,
|
||||
player.global_position.y,
|
||||
)
|
||||
assert(water_recovery.is_recovery_active())
|
||||
var screen_fade := game_ui.get_screen_fade() as ScreenFade
|
||||
assert(screen_fade != null)
|
||||
screen_fade.fade_to_black(int(water_recovery.get("_generation")))
|
||||
water_recovery.set_recovery_enabled(false)
|
||||
assert(not water_recovery.is_recovery_active())
|
||||
assert(not player.is_water_recovery_active())
|
||||
assert(not bool(fishing_spot.get("_external_input_blocked")))
|
||||
assert(not player.is_movement_enabled())
|
||||
assert(not screen_fade.visible)
|
||||
water_recovery.set_recovery_enabled(true)
|
||||
player.set_movement_enabled(true)
|
||||
player.set_camera_input_enabled(true)
|
||||
|
||||
print("Fishing authority validation: PASS")
|
||||
session.disconnect_session("")
|
||||
main.queue_free()
|
||||
|
|
|
|||
|
|
@ -271,7 +271,6 @@ func _run_client() -> void:
|
|||
assert(player.global_position.distance_to(exterior_spawn.origin) > 1.0)
|
||||
for _frame: int in 2:
|
||||
await physics_frame
|
||||
var expected_position: Vector3 = player.global_position
|
||||
var expected_direction: Vector3 = player.get_facing_direction()
|
||||
|
||||
player.set_casting_visual()
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ func _run() -> void:
|
|||
|
||||
wall.queue_free()
|
||||
await process_frame
|
||||
var floor := _make_surface(
|
||||
var floor_surface := _make_surface(
|
||||
Vector3(10.0, 0.2, 10.0),
|
||||
Vector3(0.0, -0.1, -1.7),
|
||||
)
|
||||
root.add_child(floor)
|
||||
root.add_child(floor_surface)
|
||||
await physics_frame
|
||||
controller.call("_update_marker_target")
|
||||
assert(bool(controller.get("_marker_has_surface")))
|
||||
|
|
@ -80,7 +80,7 @@ func _run() -> void:
|
|||
)
|
||||
)
|
||||
|
||||
floor.queue_free()
|
||||
floor_surface.queue_free()
|
||||
controller.queue_free()
|
||||
player.queue_free()
|
||||
await process_frame
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ const Gatherables: GatherableCatalog = preload(
|
|||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const FOLIAGE_WIND_SHADER: Shader = preload(
|
||||
"res://world/materials/foliage_wind.gdshader"
|
||||
)
|
||||
const FOLIAGE_WIND_SOURCE_MATERIALS_META: StringName = (
|
||||
&"foliage_wind_source_materials"
|
||||
)
|
||||
const FIRST_SEED := 13001
|
||||
const SECOND_SEED := 13002
|
||||
const RIVER_FALLBACK_SEED := 13012
|
||||
|
|
@ -130,8 +136,8 @@ func _validate_generated_region(
|
|||
var placements := generator.placement_keys()
|
||||
var expected_chunk_count := generator.grid_size.x * generator.grid_size.y
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
int(float(generator.grid_size.x) / 2.0),
|
||||
int(float(generator.grid_size.y) / 2.0),
|
||||
)
|
||||
var center_index := center.y * generator.grid_size.x + center.x
|
||||
assert(generator.grid_size == Vector2i(20, 20))
|
||||
|
|
@ -341,7 +347,7 @@ func _validate_generated_region(
|
|||
):
|
||||
var outlet_coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
int(float(index) / float(generator.grid_size.x)),
|
||||
)
|
||||
outlet_coordinates.append(outlet_coordinate)
|
||||
assert(
|
||||
|
|
@ -366,7 +372,7 @@ func _validate_generated_region(
|
|||
for index: int in placements.size():
|
||||
var coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
int(float(index) / float(generator.grid_size.x)),
|
||||
)
|
||||
if placements[index].begins_with("chunk_0001@"):
|
||||
assert(generator._distance_from_map_boundary(coordinate) <= 1)
|
||||
|
|
@ -587,8 +593,8 @@ func _validate_generated_region(
|
|||
Vector2i.ZERO,
|
||||
)
|
||||
var spawn_coordinate := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
int(float(generator.grid_size.x) / 2.0),
|
||||
int(float(generator.grid_size.y) / 2.0),
|
||||
)
|
||||
var spawn_distance := (
|
||||
absi(coordinate.x - spawn_coordinate.x)
|
||||
|
|
@ -1146,8 +1152,8 @@ func _validate_biome_catalog(
|
|||
for biome_id: StringName in EXPECTED_BIOME_IDS:
|
||||
assert(counts.get(biome_id, 0) > 0)
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
int(float(generator.grid_size.x) / 2.0),
|
||||
int(float(generator.grid_size.y) / 2.0),
|
||||
)
|
||||
assert(region.get_biome_at(center) == &"biome_plains")
|
||||
for child: Node in generator.get_generated_chunks_root().get_children():
|
||||
|
|
@ -1504,12 +1510,27 @@ func _has_material_variant_override(
|
|||
return false
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
var source_materials: Dictionary = mesh_instance.get_meta(
|
||||
FOLIAGE_WIND_SOURCE_MATERIALS_META,
|
||||
{},
|
||||
)
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var override := mesh_instance.get_surface_override_material(
|
||||
surface_index
|
||||
)
|
||||
if override != null and override in variants:
|
||||
return true
|
||||
var wind_material := override as ShaderMaterial
|
||||
if (
|
||||
wind_material != null
|
||||
and wind_material.shader == FOLIAGE_WIND_SHADER
|
||||
):
|
||||
var source_material: Material = source_materials.get(
|
||||
surface_index,
|
||||
null,
|
||||
) as Material
|
||||
if source_material != null and source_material in variants:
|
||||
return true
|
||||
for child: Node in root_node.get_children():
|
||||
if _has_material_variant_override(child, variants):
|
||||
return true
|
||||
|
|
@ -1647,7 +1668,7 @@ func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray:
|
|||
return result
|
||||
|
||||
|
||||
func _validate_projected_terrain_materials(root: Node) -> void:
|
||||
func _validate_projected_terrain_materials(projected_root: Node) -> void:
|
||||
var expected_sizes := {
|
||||
"grass_lite": 1.75,
|
||||
"sand": 2.6,
|
||||
|
|
@ -1658,17 +1679,21 @@ func _validate_projected_terrain_materials(root: Node) -> void:
|
|||
"sand": 0,
|
||||
"dirt": 0,
|
||||
}
|
||||
_validate_projected_material_node(root, expected_sizes, surface_counts)
|
||||
_validate_projected_material_node(
|
||||
projected_root,
|
||||
expected_sizes,
|
||||
surface_counts,
|
||||
)
|
||||
for material_name: String in expected_sizes:
|
||||
assert(surface_counts[material_name] > 0)
|
||||
|
||||
|
||||
func _validate_projected_material_node(
|
||||
root: Node,
|
||||
node: Node,
|
||||
expected_sizes: Dictionary,
|
||||
surface_counts: Dictionary,
|
||||
) -> void:
|
||||
var mesh_instance := root as MeshInstance3D
|
||||
var mesh_instance := node as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var material := mesh_instance.get_active_material(surface_index)
|
||||
|
|
@ -1689,7 +1714,7 @@ func _validate_projected_material_node(
|
|||
)
|
||||
)
|
||||
surface_counts[material.resource_name] += 1
|
||||
for child: Node in root.get_children():
|
||||
for child: Node in node.get_children():
|
||||
_validate_projected_material_node(
|
||||
child,
|
||||
expected_sizes,
|
||||
|
|
@ -1708,8 +1733,10 @@ func _validate_pond_collision(
|
|||
var vertices := arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array
|
||||
var normals := arrays[Mesh.ARRAY_NORMAL] as PackedVector3Array
|
||||
var indices := arrays[Mesh.ARRAY_INDEX] as PackedInt32Array
|
||||
var triangle_count := (
|
||||
indices.size() / 3 if not indices.is_empty() else vertices.size() / 3
|
||||
var triangle_count: int = (
|
||||
int(float(indices.size()) / 3.0)
|
||||
if not indices.is_empty()
|
||||
else int(float(vertices.size()) / 3.0)
|
||||
)
|
||||
for triangle_index: int in triangle_count:
|
||||
var offset := triangle_index * 3
|
||||
|
|
|
|||
|
|
@ -678,7 +678,7 @@ func _capture_inventory_pages(player_menu: PlayerMenu) -> void:
|
|||
player_menu.call("_show_section_immediate", sections[index])
|
||||
await process_frame
|
||||
await process_frame
|
||||
await _save_capture(suffixes[index])
|
||||
_save_capture(suffixes[index])
|
||||
player_menu.call("_show_section_immediate", PlayerMenu.Section.BAG)
|
||||
var sale_confirmation := player_menu.get_node("%SaleConfirmation") as Control
|
||||
var confirmation_message := player_menu.get_node(
|
||||
|
|
@ -693,7 +693,7 @@ func _capture_inventory_pages(player_menu: PlayerMenu) -> void:
|
|||
sale_confirmation.visible = true
|
||||
await process_frame
|
||||
await process_frame
|
||||
await _save_capture("sale-confirmation")
|
||||
_save_capture("sale-confirmation")
|
||||
sale_confirmation.visible = false
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,16 +38,18 @@ func _run() -> void:
|
|||
KeyboardMouseMappingManagerType.ROLE_INTERACT
|
||||
) == "e"
|
||||
)
|
||||
assert(
|
||||
var zoom_in_label: String = str(
|
||||
KeyboardMouseMappingManagerType.ROLE_LABELS[
|
||||
KeyboardMouseMappingManagerType.ROLE_CAMERA_ZOOM_IN
|
||||
] == "hotbar select (+SHIFT to zoom)"
|
||||
]
|
||||
)
|
||||
assert(
|
||||
var zoom_out_label: String = str(
|
||||
KeyboardMouseMappingManagerType.ROLE_LABELS[
|
||||
KeyboardMouseMappingManagerType.ROLE_CAMERA_ZOOM_OUT
|
||||
] == "hotbar select (+SHIFT to zoom)"
|
||||
]
|
||||
)
|
||||
assert(zoom_in_label == "hotbar select (+SHIFT to zoom)")
|
||||
assert(zoom_out_label == "hotbar select (+SHIFT to zoom)")
|
||||
assert(
|
||||
str(defaults[
|
||||
str(KeyboardMouseMappingManagerType.ROLE_PRIMARY_ACTION)
|
||||
|
|
|
|||
|
|
@ -293,25 +293,25 @@ func _wait_for_avatar_sitting(
|
|||
var deadline: int = Time.get_ticks_msec() + 10000
|
||||
while Time.get_ticks_msec() < deadline:
|
||||
await process_frame
|
||||
var avatar: Player = spawn_service.get_avatar(peer_id)
|
||||
if avatar != null and avatar.is_sitting() == expected:
|
||||
var observed_avatar: Player = spawn_service.get_avatar(peer_id)
|
||||
if observed_avatar != null and observed_avatar.is_sitting() == expected:
|
||||
return true
|
||||
var avatar: Player = spawn_service.get_avatar(peer_id)
|
||||
if avatar != null:
|
||||
var timed_out_avatar: Player = spawn_service.get_avatar(peer_id)
|
||||
if timed_out_avatar != null:
|
||||
print(
|
||||
"sitting wait timed out: ",
|
||||
{
|
||||
"peer_id": peer_id,
|
||||
"expected": expected,
|
||||
"sitting": avatar.is_sitting(),
|
||||
"sit_after_landing": bool(avatar.get("_sit_after_landing")),
|
||||
"on_floor": avatar.is_on_floor(),
|
||||
"position": avatar.global_position,
|
||||
"velocity": avatar.velocity,
|
||||
"authoritative": bool(avatar.get(
|
||||
"sitting": timed_out_avatar.is_sitting(),
|
||||
"sit_after_landing": bool(timed_out_avatar.get("_sit_after_landing")),
|
||||
"on_floor": timed_out_avatar.is_on_floor(),
|
||||
"position": timed_out_avatar.global_position,
|
||||
"velocity": timed_out_avatar.velocity,
|
||||
"authoritative": bool(timed_out_avatar.get(
|
||||
"_network_authoritative_simulation"
|
||||
)),
|
||||
"last_input": int(avatar.get(
|
||||
"last_input": int(timed_out_avatar.get(
|
||||
"_last_network_input_sequence"
|
||||
)),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -217,11 +217,11 @@ func _validate_new_game_music_transition(main: Node) -> void:
|
|||
|
||||
|
||||
func _first_fresh_water_visual(main: Node) -> MeshInstance3D:
|
||||
var root := main.get_node(
|
||||
var fresh_water_root := main.get_node(
|
||||
"TestWorld/Regions/GeneratedWorldRegion/WaterBodies/FreshWaterBodies"
|
||||
) as Node3D
|
||||
assert(root != null and root.get_child_count() > 0)
|
||||
return root.get_child(0).get_node("VisualWater") as MeshInstance3D
|
||||
assert(fresh_water_root != null and fresh_water_root.get_child_count() > 0)
|
||||
return fresh_water_root.get_child(0).get_node("VisualWater") as MeshInstance3D
|
||||
|
||||
|
||||
func _validate_ocean_fishing_coverage(
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ func _validate_page() -> void:
|
|||
== "flathead\ncatfish"
|
||||
)
|
||||
|
||||
var shared_material: Material
|
||||
var shared_material: Material = null
|
||||
var silhouette_count: int = 0
|
||||
var target_silhouette_area: float = (
|
||||
LogbookPage.CATALOG_PORTRAIT_SIZE.x
|
||||
|
|
@ -558,9 +558,9 @@ func _validate_page() -> void:
|
|||
]:
|
||||
page.call("_select_category", category)
|
||||
await create_timer(0.25).timeout
|
||||
var entries: Dictionary = page.get("_entry_buttons")
|
||||
var category_entries: Dictionary = page.get("_entry_buttons")
|
||||
assert(
|
||||
entries.size()
|
||||
category_entries.size()
|
||||
== (108 if category == LogbookCatalog.Category.FRESH_WATER else 202)
|
||||
)
|
||||
for fish: FishDataType in LogbookCatalog.ordered_species(
|
||||
|
|
@ -571,7 +571,7 @@ func _validate_page() -> void:
|
|||
var unknown_key := StringName(
|
||||
"unknown_%d" % LogbookCatalog.catalog_number(fish)
|
||||
)
|
||||
var entry := entries.get(unknown_key) as Button
|
||||
var entry := category_entries.get(unknown_key) as Button
|
||||
assert(entry != null)
|
||||
assert(entry.text.is_empty())
|
||||
assert(entry.tooltip_text.is_empty())
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ func _validate_latency_smoothing() -> void:
|
|||
avatar.set_physics_process(false)
|
||||
_validate_compact_input_encoding()
|
||||
_validate_compact_snapshot_encoding()
|
||||
_validate_authoritative_teleport_snapshot_guard()
|
||||
_validate_compact_animation_encoding()
|
||||
_validate_animation_action_ordering(avatar)
|
||||
_validate_transit_estimation()
|
||||
|
|
@ -72,8 +73,12 @@ func _validate_compact_snapshot_encoding() -> void:
|
|||
Vector3(4.5, 0.0, 0.0),
|
||||
1,
|
||||
)
|
||||
assert(NetworkSession.MOVEMENT_SNAPSHOT_BATCH_SIZE == 8)
|
||||
assert(NetworkSession.OWNER_SNAPSHOT_DIVISOR == 3)
|
||||
var movement_snapshot_batch_size: int = (
|
||||
NetworkSession.MOVEMENT_SNAPSHOT_BATCH_SIZE
|
||||
)
|
||||
var owner_snapshot_divisor: int = NetworkSession.OWNER_SNAPSHOT_DIVISOR
|
||||
assert(movement_snapshot_batch_size == 8)
|
||||
assert(owner_snapshot_divisor == 3)
|
||||
var encoded_snapshots: Array = []
|
||||
for _peer: int in NetworkSession.MOVEMENT_SNAPSHOT_BATCH_SIZE:
|
||||
encoded_snapshots.append(
|
||||
|
|
@ -100,6 +105,50 @@ func _validate_compact_snapshot_encoding() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _validate_authoritative_teleport_snapshot_guard() -> void:
|
||||
var session := NetworkSession.new()
|
||||
var destination := Vector3(24.0, 0.0, -8.0)
|
||||
session.set("_local_authoritative_teleport_guard_active", true)
|
||||
session.set("_local_authoritative_teleport_guard_position", destination)
|
||||
session.set("_local_authoritative_teleport_minimum_ack", 48)
|
||||
|
||||
# A channel-two audit from before the reliable relocation must not move the
|
||||
# local player back to its old position.
|
||||
assert(bool(session.call(
|
||||
"_reject_stale_authoritative_teleport_snapshot",
|
||||
_network_snapshot(Vector3.ZERO, Vector3.ZERO, 48),
|
||||
)))
|
||||
assert(bool(session.get("_local_authoritative_teleport_guard_active")))
|
||||
assert(bool(session.call(
|
||||
"_reject_stale_authoritative_teleport_snapshot",
|
||||
_network_snapshot(destination, Vector3.ZERO, 47),
|
||||
)))
|
||||
|
||||
# A matching audit from the new location clears the barrier. A higher
|
||||
# acknowledgement at the old position is not sufficient: movement input and
|
||||
# the reliable teleport travelled on different channels, so it can still be
|
||||
# a held packet from before recovery.
|
||||
assert(not bool(session.call(
|
||||
"_reject_stale_authoritative_teleport_snapshot",
|
||||
_network_snapshot(destination + Vector3.RIGHT, Vector3.ZERO, 48),
|
||||
)))
|
||||
assert(not bool(session.get("_local_authoritative_teleport_guard_active")))
|
||||
session.set("_local_authoritative_teleport_guard_active", true)
|
||||
session.set("_local_authoritative_teleport_guard_position", destination)
|
||||
session.set("_local_authoritative_teleport_minimum_ack", 48)
|
||||
assert(bool(session.call(
|
||||
"_reject_stale_authoritative_teleport_snapshot",
|
||||
_network_snapshot(Vector3.ZERO, Vector3.ZERO, 49),
|
||||
)))
|
||||
assert(bool(session.get("_local_authoritative_teleport_guard_active")))
|
||||
assert(not bool(session.call(
|
||||
"_reject_stale_authoritative_teleport_snapshot",
|
||||
_network_snapshot(destination, Vector3.ZERO, 49),
|
||||
)))
|
||||
assert(not bool(session.get("_local_authoritative_teleport_guard_active")))
|
||||
session.free()
|
||||
|
||||
|
||||
func _validate_compact_animation_encoding() -> void:
|
||||
var paused_state := NetworkPlayerAnimationProtocol.make_state(
|
||||
NetworkPlayerAnimationProtocol.LOCOMOTION_IDLE,
|
||||
|
|
@ -315,16 +364,16 @@ func _validate_remote_snapshot_smoothing(avatar: Player) -> void:
|
|||
|
||||
# Reconciliation only applies after both simulations report a grounded body.
|
||||
# Give the unit avatar a real floor so this exercises the production path.
|
||||
var floor := StaticBody3D.new()
|
||||
floor.collision_layer = 1
|
||||
floor.collision_mask = 0
|
||||
floor.position = Vector3(0.0, -0.1, 0.0)
|
||||
var floor_body := StaticBody3D.new()
|
||||
floor_body.collision_layer = 1
|
||||
floor_body.collision_mask = 0
|
||||
floor_body.position = Vector3(0.0, -0.1, 0.0)
|
||||
var floor_shape := CollisionShape3D.new()
|
||||
var floor_box := BoxShape3D.new()
|
||||
floor_box.size = Vector3(100.0, 0.2, 100.0)
|
||||
floor_shape.shape = floor_box
|
||||
floor.add_child(floor_shape)
|
||||
root.add_child(floor)
|
||||
floor_body.add_child(floor_shape)
|
||||
root.add_child(floor_body)
|
||||
await physics_frame
|
||||
avatar.set_local_control(true)
|
||||
avatar.global_position = Vector3(0.8, 0.0, 0.0)
|
||||
|
|
@ -434,6 +483,24 @@ func _validate_remote_snapshot_smoothing(avatar: Player) -> void:
|
|||
int(avatar.get("_local_prediction_hard_corrections")) == 1
|
||||
)
|
||||
|
||||
# Recovery owns all local state until its reliable teleport arrives. A
|
||||
# delayed audit must neither pull the body back nor acknowledge an old jump.
|
||||
avatar.global_position = Vector3(10.0, 0.0, 0.0)
|
||||
avatar.call("_queue_local_network_jump_intent")
|
||||
avatar.capture_network_input(20)
|
||||
avatar.set_water_recovery_active(true)
|
||||
avatar.apply_local_prediction_correction(
|
||||
drift_snapshot,
|
||||
20,
|
||||
1.0 / 30.0,
|
||||
0.0,
|
||||
0.1,
|
||||
)
|
||||
assert(avatar.global_position == Vector3(10.0, 0.0, 0.0))
|
||||
assert(bool(avatar.get("_local_network_jump_intent_pending")))
|
||||
avatar.set_water_recovery_active(false)
|
||||
avatar.call("_clear_local_network_jump_intent")
|
||||
|
||||
# Soft reconciliation must respect the same terrain collision as ordinary
|
||||
# player movement instead of phasing the capsule through a solid obstacle.
|
||||
var blocker := StaticBody3D.new()
|
||||
|
|
@ -470,7 +537,7 @@ func _validate_remote_snapshot_smoothing(avatar: Player) -> void:
|
|||
== blocked_camera_position
|
||||
)
|
||||
blocker.queue_free()
|
||||
floor.queue_free()
|
||||
floor_body.queue_free()
|
||||
await physics_frame
|
||||
|
||||
|
||||
|
|
@ -567,6 +634,40 @@ func _validate_stale_input_expiry(avatar: Player) -> void:
|
|||
assert(not bool(avatar.get("_network_input_stale")))
|
||||
assert((avatar.get("_network_axis") as Vector2).length_squared() > 0.0)
|
||||
|
||||
# The reliable recovery request carries the last client-created sequence.
|
||||
# A host uses it as a hard watermark, so every delayed input from before the
|
||||
# request is discarded even if it arrives after the teleport RPC.
|
||||
avatar.neutralize_authoritative_input_for_relocation(44)
|
||||
assert(int(avatar.get("_last_network_input_sequence")) == 44)
|
||||
assert((avatar.get("_network_axis") as Vector2) == Vector2.ZERO)
|
||||
assert(not bool(avatar.get("_network_sprint")))
|
||||
assert(bool(avatar.get("_authoritative_relocation_input_quarantine")))
|
||||
avatar.apply_authoritative_network_input(_movement_input(41, true))
|
||||
assert((avatar.get("_network_axis") as Vector2) == Vector2.ZERO)
|
||||
avatar.apply_authoritative_network_input(_movement_input(42, false, false))
|
||||
assert(int(avatar.get("_last_network_input_sequence")) == 44)
|
||||
|
||||
# A non-neutral post-watermark packet cannot restart motion. The first
|
||||
# neutral packet past the watermark releases the barrier; the following
|
||||
# input is the first one allowed to move the recovered avatar.
|
||||
avatar.apply_authoritative_network_input(_movement_input(45, false, true))
|
||||
assert((avatar.get("_network_axis") as Vector2) == Vector2.ZERO)
|
||||
assert(bool(avatar.get("_authoritative_relocation_input_quarantine")))
|
||||
avatar.apply_authoritative_network_input(_movement_input(46, false, false))
|
||||
assert(not bool(avatar.get("_authoritative_relocation_input_quarantine")))
|
||||
avatar.apply_authoritative_network_input(_movement_input(47, false, true))
|
||||
assert((avatar.get("_network_axis") as Vector2).length_squared() > 0.0)
|
||||
|
||||
# Recovery itself also consumes incoming movement rather than remembering a
|
||||
# held axis which could resume on the exact frame recovery presentation ends.
|
||||
avatar.set_water_recovery_active(true)
|
||||
avatar.apply_authoritative_network_input(_movement_input(48, false, true))
|
||||
assert((avatar.get("_network_axis") as Vector2) == Vector2.ZERO)
|
||||
avatar.set_water_recovery_active(false)
|
||||
avatar.apply_authoritative_network_input(_movement_input(42, false, false))
|
||||
avatar.apply_authoritative_network_input(_movement_input(49, false, true))
|
||||
assert((avatar.get("_network_axis") as Vector2).length_squared() > 0.0)
|
||||
|
||||
|
||||
func _network_snapshot(
|
||||
position: Vector3,
|
||||
|
|
|
|||
|
|
@ -307,13 +307,13 @@ func _validate_native_keyboard() -> void:
|
|||
await process_frame
|
||||
edit.grab_focus()
|
||||
assert(keyboard.is_open())
|
||||
var focus_exit_count: int = 0
|
||||
var focus_exit_state: Dictionary = {"count": 0}
|
||||
edit.focus_exited.connect(func() -> void:
|
||||
focus_exit_count += 1
|
||||
focus_exit_state["count"] = int(focus_exit_state["count"]) + 1
|
||||
)
|
||||
assert(keyboard.request_for_control(edit))
|
||||
assert(edit.has_focus())
|
||||
assert(focus_exit_count == 0)
|
||||
assert(int(focus_exit_state["count"]) == 0)
|
||||
assert(not edit.virtual_keyboard_enabled)
|
||||
assert(not edit.virtual_keyboard_show_on_focus)
|
||||
assert(keyboard.shown_controls == [edit])
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func _run() -> void:
|
|||
assert(not vertices.is_empty())
|
||||
assert(vertices.size() % 2 == 0)
|
||||
assert(indices.size() == vertices.size() * 3)
|
||||
var point_count := vertices.size() / 2
|
||||
var point_count: int = int(float(vertices.size()) / 2.0)
|
||||
var land_edge := PackedVector2Array()
|
||||
var water_edge := PackedVector2Array()
|
||||
for index: int in point_count:
|
||||
|
|
|
|||
|
|
@ -40,13 +40,16 @@ func _validate_palette() -> void:
|
|||
|
||||
|
||||
func _validate_protocol_bounds() -> void:
|
||||
assert(SurfaceDrawingProtocol.GRID_WIDTH == 16)
|
||||
assert(SurfaceDrawingProtocol.GRID_HEIGHT == 16)
|
||||
assert(SurfaceDrawingProtocol.GRID_SIZES == [16, 32, 64, 128])
|
||||
assert(is_equal_approx(SurfaceDrawingProtocol.CELL_SIZE, 0.075))
|
||||
var grid_width: int = SurfaceDrawingProtocol.GRID_WIDTH
|
||||
var grid_height: int = SurfaceDrawingProtocol.GRID_HEIGHT
|
||||
var grid_sizes: Variant = SurfaceDrawingProtocol.GRID_SIZES
|
||||
var cell_size: float = SurfaceDrawingProtocol.CELL_SIZE
|
||||
assert(grid_width == 16)
|
||||
assert(grid_height == 16)
|
||||
assert(grid_sizes == [16, 32, 64, 128])
|
||||
assert(is_equal_approx(cell_size, 0.075))
|
||||
assert(is_equal_approx(
|
||||
float(SurfaceDrawingProtocol.GRID_WIDTH)
|
||||
* SurfaceDrawingProtocol.CELL_SIZE,
|
||||
float(grid_width) * cell_size,
|
||||
1.2,
|
||||
))
|
||||
var canvas_request: Dictionary = {
|
||||
|
|
@ -157,14 +160,14 @@ func _validate_grid_snapping() -> void:
|
|||
"creator_fingerprint": FINGERPRINT_A,
|
||||
"cells": [],
|
||||
}
|
||||
var snapped: Dictionary = SurfaceDrawingPlacement.resolve(
|
||||
var snap_result: Dictionary = SurfaceDrawingPlacement.resolve(
|
||||
Vector3(1.08, 0.02, 0.08),
|
||||
Vector3.UP,
|
||||
Vector3.RIGHT,
|
||||
[anchor],
|
||||
)
|
||||
assert(bool(snapped["snapped"]))
|
||||
var snapped_origin: Vector3 = snapped["origin"]
|
||||
assert(bool(snap_result["snapped"]))
|
||||
var snapped_origin: Vector3 = snap_result["origin"]
|
||||
assert(snapped_origin.is_equal_approx(Vector3(1.2, 0.0, 0.0)))
|
||||
var unsnapped: Dictionary = SurfaceDrawingPlacement.resolve(
|
||||
Vector3(0.6, 0.0, 0.0),
|
||||
|
|
|
|||
|
|
@ -1878,7 +1878,7 @@ func _all_neighbor_edges_match(generator: TerrainChunkGenerator) -> bool:
|
|||
for index: int in generator._placements.size():
|
||||
var coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
int(float(index) / float(generator.grid_size.x)),
|
||||
)
|
||||
var current: TerrainChunkVariant = generator._placements[index]
|
||||
if coordinate.x > 0:
|
||||
|
|
@ -1918,8 +1918,8 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
|
|||
"All walkable generated terrain must remain connected to spawn.",
|
||||
)
|
||||
var center := Vector2i(
|
||||
generator.grid_size.x / 2,
|
||||
generator.grid_size.y / 2,
|
||||
int(float(generator.grid_size.x) / 2.0),
|
||||
int(float(generator.grid_size.y) / 2.0),
|
||||
)
|
||||
var safe_spawn_neighbors := 0
|
||||
for edge_value: int in TerrainChunkTopology.Edge.values():
|
||||
|
|
@ -1943,7 +1943,7 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
|
|||
var placement := generator._placements[index]
|
||||
var coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
int(float(index) / float(generator.grid_size.x)),
|
||||
)
|
||||
if "coast" in placement.definition.tags:
|
||||
_check(
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ func _initialize() -> void:
|
|||
|
||||
|
||||
func _run() -> void:
|
||||
var root := Node.new()
|
||||
get_root().add_child(root)
|
||||
var scene_root := Node.new()
|
||||
get_root().add_child(scene_root)
|
||||
var bag := PlayerBag.new()
|
||||
var catches := FishInventory.new()
|
||||
var capacity := PlayerCoolerCapacity.new()
|
||||
var layout := PlayerInventoryLayout.new()
|
||||
var hotbar := PlayerHotbar.new()
|
||||
for node: Node in [bag, catches, capacity, layout, hotbar]:
|
||||
root.add_child(node)
|
||||
scene_root.add_child(node)
|
||||
bag.setup(ItemCatalogResource)
|
||||
layout.setup(bag, catches, ItemCatalogResource, capacity)
|
||||
bag.set_inventory_layout(layout)
|
||||
|
|
@ -109,7 +109,7 @@ func _run() -> void:
|
|||
assert(layout.get_storage_count() == 1)
|
||||
assert(layout.get_storage_capacity() == 9)
|
||||
var inventory_grid := GeneralInventoryGrid.new()
|
||||
root.add_child(inventory_grid)
|
||||
scene_root.add_child(inventory_grid)
|
||||
inventory_grid.set_slot_presentation(Vector2(78.0, 78.0), 10)
|
||||
inventory_grid.setup(
|
||||
layout,
|
||||
|
|
@ -143,7 +143,7 @@ func _run() -> void:
|
|||
)
|
||||
staged_slot.set_staged(false)
|
||||
var quality_slot := GeneralInventorySlot.new()
|
||||
root.add_child(quality_slot)
|
||||
scene_root.add_child(quality_slot)
|
||||
quality_slot.set_presentation_size(Vector2(78.0, 78.0))
|
||||
quality_slot.configure(
|
||||
0,
|
||||
|
|
@ -181,7 +181,7 @@ func _run() -> void:
|
|||
distinct_quality_colors[quality_color] = true
|
||||
assert(distinct_quality_colors.size() == FishQuality.TIER_COUNT)
|
||||
var sale_tray_slot := ShopSaleTraySlot.new()
|
||||
root.add_child(sale_tray_slot)
|
||||
scene_root.add_child(sale_tray_slot)
|
||||
sale_tray_slot.configure(
|
||||
"catch:%s" % fish_catch.catch_id,
|
||||
fish_catch.fish.display_texture,
|
||||
|
|
@ -207,7 +207,7 @@ func _run() -> void:
|
|||
expected_tray_quality_color.a = 0.96
|
||||
assert(sale_tray_style.bg_color == expected_tray_quality_color)
|
||||
var wallet := PlayerWallet.new()
|
||||
root.add_child(wallet)
|
||||
scene_root.add_child(wallet)
|
||||
assert(wallet.restore_balance(15000))
|
||||
assert(layout.get_next_backpack_cost() == 1500)
|
||||
assert(layout.purchase_backpack(wallet))
|
||||
|
|
@ -223,7 +223,7 @@ func _run() -> void:
|
|||
assert(inventory_grid.get_slots().size() == 36)
|
||||
assert(layout.get_next_backpack_cost() == -1)
|
||||
var storage_grid := GeneralInventoryGrid.new()
|
||||
root.add_child(storage_grid)
|
||||
scene_root.add_child(storage_grid)
|
||||
storage_grid.setup(
|
||||
layout,
|
||||
bag,
|
||||
|
|
@ -240,7 +240,7 @@ func _run() -> void:
|
|||
== "locked storage slot 10"
|
||||
)
|
||||
var storage_wallet := PlayerWallet.new()
|
||||
root.add_child(storage_wallet)
|
||||
scene_root.add_child(storage_wallet)
|
||||
assert(storage_wallet.restore_balance(9500))
|
||||
for expected_capacity: int in [18, 27, 36, 45, 54, 63, 72]:
|
||||
assert(capacity.purchase(storage_wallet))
|
||||
|
|
@ -266,8 +266,8 @@ func _run() -> void:
|
|||
|
||||
var network_sale := NetworkSaleService.new()
|
||||
var session := NetworkSession.new()
|
||||
root.add_child(session)
|
||||
root.add_child(network_sale)
|
||||
scene_root.add_child(session)
|
||||
scene_root.add_child(network_sale)
|
||||
network_sale.set("_session", session)
|
||||
network_sale.set("_item_catalog", ItemCatalogResource)
|
||||
network_sale.set("_fish_catalog", FishCatalogResource)
|
||||
|
|
@ -284,5 +284,5 @@ func _run() -> void:
|
|||
assert((result.get("items", []) as Array).size() == 1)
|
||||
|
||||
print("Unified inventory validation: PASS")
|
||||
root.free()
|
||||
scene_root.free()
|
||||
quit()
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ func _validate_setup_page() -> void:
|
|||
page.call("_set_world_layout", WorldLayout.STARTER_ISLAND)
|
||||
assert(not (page.get_node("%SeedSection") as Control).visible)
|
||||
var request: Dictionary = {}
|
||||
page.start_requested.connect(func(layout: StringName, seed: int) -> void:
|
||||
page.start_requested.connect(func(layout: StringName, world_seed: int) -> void:
|
||||
request["layout"] = layout
|
||||
request["seed"] = seed
|
||||
request["seed"] = world_seed
|
||||
)
|
||||
page.call("_request_start")
|
||||
assert(request.get("layout") == WorldLayout.STARTER_ISLAND)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,21 @@ func _initialize() -> void:
|
|||
|
||||
|
||||
func _run() -> void:
|
||||
assert(NetworkProtocol.PROTOCOL_VERSION == 12)
|
||||
assert(
|
||||
var protocol_version: int = NetworkProtocol.PROTOCOL_VERSION
|
||||
var world_spawn_capability: String = (
|
||||
NetworkWorldSpawnProtocol.CAPABILITY
|
||||
== NetworkProtocol.WORLD_SPAWN_CAPABILITY
|
||||
)
|
||||
assert(NetworkWorldSpawnProtocol.SNAPSHOT_ENTITIES_PER_ENVELOPE <= 4)
|
||||
var protocol_world_spawn_capability: String = (
|
||||
NetworkProtocol.WORLD_SPAWN_CAPABILITY
|
||||
)
|
||||
var snapshot_entities_per_envelope: int = (
|
||||
NetworkWorldSpawnProtocol.SNAPSHOT_ENTITIES_PER_ENVELOPE
|
||||
)
|
||||
assert(protocol_version == 12)
|
||||
assert(
|
||||
world_spawn_capability == protocol_world_spawn_capability
|
||||
)
|
||||
assert(snapshot_entities_per_envelope <= 4)
|
||||
_validate_catalog_statuses()
|
||||
await _validate_billboard_presentation()
|
||||
_validate_envelopes()
|
||||
|
|
|
|||
|
|
@ -42,9 +42,15 @@ func _run() -> void:
|
|||
|
||||
|
||||
func _validate_clock_boundaries_and_duration() -> void:
|
||||
assert(WorldTimeServiceType.REAL_SECONDS_PER_CYCLE == 86400.0)
|
||||
var real_seconds_per_cycle: float = (
|
||||
WorldTimeServiceType.REAL_SECONDS_PER_CYCLE
|
||||
)
|
||||
var hours_per_real_second: float = (
|
||||
WorldTimeServiceType.HOURS_PER_REAL_SECOND
|
||||
)
|
||||
assert(real_seconds_per_cycle == 86400.0)
|
||||
assert(is_equal_approx(
|
||||
WorldTimeServiceType.HOURS_PER_REAL_SECOND,
|
||||
hours_per_real_second,
|
||||
1.0 / 3600.0,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
|
|
|
|||
|
|
@ -382,11 +382,13 @@ func _validate_weather_presentation() -> void:
|
|||
"res://world/environment/local_storm_cloud.gdshader"
|
||||
)
|
||||
assert("cull_disabled" in cloud_shader.code)
|
||||
assert(LocalStormCloudLayer.WRAP_RADIUS >= 315.0)
|
||||
assert(is_equal_approx(LocalStormCloudLayer.MAXIMUM_OPACITY, 1.0))
|
||||
var cloud_wrap_radius: float = LocalStormCloudLayer.WRAP_RADIUS
|
||||
var cloud_maximum_opacity: float = LocalStormCloudLayer.MAXIMUM_OPACITY
|
||||
var cloud_distance_fade_end: float = LocalStormCloudLayer.DISTANCE_FADE_END
|
||||
assert(cloud_wrap_radius >= 315.0)
|
||||
assert(is_equal_approx(cloud_maximum_opacity, 1.0))
|
||||
assert(
|
||||
LocalStormCloudLayer.DISTANCE_FADE_END
|
||||
< LocalStormCloudLayer.WRAP_RADIUS
|
||||
cloud_distance_fade_end < cloud_wrap_radius
|
||||
)
|
||||
assert(is_zero_approx(float(storm_clouds.call("get_storm_amount"))))
|
||||
visuals.apply_weather_immediately(
|
||||
|
|
@ -524,10 +526,18 @@ func _validate_weather_presentation() -> void:
|
|||
assert(rain.emitting)
|
||||
assert(rain.amount_ratio > 0.99)
|
||||
assert(rain.amount == WorldTimeVisualControllerType.RAIN_PARTICLE_AMOUNT)
|
||||
var rain_emitter_offset: Vector3 = (
|
||||
WorldTimeVisualControllerType.RAIN_EMITTER_OFFSET
|
||||
)
|
||||
var rain_supported_canopy_height: float = (
|
||||
WorldTimeVisualControllerType.RAIN_SUPPORTED_CANOPY_HEIGHT
|
||||
)
|
||||
var rain_canopy_clearance: float = (
|
||||
WorldTimeVisualControllerType.RAIN_CANOPY_CLEARANCE
|
||||
)
|
||||
assert(
|
||||
WorldTimeVisualControllerType.RAIN_EMITTER_OFFSET.y
|
||||
>= WorldTimeVisualControllerType.RAIN_SUPPORTED_CANOPY_HEIGHT
|
||||
+ WorldTimeVisualControllerType.RAIN_CANOPY_CLEARANCE
|
||||
rain_emitter_offset.y
|
||||
>= rain_supported_canopy_height + rain_canopy_clearance
|
||||
)
|
||||
assert(is_equal_approx(
|
||||
rain.lifetime,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue