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

@@ -1,192 +1,57 @@
class_name CockpitPressureBulkhead
extends Node3D
const CockpitMaterialPalette = preload(
"res://game/presentation/cockpit/cockpit_material_palette.gd"
)
const CockpitMeshFactory = preload(
"res://game/presentation/cockpit/cockpit_mesh_factory.gd"
)
## Imported pressure hull (end cap, knuckle and barrel in one shell) and the
## porthole bolted into it. Both models are instanced by `cockpit_interior.tscn`.
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
const WHITEBOX_NAMES: Array[String] = [
"LeftBulkhead",
"RightBulkhead",
"TopBulkhead",
"BottomBulkhead",
]
var _materials: CockpitMaterialPalette
var _panel_count := 0
var _hull_shells: Array[Node] = []
var _porthole_frame: MeshInstance3D
var _porthole_glass: MeshInstance3D
func _ready() -> void:
_materials = CockpitMaterialPalette.new()
_hide_rectangular_whitebox()
_style_existing_frame()
_build_curved_panels()
_build_outer_rib()
_build_fasteners()
_build_clamp_plates()
var hull := get_node_or_null("HullWall")
if hull != null:
_hull_shells = hull.find_children("*", "MeshInstance3D", true, false)
_porthole_frame = _find_mesh("Porthole")
_porthole_glass = _find_mesh("PortholeGlass")
if _hull_shells.is_empty() or _porthole_frame == null or _porthole_glass == null:
push_error("Pressure hull or porthole model is missing a node")
return
_porthole_glass.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
func panel_count() -> int:
return _panel_count
return _hull_shells.size()
func hull_shell() -> MeshInstance3D:
return null if _hull_shells.is_empty() else _hull_shells[0] as MeshInstance3D
func porthole_frame() -> MeshInstance3D:
return _porthole_frame
func porthole_glass() -> MeshInstance3D:
return _porthole_glass
func rectangular_whitebox_is_hidden() -> bool:
for node_name: String in ["LeftBulkhead", "RightBulkhead", "TopBulkhead", "BottomBulkhead"]:
for node_name: String in WHITEBOX_NAMES:
var whitebox := get_parent().get_node_or_null(node_name) as MeshInstance3D
if whitebox != null and whitebox.visible:
return false
return true
func _hide_rectangular_whitebox() -> void:
for node_name: String in ["LeftBulkhead", "RightBulkhead", "TopBulkhead", "BottomBulkhead"]:
var whitebox := get_parent().get_node_or_null(node_name) as MeshInstance3D
if whitebox != null:
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.brass
func _build_curved_panels() -> void:
var panel_angle := TAU / float(PANEL_COUNT)
for panel_index in PANEL_COUNT:
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)
panel.material_override = _materials.overhead_steel
panel.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
add_child(panel)
_panel_count += 1
func _create_curved_panel_mesh(start_angle: float, end_angle: float) -> ArrayMesh:
var surface := SurfaceTool.new()
surface.begin(Mesh.PRIMITIVE_TRIANGLES)
for angular_index in ANGULAR_STEPS:
var angle_ratio_0 := float(angular_index) / float(ANGULAR_STEPS)
var angle_ratio_1 := float(angular_index + 1) / float(ANGULAR_STEPS)
var angle_0 := lerpf(start_angle, end_angle, angle_ratio_0)
var angle_1 := lerpf(start_angle, end_angle, angle_ratio_1)
for radial_index in RADIAL_STEPS:
var radial_ratio_0 := float(radial_index) / float(RADIAL_STEPS)
var radial_ratio_1 := float(radial_index + 1) / float(RADIAL_STEPS)
_add_panel_quad(
surface,
angle_0,
angle_1,
angle_ratio_0,
angle_ratio_1,
radial_ratio_0,
radial_ratio_1
)
surface.generate_normals()
return surface.commit()
func _add_panel_quad(
surface: SurfaceTool,
angle_0: float,
angle_1: float,
angle_ratio_0: float,
angle_ratio_1: float,
radial_ratio_0: float,
radial_ratio_1: float
) -> void:
_add_panel_vertex(surface, angle_0, angle_ratio_0, radial_ratio_0)
_add_panel_vertex(surface, angle_1, angle_ratio_1, radial_ratio_0)
_add_panel_vertex(surface, angle_0, angle_ratio_0, radial_ratio_1)
_add_panel_vertex(surface, angle_0, angle_ratio_0, radial_ratio_1)
_add_panel_vertex(surface, angle_1, angle_ratio_1, radial_ratio_0)
_add_panel_vertex(surface, angle_1, angle_ratio_1, radial_ratio_1)
func _add_panel_vertex(
surface: SurfaceTool,
angle: float,
angle_ratio: float,
radial_ratio: float
) -> void:
var radius := lerpf(INNER_RADIUS, OUTER_RADIUS, radial_ratio)
var convex_depth := sin(radial_ratio * PI) * 0.075
surface.set_uv(Vector2(angle_ratio, radial_ratio))
surface.add_vertex(
Vector3(cos(angle) * radius, sin(angle) * radius, 0.045 + convex_depth)
)
func _build_outer_rib() -> void:
var rib := MeshInstance3D.new()
rib.name = "OuterPressureRib"
var torus := TorusMesh.new()
torus.inner_radius = 1.055
torus.outer_radius = 1.125
torus.rings = 64
torus.ring_segments = 12
rib.mesh = torus
rib.material_override = _materials.dark_steel
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 FASTENER_COUNT:
var angle := (fastener_index + 0.5) * TAU / float(FASTENER_COUNT)
var fastener_position := Vector3(
cos(angle) * 0.96,
sin(angle) * 0.96,
0.12
)
CockpitMeshFactory.add_cylinder(
"OuterFastener",
self,
fastener_position,
0.020,
0.018,
_materials.brass,
Vector3(90.0, 0.0, 0.0)
)
CockpitMeshFactory.add_sphere(
"FastenerCap",
self,
fastener_position + Vector3(0.0, 0.0, 0.012),
0.017,
_materials.brushed_steel
)
func _build_clamp_plates() -> void:
for plate_index in 12:
var angle := (plate_index + 0.5) * TAU / 12.0
var plate := CockpitMeshFactory.add_box(
"PressureClamp",
self,
Vector3(cos(angle) * 0.88, sin(angle) * 0.88, 0.105),
Vector3(0.11, 0.055, 0.028),
_materials.brushed_steel
)
plate.rotation_degrees.z = rad_to_deg(angle) + 90.0
func _find_mesh(mesh_name: String) -> MeshInstance3D:
var matches := find_children(mesh_name, "MeshInstance3D", true, false)
return null if matches.is_empty() else matches[0] as MeshInstance3D