Add industrial metal soundtrack with seamless loop

- assets/audio/music/zombie_industrial_loop.ogg: 57.60 s Ogg Vorbis loop
  generated locally with YuE2-3B on the Tesla V100 (score written for this
  track; vocals removed with HTDemucs). The loop is spliced at the track's
  structural repeat with a 0.6 s crossfade: measured seam dip <= 1.6 dB.
- scripts/main.gd: "Music" AudioStreamPlayer at -11 dB, started at boot on
  desktop and on the start click on Web (browser autoplay policy). It forces
  stream.loop at runtime because the Web staging step re-imports the OGG with
  loop=false.
- tools/test_music.gd: soundtrack test (loop, length, volume, playback,
  still playing after player death).
- docs/MUSIC.md: source, loop construction, verification, licence.
- docs/WEB-DEPLOY.md: release ab6054111f5919a8 notes.

Initial commit of the project.
This commit is contained in:
enne2
2026-10-10 23:11:13 +02:00
commit 956a15a525
199 changed files with 4563 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
extends CanvasLayer
## Local-only trauma pivot: never modifies the player's aim angles.
const SHAKE_SECONDS := 0.42
const BLOOD_SECONDS := 1.15
const SPLAT_COUNT := 7
var pivot: Node3D
var shake_left := 0.0
var blood_left := 0.0
var intensity := 0.0
var phase := 0.0
var active := true
var overlay: Control
var vignette: ColorRect
var shader_material: ShaderMaterial
var splats: Array[TextureRect] = []
func _ready() -> void:
layer = 2
overlay = Control.new()
overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(overlay)
overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
vignette = ColorRect.new()
vignette.mouse_filter = Control.MOUSE_FILTER_IGNORE
shader_material = ShaderMaterial.new()
shader_material.shader = load("res://assets/effects/damage_vignette.gdshader")
vignette.material = shader_material
overlay.add_child(vignette)
vignette.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
for i in SPLAT_COUNT:
var splat := TextureRect.new()
splat.texture = load("res://assets/effects/blood.png")
splat.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
splat.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
splat.mouse_filter = Control.MOUSE_FILTER_IGNORE
splat.modulate.a = 0
overlay.add_child(splat)
splats.append(splat)
func hit(amount: float) -> void:
if not active or amount <= 0:
return
# Refresh instead of stacking tweens/particles: bounded cost and shake.
intensity = minf(1.0, maxf(intensity, 0.55 + amount / 100.0) + 0.12)
shake_left = SHAKE_SECONDS
blood_left = BLOOD_SECONDS
phase = randf_range(0, TAU)
var viewport_size := get_viewport().get_visible_rect().size
for i in splats.size():
var splat := splats[i]
var size := minf(viewport_size.x, viewport_size.y) * randf_range(0.22, 0.38)
splat.size = Vector2.ONE * size
splat.pivot_offset = splat.size * 0.5
# Keep the central reticle and HUD readable; splats lie near the edges.
var center := Vector2.ZERO
match i % 4:
0: center = viewport_size * Vector2(randf_range(0.06, 0.17), randf_range(0.25, 0.90))
1: center = viewport_size * Vector2(randf_range(0.83, 0.95), randf_range(0.25, 0.90))
2: center = viewport_size * Vector2(randf_range(0.25, 0.75), randf_range(0.82, 0.98))
3: center = viewport_size * Vector2(randf_range(0.3, 0.8), randf_range(0.04, 0.13))
splat.position = center - splat.size * 0.5
splat.rotation = randf_range(-PI, PI)
splat.modulate = Color(0.85, 0.1, 0.12, 0.85 * intensity)
shader_material.set_shader_parameter("strength", intensity)
func _process(delta: float) -> void:
if not active:
return
phase += delta * 42.0
shake_left = maxf(0, shake_left - delta)
blood_left = maxf(0, blood_left - delta)
var shake := pow(shake_left / SHAKE_SECONDS, 2.0) * intensity
if is_instance_valid(pivot):
pivot.position = Vector3(sin(phase) * 0.025, cos(phase * 1.3) * 0.018, sin(phase * 0.8) * 0.012) * shake
pivot.rotation = Vector3(sin(phase * 0.9) * 0.024, cos(phase) * 0.013, sin(phase * 1.1) * 0.035) * shake
var alpha := blood_left / BLOOD_SECONDS
for splat in splats:
splat.modulate.a = alpha * 0.85 * intensity
shader_material.set_shader_parameter("strength", minf(1, alpha * 1.4) * intensity)
if blood_left <= 0:
intensity = 0
func stop() -> void:
active = false
shake_left = 0
blood_left = 0
intensity = 0
if is_instance_valid(pivot):
pivot.transform = Transform3D.IDENTITY
overlay.hide()