chore(multiplayer): Phase 0 refactors + graphics/perf settings groundwork

Lands the non-networked Phase 0 tasks from multiplayer-todo.md (ship/camera/
arena refactors, sim constants, background FPS handling) plus a first pass
at exposing graphics/performance settings (presets, resolution scaling,
vsync, FPS cap, perf overlay) and a GPU profiling harness for the
real-hardware follow-up in task 0.15b.
This commit is contained in:
Josh Creek
2026-08-19 22:37:17 +01:00
parent 88591e031f
commit 04691aaa48
29 changed files with 2212 additions and 134 deletions
+48 -12
View File
@@ -19,23 +19,59 @@ extends Node3D
@export var glow_hdr_threshold := 1.0
var _env: Environment
# Lights authored with shadow_enabled = true (the DirectionalLight3D + 4
# PitchLights omnis) — captured once, before gating ever touches them. Every
# call after the first re-applies VideoSettings.shadows_enabled to exactly
# these lights, so the set can't self-poison (if it were re-derived from
# current state, a light this same code just turned off would look
# indistinguishable from FillLight, which is authored off on purpose and must
# never be turned on by the preset ladder).
var _shadow_capable_lights: Array[Light3D] = []
func _ready():
add_to_group("arena")
# A headless server never renders, so duplicating and configuring a full
# Environment (glow/SSAO/SSIL/SDFGI) for it is pure waste — mirrors the
# same guard at ship.gd and arena_boundary.gd.
if DisplayServer.get_name() == "headless":
return
var world_env := get_node_or_null("WorldEnvironment") as WorldEnvironment
if world_env and world_env.environment:
var env := world_env.environment.duplicate(true) as Environment
world_env.environment = env
_env = world_env.environment.duplicate(true) as Environment
world_env.environment = _env
if sky_material:
if not env.sky:
env.sky = Sky.new()
env.sky.sky_material = sky_material
env.ambient_light_color = ambient_light_color
env.ambient_light_energy = ambient_light_energy
env.glow_intensity = glow_intensity
env.glow_strength = glow_strength
env.glow_bloom = glow_bloom
env.glow_hdr_threshold = glow_hdr_threshold
VideoSettings.apply_to_environment(env)
if not _env.sky:
_env.sky = Sky.new()
_env.sky.sky_material = sky_material
_env.ambient_light_color = ambient_light_color
_env.ambient_light_energy = ambient_light_energy
_env.glow_intensity = glow_intensity
_env.glow_strength = glow_strength
_env.glow_bloom = glow_bloom
_env.glow_hdr_threshold = glow_hdr_threshold
for light in find_children("*", "Light3D", true, false):
if (light as Light3D).shadow_enabled:
_shadow_capable_lights.append(light)
_apply_video_settings()
# Task 0.17: a preset change from the settings menu must take effect
# on the arena that's already loaded, not just the next one — this is
# the "settings persist and apply without a restart" acceptance bar.
VideoSettings.settings_changed.connect(_apply_video_settings)
# Re-run on every VideoSettings.settings_changed (preset or individual
# toggle) as well as once at load. Shadow gating lives here rather than in
# VideoSettings.apply_to_environment() because it targets Light3D nodes in
# this arena's own tree, not the Environment resource.
func _apply_video_settings() -> void:
if not is_instance_valid(_env):
return
VideoSettings.apply_to_environment(_env)
for light in _shadow_capable_lights:
if is_instance_valid(light):
light.shadow_enabled = VideoSettings.shadows_enabled
func get_ball_spawn() -> Transform3D: