extends SceneTree const VerticalSliceRoot = preload( "res://game/composition/vertical_slice/vertical_slice_root.gd" ) const MainMenuScreen = preload("res://game/presentation/main_menu/main_menu_screen.gd") const CockpitHud = preload("res://game/presentation/cockpit/cockpit_hud.gd") const CockpitDetailing = preload( "res://game/presentation/cockpit/cockpit_detailing.gd" ) const CockpitPressureBulkhead = preload( "res://game/presentation/cockpit/cockpit_pressure_bulkhead.gd" ) const PauseMenu = preload("res://game/presentation/pause/pause_menu.gd") const ControlsSettingsScreen = preload( "res://game/presentation/settings/controls_settings_screen.gd" ) const ProductionInputAdapter = preload( "res://game/infrastructure/input/production_input_adapter.gd" ) const TEST_COUNT := 78 var _failures: Array[String] = [] func _init() -> void: _run.call_deferred() func _run() -> void: change_scene_to_file( "res://game/composition/vertical_slice/vertical_slice_root.tscn" ) await process_frame await physics_frame var composition := current_scene as VerticalSliceRoot _expect(composition != null, "production composition loads") if composition != null: await _test_vehicle_slice(composition) _expect( int(ProjectSettings.get_setting("physics/common/physics_ticks_per_second")) == 60, "project physics runs at 60 Hz" ) var project_features: PackedStringArray = ProjectSettings.get_setting( "application/config/features" ) _expect("Forward Plus" in project_features, "production project uses Forward+") var exit_code := 0 if _failures.is_empty(): print("Vertical slice foundation integration: %d passed, 0 failed." % TEST_COUNT) else: for failure: String in _failures: push_error(failure) print("Vertical slice foundation integration: %d failed." % _failures.size()) exit_code = 1 Input.action_release(ProductionInputAdapter.ACTION_FORWARD) Input.action_release(ProductionInputAdapter.ACTION_RIGHT) if current_scene != null: current_scene.queue_free() await process_frame await process_frame quit(exit_code) func _test_vehicle_slice(composition: VerticalSliceRoot) -> void: var menu := composition.active_screen() as MainMenuScreen _expect(menu != null, "composition starts on main menu") if menu == null: return var continue_button := menu.get_node("%ContinueButton") as Button _expect(continue_button.disabled, "continue is disabled without save") var new_game_button := menu.get_node("%NewGameButton") as Button new_game_button.pressed.emit() await process_frame await physics_frame var actor = composition.submarine_actor() var camera = composition.camera_controller() var instruments = composition.cockpit_instrument_panel() var detailing: CockpitDetailing = ( instruments.detailing() if instruments != null else null ) var pressure_bulkhead := ( instruments.get_node_or_null("%CurvedPressureBulkhead") as CockpitPressureBulkhead if instruments != null else null ) var analog_gauges := ( instruments.get_node_or_null("Dashboard/AnalogGauges") if instruments != null else null ) var adapter := composition.input_adapter() as ProductionInputAdapter var hud := composition.active_screen() as CockpitHud var sonar_service = composition.sonar_service() var sonar_display = composition.sonar_display() _expect(composition.active_world() != null, "vehicle test world is assembled") _expect(actor != null, "production submarine actor is assembled") _expect(camera != null, "cockpit camera is attached to the submarine") _expect(instruments != null, "physical cockpit instruments are assembled") _expect(detailing != null, "cockpit structural detailing is assembled") _expect(pressure_bulkhead != null, "curved pressure bulkhead is assembled") _expect(analog_gauges != null, "dashboard analog instruments are assembled") _expect(hud != null, "critical HUD overlay is assembled") _expect(sonar_service != null, "production sonar service is assembled") _expect(sonar_display != null, "shared sonar presentation is assembled") _expect(composition.latest_update() != null, "dive session starts with a typed update") if ( actor == null or camera == null or instruments == null or detailing == null or pressure_bulkhead == null or analog_gauges == null or adapter == null or hud == null ): return _expect(instruments.get_node("%WindowFrame") is MeshInstance3D, "cockpit has a physical porthole frame") _expect(instruments.get_node("%Dashboard") is Node3D, "cockpit has a physical dashboard") _expect(instruments.get_node("%SonarScreen") is MeshInstance3D, "cockpit reserves a physical sonar display") var sonar_monitor := instruments.get_node("SonarMonitor") as Node3D var sonar_screen := instruments.get_node("%SonarScreen") as MeshInstance3D var sonar_housing := instruments.get_node("SonarMonitor/Housing") as MeshInstance3D var cockpit_camera := camera.get_node("Camera") as Camera3D var electrical_bank := detailing.get_node("SideEquipment/ElectricalBank") as Node3D _expect(sonar_monitor.position.x > 0.5, "cockpit sonar is mounted on the right side") _expect( _sonar_screen_faces_camera(sonar_screen, cockpit_camera), "right-side sonar display faces the pilot" ) _expect( _sonar_screen_is_ahead_of_bank(sonar_screen, electrical_bank), "right-side electrical equipment does not hide the sonar display" ) _expect( _sonar_screen_clears_housing(sonar_screen, sonar_housing), "sonar texture clears its housing without depth overlap" ) _expect( instruments.find_children("SonarReferenceTrace", "MeshInstance3D", true).is_empty(), "cockpit sonar does not invent a decorative profile" ) _expect(detailing.pressure_hull_rib_count() >= 6, "cockpit exposes reinforced pressure-hull structure") _expect(detailing.pipe_segment_count() >= 13, "cockpit exposes a complete life-support pipe run") _expect(absf(detailing.battery_needle_degrees()) > 100.0, "analog battery gauge follows the dive snapshot") _expect(detailing.textures_are_loaded(), "cockpit loads its dedicated analog gauge textures") _expect(detailing.gauges_are_dashboard_mounted(), "analog gauges are mounted on the dashboard") _expect(detailing.dashboard_control_count() >= 8, "dashboard exposes physical auxiliary controls") _expect(detailing.side_equipment_count() >= 5, "cockpit sidewalls expose tanks and junction boxes") _expect(detailing.side_control_count() >= 9, "cockpit sidewalls expose valves and pilot controls") _expect(detailing.sonar_control_count() >= 4, "sonar console exposes physical lamps and gain control") _expect(detailing.practical_light_count() == 2, "cockpit uses two caged practical lights") _expect(pressure_bulkhead.panel_count() == 1, "porthole bulkhead uses one continuous curved shell") _expect(pressure_bulkhead.rectangular_whitebox_is_hidden(), "porthole rectangular whitebox is hidden") _expect(analog_gauges.gauge_count() == 4, "dashboard exposes four primary analog instruments") _expect(analog_gauges.indicator_count() == 3, "instrument binnacle exposes three inset pilot lights") _expect(analog_gauges.active_indicator_count() == 1, "exactly one pilot light reflects the alert state") _expect("kWh" in instruments.resources_text() and "kW" in instruments.resources_text(), "battery instrument has exact units") _expect("L/min" in instruments.resources_text(), "oxygen instrument has volume and consumption") _expect("bar" in instruments.integrity_text(), "hull instrument has current and certified pressure") _expect(not hud.alert_overlay_visible(), "normal telemetry stays on physical instruments only") _expect( instruments.sonar_texture() == sonar_display.sonar_texture(), "physical and expanded sonar views share one texture" ) _expect(sonar_display.idle_prompt_visible(), "idle sonar explains how to emit a scan") adapter.sonar_scan_requested.emit() var first_scan = sonar_service.current_scan() _expect(first_scan != null, "manual sonar command publishes a typed scan") _expect(first_scan.samples.observed_count() > 1000, "reference acquisition captures dense bathymetry") _expect(first_scan.contacts.size() >= 1, "metal calibration target becomes a distinct contact") _expect("RELEVÉ EN COURS" in sonar_display.status_text(), "sonar display reports progressive reveal") _expect(not sonar_display.idle_prompt_visible(), "sonar data replaces the idle instruction") for frame in 18: await physics_frame _expect(sonar_display.built_row_count() > 0, "renderer reveals near rows incrementally") _expect(sonar_display.built_triangle_count() >= 90, "sonar renders a dense relief surface, not a line") _expect(sonar_display.built_grid_segment_count() > 0, "sonar overlays a perspective grid on the relief") adapter.sonar_view_toggle_requested.emit() _expect(sonar_display.is_expanded(), "sonar can expand without pausing the dive") adapter.sonar_view_toggle_requested.emit() _expect(not sonar_display.is_expanded(), "expanded sonar returns to cockpit view") var start_position: Vector3 = actor.global_position var starting_battery: float = composition.latest_update().snapshot.battery.remaining_kwh Input.action_press(ProductionInputAdapter.ACTION_FORWARD) for frame in 35: await physics_frame Input.action_release(ProductionInputAdapter.ACTION_FORWARD) _expect(actor.global_position.distance_to(start_position) > 0.1, "keyboard propulsion moves the submarine") _expect(actor.velocity.length() > 0.1, "propulsion builds inertial velocity") _expect( composition.latest_update().snapshot.battery.remaining_kwh < starting_battery, "propulsion effort drains the domain battery" ) var start_heading: float = actor.rotation.y Input.action_press(ProductionInputAdapter.ACTION_RIGHT) for frame in 24: await physics_frame Input.action_release(ProductionInputAdapter.ACTION_RIGHT) _expect(absf(actor.rotation.y - start_heading) > 0.01, "keyboard steering changes submarine heading") _expect(absf(actor.current_yaw_velocity_rps()) > 0.01, "yaw keeps angular inertia after release") for frame in 90: await physics_frame var settled_heading: float = actor.rotation.y var initial_look_yaw: float = camera.look_yaw_radians() var cockpit_transform: Transform3D = instruments.global_transform var mouse_motion := InputEventMouseMotion.new() mouse_motion.relative = Vector2(40.0, -20.0) adapter._unhandled_input(mouse_motion) await physics_frame _expect(absf(camera.look_yaw_radians() - initial_look_yaw) > 0.01, "mouse rotates the cockpit camera") _expect(absf(actor.rotation.y - settled_heading) < 0.001, "mouse does not steer the submarine") _expect(instruments.global_transform.is_equal_approx(cockpit_transform), "cockpit remains fixed while the player looks around") var paused_sequence: int = composition.latest_update().snapshot.tick_sequence var paused_sonar_progress: float = sonar_service.current_scan().reveal_progress adapter.pause_requested.emit() await process_frame _expect(paused, "pause menu suspends the scene tree") var pause_menu := composition.active_overlay() as PauseMenu _expect(pause_menu != null, "pause overlay is displayed") await process_frame await process_frame _expect( composition.latest_update().snapshot.tick_sequence == paused_sequence, "dive ticks are frozen while paused" ) _expect( is_equal_approx(sonar_service.current_scan().reveal_progress, paused_sonar_progress), "sonar reveal is frozen while paused" ) var controls_button := pause_menu.get_node("%ControlsButton") as Button controls_button.pressed.emit() await process_frame var controls := composition.active_overlay() as ControlsSettingsScreen _expect(controls != null, "controls settings open over the paused dive") _expect(adapter.binding_snapshots().size() == 11, "all gameplay keyboard bindings are exposed") var rebind_results: Array = [] adapter.rebind_result_available.connect( func(result) -> void: rebind_results.append(result) ) adapter.begin_rebind(ProductionInputAdapter.ACTION_FORWARD) adapter._unhandled_input(_pressed_key(KEY_UP)) _expect(not rebind_results.is_empty() and rebind_results.back().accepted, "free key remapping is accepted") _expect( _binding_for(adapter, ProductionInputAdapter.ACTION_FORWARD) == "Up", "binding snapshot reflects the remapped key" ) adapter.begin_rebind(ProductionInputAdapter.ACTION_BACKWARD) adapter._unhandled_input(_pressed_key(KEY_UP)) _expect( not rebind_results.back().accepted and rebind_results.back().conflicting_action_id == ProductionInputAdapter.ACTION_FORWARD, "binding conflict identifies the existing action" ) var swap_result = adapter.resolve_pending_conflict(true) _expect(swap_result.accepted, "conflicting bindings can be swapped") _expect( _binding_for(adapter, ProductionInputAdapter.ACTION_BACKWARD) == "Up", "requested action receives the conflicting key" ) adapter.restore_default_bindings() _expect( _binding_for(adapter, ProductionInputAdapter.ACTION_FORWARD) == "W", "default bindings can be restored" ) adapter.pause_requested.emit() await process_frame _expect(composition.active_overlay() is PauseMenu, "escape from settings returns to pause") adapter.pause_requested.emit() await process_frame _expect(not paused, "second escape resumes the dive") for frame in 3: await physics_frame if composition.latest_update().snapshot.tick_sequence > paused_sequence: break _expect( composition.latest_update().snapshot.tick_sequence > paused_sequence, "fixed dive ticks resume after pause" ) func _pressed_key(keycode: Key) -> InputEventKey: var event := InputEventKey.new() event.physical_keycode = keycode event.pressed = true return event func _sonar_screen_faces_camera( sonar_screen: MeshInstance3D, cockpit_camera: Camera3D ) -> bool: if sonar_screen == null or cockpit_camera == null: return false var direction_to_camera := ( cockpit_camera.global_position - sonar_screen.global_position ).normalized() return sonar_screen.global_basis.z.normalized().dot(direction_to_camera) > 0.85 func _sonar_screen_is_ahead_of_bank( sonar_screen: MeshInstance3D, electrical_bank: Node3D ) -> bool: if sonar_screen == null or electrical_bank == null: return false for child: Node in electrical_bank.get_children(): if child is MeshInstance3D and child.name.begins_with("JunctionBox"): if sonar_screen.global_position.z <= (child as MeshInstance3D).global_position.z: return false return true func _sonar_screen_clears_housing( sonar_screen: MeshInstance3D, sonar_housing: MeshInstance3D ) -> bool: if sonar_screen == null or sonar_housing == null: return false var housing_box := sonar_housing.mesh as BoxMesh if housing_box == null: return false var housing_front_z := ( sonar_housing.position.z + housing_box.size.z * sonar_housing.scale.z * 0.5 ) return sonar_screen.position.z >= housing_front_z + 0.005 func _binding_for(adapter: ProductionInputAdapter, action_id: StringName) -> String: for binding in adapter.binding_snapshots(): if binding.action_id == action_id: return binding.binding_text return "" func _expect(condition: bool, label: String) -> void: if not condition: _failures.append(label)