Reframe cockpit around industrial reference

This commit is contained in:
chindit committed 2026-10-04 09:50:41 +02:00
1 parent c2b3ac42a3
commit dffe790926
9 files changed
+146 -95

No files matched your search

+5 -4
View File
@@ -816,10 +816,11 @@ binnacle solidaire du tableau de bord, hors du hublot. La façade du tableau est
structurée par des rails, séparateurs, vis et banques de commutateurs physiques.
Passe du 4 octobre 2026 : les quatre volumes rectangulaires autour du hublot
sont remplacés par douze panneaux annulaires bombés, séparés par des joints fins
et ceinturés par un raidisseur. Les cadrans utilisent des graduations physiques
et les témoins colorés provisoires sont remplacés par trois voyants encastrés ;
un seul voyant reflète l'état d'alerte courant.
sont remplacés par une cloison annulaire bombée continue, serrée entre deux
raidisseurs métalliques et boulonnée. Le tableau reprend la hiérarchie de la
référence `design.jpg` : quatre cadrans principaux de tailles variées, petits
afficheurs de valeurs exactes et trois voyants encastrés dont un seul reflète
l'état d'alerte courant.
- Construire une coque intérieure 3D fixe par rapport au véhicule, indépendante
du regard de la caméra.
@@ -6,8 +6,10 @@ const CockpitMeshFactory = preload("res://game/presentation/cockpit/cockpit_mesh
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
var _materials: CockpitMaterialPalette
var _oxygen_needle: Node3D
var _battery_needle: Node3D
var _pressure_needle: Node3D
var _hull_needle: Node3D
var _depth_needle: Node3D
var _indicator_materials: Array[StandardMaterial3D] = []
var _active_indicator_count := 0
@@ -15,29 +17,49 @@ var _active_indicator_count := 0
func _init(materials: CockpitMaterialPalette) -> void:
_materials = materials
name = "AnalogGauges"
_build_binnacle()
_build_instrument_cluster()
_build_indicator_bank()
_oxygen_needle = _add_gauge(
"OxygenGauge",
Vector3(-0.50, 0.12, 0.145),
"%",
0.72
)
_battery_needle = _add_gauge(
"BatteryGauge",
Vector3(-0.095, 0.315, 0.145),
"%"
Vector3(-0.22, 0.12, 0.145),
"%",
0.72
)
_pressure_needle = _add_gauge(
"PressureGauge",
Vector3(0.095, 0.315, 0.145),
"BAR"
_hull_needle = _add_gauge(
"HullGauge",
Vector3(0.05, 0.12, 0.145),
"%",
0.56
)
_add_binnacle_label("CHARGE BAT.", Vector3(-0.095, 0.235, 0.160))
_add_binnacle_label("PRESSION COQUE", Vector3(0.095, 0.235, 0.160))
_depth_needle = _add_gauge(
"DepthGauge",
Vector3(0.40, 0.12, 0.145),
"%",
0.90
)
_add_cluster_label("OXYGÈNE", Vector3(-0.50, 0.025, 0.170))
_add_cluster_label("BATTERIE", Vector3(-0.22, 0.025, 0.170))
_add_cluster_label("COQUE", Vector3(0.05, 0.045, 0.170))
_add_cluster_label("PROFONDEUR", Vector3(0.40, 0.005, 0.170))
func present(
oxygen_ratio: float,
battery_ratio: float,
pressure_ratio: float,
hull_ratio: float,
depth_ratio: float,
alert_level: DiveAlertLevel.Value
) -> void:
_set_needle_ratio(_oxygen_needle, oxygen_ratio)
_set_needle_ratio(_battery_needle, battery_ratio)
_set_needle_ratio(_pressure_needle, pressure_ratio)
_set_needle_ratio(_hull_needle, hull_ratio)
_set_needle_ratio(_depth_needle, depth_ratio)
_update_indicator_bank(alert_level)
@@ -49,23 +71,27 @@ func indicator_count() -> int:
return _indicator_materials.size()
func gauge_count() -> int:
return 4
func active_indicator_count() -> int:
return _active_indicator_count
func _build_binnacle() -> void:
func _build_instrument_cluster() -> void:
CockpitMeshFactory.add_box(
"BinnacleHousing",
"InstrumentClusterHousing",
self,
Vector3(0.0, 0.290, 0.095),
Vector3(0.40, 0.180, 0.105),
Vector3(-0.04, 0.105, 0.095),
Vector3(1.26, 0.255, 0.105),
_materials.overhead_steel
)
CockpitMeshFactory.add_box(
"BinnacleFoot",
"InstrumentClusterFoot",
self,
Vector3(0.0, 0.205, 0.075),
Vector3(0.47, 0.040, 0.14),
Vector3(-0.04, -0.025, 0.075),
Vector3(1.32, 0.040, 0.14),
_materials.dark_steel
)
@@ -81,11 +107,11 @@ func _build_indicator_bank() -> void:
]
var captions: Array[String] = ["SYS", "ATTN", "ALRM"]
for indicator_index in colors.size():
var x := -0.055 + indicator_index * 0.055
var y := 0.165 - indicator_index * 0.050
CockpitMeshFactory.add_cylinder(
"%sBezel" % captions[indicator_index],
root,
Vector3(x, 0.198, 0.156),
Vector3(0.205, y, 0.156),
0.016,
0.012,
_materials.brass,
@@ -96,7 +122,7 @@ func _build_indicator_bank() -> void:
CockpitMeshFactory.add_cylinder(
"%sLens" % captions[indicator_index],
root,
Vector3(x, 0.198, 0.165),
Vector3(0.205, y, 0.165),
0.009,
0.008,
material,
@@ -108,7 +134,7 @@ func _build_indicator_bank() -> void:
label.font_size = 8
label.pixel_size = 0.00042
label.modulate = Color(0.70, 0.67, 0.56)
label.position = Vector3(x, 0.177, 0.168)
label.position = Vector3(0.235, y, 0.168)
root.add_child(label)
@@ -136,7 +162,7 @@ func _update_indicator_bank(alert_level: DiveAlertLevel.Value) -> void:
_active_indicator_count = 1
func _add_binnacle_label(caption: String, position: Vector3) -> void:
func _add_cluster_label(caption: String, position: Vector3) -> void:
var label := Label3D.new()
label.name = "%sLabel" % caption.to_pascal_case().replace(" ", "")
label.text = caption
@@ -147,18 +173,23 @@ func _add_binnacle_label(caption: String, position: Vector3) -> void:
add_child(label)
func _add_gauge(gauge_name: String, position: Vector3, caption: String) -> Node3D:
func _add_gauge(
gauge_name: String,
position: Vector3,
caption: String,
gauge_scale: float
) -> Node3D:
var gauge := Node3D.new()
gauge.name = gauge_name
gauge.position = position
gauge.scale = Vector3(0.62, 0.62, 0.62)
gauge.scale = Vector3(gauge_scale, gauge_scale, gauge_scale)
add_child(gauge)
CockpitMeshFactory.add_box(
"MountingPlate",
gauge,
Vector3(0.0, 0.0, -0.025),
Vector3(0.225, 0.225, 0.055),
_materials.dark_steel
_materials.overhead_steel
)
for screw_position: Vector2 in [
Vector2(-0.086, -0.086),
@@ -182,7 +213,7 @@ func _add_gauge(gauge_name: String, position: Vector3, caption: String) -> Node3
bezel_mesh.height = 0.035
bezel_mesh.radial_segments = 32
bezel.mesh = bezel_mesh
bezel.material_override = _materials.brass
bezel.material_override = _materials.brushed_steel
bezel.rotation_degrees.x = 90.0
gauge.add_child(bezel)
var face := MeshInstance3D.new()
@@ -64,8 +64,8 @@ func _build_structural_frame() -> void:
CockpitMeshFactory.add_box(
"ScreenSeparator",
self,
Vector3(x, 0.015, 0.118),
Vector3(0.038, 0.39, 0.048),
Vector3(x, -0.075, 0.118),
Vector3(0.038, 0.16, 0.048),
_materials.brass
)
for screw_position: Vector2 in [
@@ -14,7 +14,6 @@ const CockpitMeshFactory = preload("res://game/presentation/cockpit/cockpit_mesh
var _materials: CockpitMaterialPalette
var _painted_steel: Material
var _dark_steel: Material
var _overhead_steel: Material
var _brass: Material
var _copper: Material
var _analog_gauges: CockpitAnalogGauges
@@ -39,10 +38,12 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
return
var snapshot = update.snapshot
_analog_gauges.present(
snapshot.oxygen.ratio,
snapshot.battery.ratio,
snapshot.hull.ratio,
clampf(
snapshot.hull.exterior_pressure_bar
/ maxf(snapshot.hull.certified_pressure_bar, 0.001),
snapshot.depth.current_m
/ maxf(snapshot.depth.certified_m, 0.001),
0.0,
1.0
),
@@ -89,7 +90,6 @@ func _create_materials() -> void:
_materials = CockpitMaterialPalette.new()
_painted_steel = _materials.painted_steel
_dark_steel = _materials.dark_steel
_overhead_steel = _materials.overhead_steel
_brass = _materials.brass
_copper = _materials.copper
@@ -116,7 +116,7 @@ func _apply_materials_to_existing_cockpit() -> void:
]
)
_apply_material_to_paths(
_overhead_steel,
_materials.overhead_steel,
[
"CabinShell/Ceiling",
"PortholeAssembly/TopBulkhead",
@@ -240,7 +240,7 @@ func _build_overhead_console() -> void:
root,
Vector3(0.0, 0.94, -1.06),
Vector3(0.30, 0.09, 0.12),
_overhead_steel,
_materials.overhead_steel,
Vector3(-8.0, 0.0, 0.0)
)
CockpitMeshFactory.add_box(
@@ -30,20 +30,16 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
return
var snapshot = update.snapshot
_resources_label.text = (
"BATTERIE\n%.1f / %.1f kWh · %.0f %%\n%.0f kW\n\nOXYGÈNE\n%.1f / %.1f L · %.0f %%\n%.1f L/min"
"BAT %.1f kWh · O₂ %.0f %%\n%.0f kW · %.1f L/min"
% [
snapshot.battery.remaining_kwh,
snapshot.battery.maximum_kwh,
snapshot.battery.ratio * 100.0,
snapshot.battery.current_power_kw,
snapshot.oxygen.remaining_l,
snapshot.oxygen.maximum_l,
snapshot.oxygen.ratio * 100.0,
snapshot.battery.current_power_kw,
snapshot.oxygen.consumption_l_per_min,
]
)
_navigation_label.text = (
"PROFONDEUR\n%.1f m\nMAX. CONSEILLÉ %.1f m\n\nVITESSE\n%.2f m/s"
"PROF. %.1f / %.1f m\nVIT. %.2f m/s"
% [
snapshot.depth.current_m,
snapshot.depth.certified_m,
@@ -51,10 +47,8 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
]
)
_integrity_label.text = (
"COQUE\n%.1f / %.1f · %.0f %%\n\nPRESSION\n%.2f / %.2f bar"
"COQUE %.0f %% · %.2f bar\nLIMITE %.2f bar"
% [
snapshot.hull.current_integrity,
snapshot.hull.maximum_integrity,
snapshot.hull.ratio * 100.0,
snapshot.hull.exterior_pressure_bar,
snapshot.hull.certified_pressure_bar,
+35 -35
View File
@@ -267,82 +267,82 @@ mesh = SubResource("UnitBoxMesh")
material_override = SubResource("PaintedMaterial")
[node name="ResourcesFrame" type="MeshInstance3D" parent="Dashboard"]
position = Vector3(-0.5, 0.03, 0.088)
scale = Vector3(0.45, 0.31, 0.025)
position = Vector3(-0.5, -0.075, 0.088)
scale = Vector3(0.45, 0.115, 0.025)
mesh = SubResource("UnitBoxMesh")
material_override = SubResource("BrassMaterial")
[node name="ResourcesScreen" type="MeshInstance3D" parent="Dashboard"]
position = Vector3(-0.5, 0.03, 0.102)
scale = Vector3(0.41, 0.27, 1)
position = Vector3(-0.5, -0.075, 0.102)
scale = Vector3(0.41, 0.085, 1)
mesh = SubResource("ScreenQuadMesh")
material_override = SubResource("ScreenMaterial")
[node name="ResourcesLabel" type="Label3D" parent="Dashboard"]
unique_name_in_owner = true
position = Vector3(-0.5, 0.03, 0.108)
pixel_size = 0.00115
font_size = 20
position = Vector3(-0.5, -0.075, 0.108)
pixel_size = 0.00082
font_size = 14
outline_size = 2
text = "BATTERIE\n— / — kWh\n— kW\n\nOXYGÈNE\n— / — L\n— L/min"
text = "BAT — kWh · O₂ — %\n— kW · — L/min"
modulate = Color(0.45, 0.89, 0.9, 1)
[node name="NavigationFrame" type="MeshInstance3D" parent="Dashboard"]
position = Vector3(0, 0.03, 0.088)
scale = Vector3(0.4, 0.31, 0.025)
position = Vector3(0, -0.075, 0.088)
scale = Vector3(0.4, 0.115, 0.025)
mesh = SubResource("UnitBoxMesh")
material_override = SubResource("BrassMaterial")
[node name="NavigationScreen" type="MeshInstance3D" parent="Dashboard"]
position = Vector3(0, 0.03, 0.102)
scale = Vector3(0.36, 0.27, 1)
position = Vector3(0, -0.075, 0.102)
scale = Vector3(0.36, 0.085, 1)
mesh = SubResource("ScreenQuadMesh")
material_override = SubResource("ScreenMaterial")
[node name="NavigationLabel" type="Label3D" parent="Dashboard"]
unique_name_in_owner = true
position = Vector3(0, 0.03, 0.108)
pixel_size = 0.00115
font_size = 20
position = Vector3(0, -0.075, 0.108)
pixel_size = 0.00082
font_size = 14
outline_size = 2
text = "PROFONDEUR\n— m\nMAX. — m\n\nVITESSE\n— m/s"
text = "PROF. — / — m\nVIT. — m/s"
modulate = Color(0.45, 0.89, 0.9, 1)
[node name="IntegrityFrame" type="MeshInstance3D" parent="Dashboard"]
position = Vector3(0.5, 0.03, 0.088)
scale = Vector3(0.45, 0.31, 0.025)
position = Vector3(0.5, -0.075, 0.088)
scale = Vector3(0.45, 0.115, 0.025)
mesh = SubResource("UnitBoxMesh")
material_override = SubResource("BrassMaterial")
[node name="IntegrityScreen" type="MeshInstance3D" parent="Dashboard"]
position = Vector3(0.5, 0.03, 0.102)
scale = Vector3(0.41, 0.27, 1)
position = Vector3(0.5, -0.075, 0.102)
scale = Vector3(0.41, 0.085, 1)
mesh = SubResource("ScreenQuadMesh")
material_override = SubResource("ScreenMaterial")
[node name="IntegrityLabel" type="Label3D" parent="Dashboard"]
unique_name_in_owner = true
position = Vector3(0.5, 0.03, 0.108)
pixel_size = 0.00115
font_size = 20
position = Vector3(0.5, -0.075, 0.108)
pixel_size = 0.00082
font_size = 14
outline_size = 2
text = "COQUE\n— / —\n\nPRESSION\n— / — bar"
text = "COQUE — % · — bar\nLIMITE — bar"
modulate = Color(0.45, 0.89, 0.9, 1)
[node name="ReturnLabel" type="Label3D" parent="Dashboard"]
unique_name_in_owner = true
position = Vector3(0.32, 0.205, 0.108)
pixel_size = 0.00082
font_size = 15
position = Vector3(0.61, 0.245, 0.17)
pixel_size = 0.00062
font_size = 12
outline_size = 2
text = "RETOUR · INCONNU"
modulate = Color(1, 0.75, 0.2, 1)
[node name="StatusLabel" type="Label3D" parent="Dashboard"]
unique_name_in_owner = true
position = Vector3(-0.34, 0.205, 0.108)
pixel_size = 0.00082
font_size = 15
position = Vector3(-0.61, 0.245, 0.17)
pixel_size = 0.00062
font_size = 12
outline_size = 2
text = "SYSTÈMES · NORMAL"
modulate = Color(0.45, 0.89, 0.9, 1)
@@ -413,16 +413,16 @@ text = "SONAR\nMODULE EN ATTENTE"
modulate = Color(0.34, 0.6, 0.6, 1)
[node name="EmergencyDial" type="MeshInstance3D" parent="."]
position = Vector3(-0.6, 0.39, -1.15)
scale = Vector3(0.38, 0.38, 0.38)
position = Vector3(-0.7, 0.32, -1.15)
scale = Vector3(0.18, 0.18, 0.18)
rotation_degrees = Vector3(90, 0, 0)
mesh = SubResource("DialMesh")
material_override = SubResource("RedMaterial")
[node name="EmergencyLabel" type="Label3D" parent="."]
position = Vector3(-0.6, 0.3, -1.125)
pixel_size = 0.0009
font_size = 15
position = Vector3(-0.7, 0.275, -1.125)
pixel_size = 0.00072
font_size = 12
outline_size = 2
text = "ÉJECTION · X"
modulate = Color(1, 0.48, 0.38, 1)
@@ -11,6 +11,7 @@ const GAUGE_FACE_TEXTURE_PATH := (
var painted_steel: Material
var dark_steel: Material
var overhead_steel: StandardMaterial3D
var brushed_steel: StandardMaterial3D
var brass: Material
var copper: Material
var rubber: Material
@@ -43,6 +44,7 @@ func _init() -> void:
0.56,
Vector3(1.6, 1.6, 1.6)
)
brushed_steel = _plain_material(Color(0.27, 0.30, 0.29), 0.82, 0.30)
brass = _weathered_material(
Color(0.28, 0.18, 0.045),
Color(0.08, 0.16, 0.13),
@@ -8,9 +8,10 @@ const CockpitMeshFactory = preload(
"res://game/presentation/cockpit/cockpit_mesh_factory.gd"
)
const PANEL_COUNT := 12
const ANGULAR_STEPS := 4
const RADIAL_STEPS := 3
const PANEL_COUNT := 1
const FASTENER_COUNT := 16
const ANGULAR_STEPS := 64
const RADIAL_STEPS := 4
const INNER_RADIUS := 0.72
const OUTER_RADIUS := 1.11
@@ -21,6 +22,7 @@ var _panel_count := 0
func _ready() -> void:
_materials = CockpitMaterialPalette.new()
_hide_rectangular_whitebox()
_style_existing_frame()
_build_curved_panels()
_build_outer_rib()
_build_fasteners()
@@ -45,12 +47,20 @@ func _hide_rectangular_whitebox() -> void:
whitebox.visible = false
func _style_existing_frame() -> void:
var frame := get_parent().get_node_or_null("WindowFrame") as MeshInstance3D
if frame != null:
frame.material_override = _materials.brushed_steel
for child: Node in get_parent().get_children():
if child is MeshInstance3D and child.name.begins_with("Bolt"):
(child as MeshInstance3D).material_override = _materials.dark_steel
func _build_curved_panels() -> void:
var panel_angle := TAU / float(PANEL_COUNT)
var seam_half_width := deg_to_rad(0.22)
for panel_index in PANEL_COUNT:
var start_angle := panel_index * panel_angle + seam_half_width
var end_angle := (panel_index + 1) * panel_angle - seam_half_width
var start_angle := panel_index * panel_angle
var end_angle := (panel_index + 1) * panel_angle
var panel := MeshInstance3D.new()
panel.name = "PressurePanel%02d" % (panel_index + 1)
panel.mesh = _create_curved_panel_mesh(start_angle, end_angle)
@@ -128,11 +138,23 @@ func _build_outer_rib() -> void:
rib.rotation_degrees.x = 90.0
rib.position.z = 0.025
add_child(rib)
var inner_rib := MeshInstance3D.new()
inner_rib.name = "InnerPressureRib"
var inner_torus := TorusMesh.new()
inner_torus.inner_radius = 0.695
inner_torus.outer_radius = 0.755
inner_torus.rings = 64
inner_torus.ring_segments = 12
inner_rib.mesh = inner_torus
inner_rib.material_override = _materials.brushed_steel
inner_rib.rotation_degrees.x = 90.0
inner_rib.position.z = 0.075
add_child(inner_rib)
func _build_fasteners() -> void:
for fastener_index in PANEL_COUNT:
var angle := (fastener_index + 0.5) * TAU / float(PANEL_COUNT)
for fastener_index in FASTENER_COUNT:
var angle := (fastener_index + 0.5) * TAU / float(FASTENER_COUNT)
CockpitMeshFactory.add_cylinder(
"OuterFastener",
self,
@@ -19,7 +19,7 @@ const ProductionInputAdapter = preload(
"res://game/infrastructure/input/production_input_adapter.gd"
)
const TEST_COUNT := 64
const TEST_COUNT := 65
var _failures: Array[String] = []
@@ -131,8 +131,9 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
_expect(detailing.textures_are_loaded(), "cockpit loads its overhead and 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(pressure_bulkhead.panel_count() == 12, "porthole bulkhead uses twelve curved pressure panels")
_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")