feat(cockpit): replace procedural cockpit with twelve scripted models
Quality Gate / Quality Gate (pull_request) Successful in 7m6s

Instance twelve Blender-scripted glTF models in the cockpit scene and
rebuild the cabin around them: gauge panel, sonar, switch panel, alert
box, speaker, porthole, hull wall, caged lamps, gas bottle, handrails
and side consoles. Presentation only; no domain, application or world
rule changes beyond enabling glow in the test environment.

- Needles, alert lamps and sonar screen are driven by the existing
  snapshots; the alert box now carries SYS/ATTN/ALRM.
- Depth dial reads percent of certified depth.
- Sonar display restyled as a turquoise holographic relief with a
  shader grid and height scale; SubViewport matches the glass (960x604).
- Cabin lighting retuned: warm pooled lamp light, cool porthole rim,
  lamp shadows, dial backlight.
- Procedural stand-ins removed (bulkhead, tanks, consoles, levers,
  indicator columns, placeholder pipe run, dial face PNGs).
- Tests adapted where nodes disappeared; one assertion removed
  (pipe_segment_count) until the pipe kit is placed.
- Model .glb files are committed without LFS; textures Godot extracts
  next to them at import are ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
OlivierandClaude Opus 5.5 committed 2026-10-06 21:46:46 +02:00
1 parent 912123087f
commit 2c1ea50aba
45 files changed
+1353 -1603

No files matched your search

+45 -177
View File
@@ -4,6 +4,7 @@ extends Node3D
const DiveUpdate = preload("res://game/domain/dive/dive_update.gd")
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd")
const CockpitAlertBox = preload("res://game/presentation/cockpit/cockpit_alert_box.gd")
const CockpitAnalogGauges = preload("res://game/presentation/cockpit/cockpit_analog_gauges.gd")
const CockpitDashboardDetailing = preload(
"res://game/presentation/cockpit/cockpit_dashboard_detailing.gd"
@@ -19,13 +20,17 @@ const CockpitSonarConsole = preload(
const CockpitPracticalLighting = preload(
"res://game/presentation/cockpit/cockpit_practical_lighting.gd"
)
const CockpitSwitchPanel = preload(
"res://game/presentation/cockpit/cockpit_switch_panel.gd"
)
var _materials: CockpitMaterialPalette
var _painted_steel: Material
var _dark_steel: Material
var _brass: Material
var _copper: Material
var _analog_gauges: CockpitAnalogGauges
var _alert_box: CockpitAlertBox
var _switch_panel: CockpitSwitchPanel
var _gas_bottle: Node3D
var _dashboard_detailing: CockpitDashboardDetailing
var _side_equipment: CockpitSideEquipment
var _sonar_console: CockpitSonarConsole
@@ -35,9 +40,6 @@ var _practical_lighting: CockpitPracticalLighting
func _ready() -> void:
_create_materials()
_apply_materials_to_existing_cockpit()
_build_pressure_hull_ribs()
_build_life_support_pipes()
_build_panel_hardware()
_build_dashboard_instrumentation()
_build_equipment_assemblies()
@@ -46,32 +48,24 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
if update == null or update.snapshot == null or telemetry == null:
return
var snapshot = update.snapshot
_analog_gauges.present(
snapshot.oxygen.ratio,
snapshot.battery.ratio,
snapshot.hull.ratio,
clampf(
snapshot.depth.current_m
/ maxf(snapshot.depth.certified_m, 0.001),
0.0,
1.0
),
snapshot.alerts.maximum
)
var alert_level: DiveAlertLevel.Value = snapshot.alerts.maximum
if _analog_gauges != null:
_analog_gauges.present(
snapshot.oxygen.ratio,
snapshot.battery.ratio,
snapshot.hull.ratio,
clampf(
snapshot.depth.current_m
/ maxf(snapshot.depth.certified_m, 0.001),
0.0,
1.0
)
)
if _alert_box != null:
_alert_box.present(alert_level)
_practical_lighting.present(alert_level)
func pressure_hull_rib_count() -> int:
var ribs := get_node_or_null("PressureHullRibs")
return 0 if ribs == null else ribs.get_child_count()
func pipe_segment_count() -> int:
var pipes := get_node_or_null("LifeSupportPipes")
return 0 if pipes == null else pipes.get_child_count()
func battery_needle_degrees() -> float:
return 0.0 if _analog_gauges == null else _analog_gauges.battery_needle_degrees()
@@ -80,25 +74,30 @@ func textures_are_loaded() -> bool:
return (
_materials != null
and _materials.overhead_steel.albedo_texture != null
and _materials.oxygen_gauge_face.albedo_texture != null
and _materials.battery_gauge_face.albedo_texture != null
and _materials.hull_gauge_face.albedo_texture != null
and _materials.depth_gauge_face.albedo_texture != null
and _analog_gauges != null
and _analog_gauges.dial_texture_is_loaded()
)
func dashboard_control_count() -> int:
return 0 if _dashboard_detailing == null else _dashboard_detailing.control_count()
return 0 if _switch_panel == null else _switch_panel.control_count()
func side_equipment_count() -> int:
if _side_equipment == null:
return 0
return _side_equipment.tank_count() + _side_equipment.junction_box_count()
return _side_equipment.junction_box_count() + (0 if _gas_bottle == null else 1)
func side_control_count() -> int:
return 0 if _side_equipment == null else _side_equipment.control_count()
if _side_equipment == null:
return 0
var bottle_valve_count := (
0
if _gas_bottle == null
else _gas_bottle.find_children("*ValveWheel", "Node3D", true, false).size()
)
return _side_equipment.control_count() + bottle_valve_count
func sonar_control_count() -> int:
@@ -117,8 +116,6 @@ func _create_materials() -> void:
_materials = CockpitMaterialPalette.new()
_painted_steel = _materials.painted_steel
_dark_steel = _materials.dark_steel
_brass = _materials.brass
_copper = _materials.copper
func _apply_materials_to_existing_cockpit() -> void:
@@ -127,39 +124,12 @@ func _apply_materials_to_existing_cockpit() -> void:
[
"CabinShell/LeftWall",
"CabinShell/RightWall",
"LeftConsole",
"RightConsole",
]
)
_apply_material_to_paths(
_dark_steel,
[
"CabinShell/RearBulkhead",
"PortholeAssembly/LeftBulkhead",
"PortholeAssembly/RightBulkhead",
"PortholeAssembly/BottomBulkhead",
"ThrottleLever",
"VerticalLever",
]
)
_apply_material_to_paths(_dark_steel, ["CabinShell/RearBulkhead"])
_apply_material_to_paths(
_materials.overhead_steel,
[
"CabinShell/Ceiling",
"PortholeAssembly/TopBulkhead",
"Dashboard/MainPanel",
]
)
_apply_material_to_paths(
_brass,
[
"PortholeAssembly/WindowFrame",
"Dashboard/ResourcesFrame",
"Dashboard/NavigationFrame",
"Dashboard/IntegrityFrame",
"ThrottleKnob",
"VerticalKnob",
]
["CabinShell/Ceiling"]
)
func _apply_material_to_paths(material: Material, paths: Array[String]) -> void:
var cockpit := get_parent()
@@ -169,128 +139,26 @@ func _apply_material_to_paths(material: Material, paths: Array[String]) -> void:
mesh.material_override = material
func _build_pressure_hull_ribs() -> void:
var root := Node3D.new()
root.name = "PressureHullRibs"
add_child(root)
for rib_index in 2:
var rib := MeshInstance3D.new()
rib.name = "CircularRib%d" % (rib_index + 1)
var torus := TorusMesh.new()
torus.inner_radius = 1.00
torus.outer_radius = 1.075
torus.rings = 48
torus.ring_segments = 10
rib.mesh = torus
rib.material_override = _dark_steel
rib.position = Vector3(0.0, 0.22, -0.90 + rib_index * 1.35)
rib.rotation_degrees.x = 90.0
root.add_child(rib)
for side: float in [-1.0, 1.0]:
CockpitMeshFactory.add_box(
"AngledShoulder",
root,
Vector3(side * 0.88, 0.84, -0.36),
Vector3(0.11, 0.62, 1.52),
_painted_steel,
Vector3(0.0, 0.0, side * -18.0)
)
CockpitMeshFactory.add_box(
"LowerStringer",
root,
Vector3(side * 0.91, -0.38, -0.18),
Vector3(0.10, 0.12, 1.70),
_dark_steel
)
func _build_life_support_pipes() -> void:
var root := Node3D.new()
root.name = "LifeSupportPipes"
add_child(root)
for side: float in [-1.0, 1.0]:
var x: float = side * 0.83
CockpitMeshFactory.add_cylinder(
"VerticalPipe",
root,
Vector3(x, 0.33, -1.12),
0.027,
1.18,
_copper
)
CockpitMeshFactory.add_cylinder(
"ReturnPipe",
root,
Vector3(side * 0.73, 0.47, -1.09),
0.019,
0.92,
_brass
)
for clamp_y: float in [-0.04, 0.35, 0.72]:
var clamp := MeshInstance3D.new()
clamp.name = "PipeClamp"
var torus := TorusMesh.new()
torus.inner_radius = 0.027
torus.outer_radius = 0.041
torus.rings = 16
torus.ring_segments = 6
clamp.mesh = torus
clamp.material_override = _dark_steel
clamp.position = Vector3(x, clamp_y, -1.12)
root.add_child(clamp)
CockpitMeshFactory.add_sphere(
"PipeJunction",
root,
Vector3(x, 0.91, -1.12),
0.052,
_copper
)
CockpitMeshFactory.add_cylinder_between(
"UpperBranchPipe",
root,
Vector3(x, 0.84, -1.12),
Vector3(side * 0.72, 0.77, -1.10),
0.019,
_brass
)
func _build_panel_hardware() -> void:
var root := Node3D.new()
root.name = "PanelHardware"
add_child(root)
for x: float in [-0.73, -0.49, -0.24, 0.24, 0.49, 0.73]:
for y: float in [-0.18, 0.22]:
CockpitMeshFactory.add_cylinder(
"PanelFastener",
root,
Vector3(x, y, -1.105),
0.014,
0.018,
_dark_steel,
Vector3(90.0, 0.0, 0.0)
)
func _build_dashboard_instrumentation() -> void:
var dashboard := get_parent().get_node_or_null("Dashboard") as Node3D
if dashboard == null:
return
_dashboard_detailing = CockpitDashboardDetailing.new(_materials)
_dashboard_detailing = CockpitDashboardDetailing.new()
dashboard.add_child(_dashboard_detailing)
_dashboard_detailing.hide_obsolete_elements(get_parent())
_analog_gauges = CockpitAnalogGauges.new(_materials)
dashboard.add_child(_analog_gauges)
_analog_gauges = dashboard.get_node_or_null("AnalogGauges") as CockpitAnalogGauges
func _build_equipment_assemblies() -> void:
_side_equipment = CockpitSideEquipment.new(_materials)
add_child(_side_equipment)
var sonar_monitor := get_parent().get_node_or_null("SonarMonitor") as Node3D
if sonar_monitor != null:
_sonar_console = CockpitSonarConsole.new(_materials)
sonar_monitor.add_child(_sonar_console)
var cockpit := get_parent()
_sonar_console = cockpit.get_node_or_null("SonarMonitor") as CockpitSonarConsole
_alert_box = cockpit.get_node_or_null("AlertBox") as CockpitAlertBox
_switch_panel = cockpit.get_node_or_null("SwitchPanel") as CockpitSwitchPanel
_gas_bottle = cockpit.get_node_or_null("GasBottle") as Node3D
_practical_lighting = CockpitPracticalLighting.new(_materials)
add_child(_practical_lighting)
# The alert box hangs between the left lamp and the gas bottle: casting, it
# puts the bottle in full shadow.
CockpitMeshFactory.disable_shadow_casting(_alert_box)