OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_sfx_bus.gd

118 lines · gd ref:
extends SceneTree
## GATE — game sounds go through the SFX bus, whose low-pass filter takes the treble out
## (user 2026-10-06: "remove all the treble high notes from our sounds so they are
## mostly thudding sounding"). A bus effect, not re-encoded files: non-destructive,
## adjustable, and the files live in the overlay store.
##   S1  res://default_bus_layout.tres loads: bus "SFX" exists, sends to Master
##   S2  COZY, NOT MUFFLED (research 2026-10-06: harshness is the 2-5 kHz presence band
##       and the air above ~7 kHz; the 1-8 kHz texture is where the "crunch" lives, and
##       the old 700 Hz low-pass took it all): EQ6 cuts 3.2 kHz and 10 kHz, a high shelf
##       ~7 kHz takes ~6 dB off the air, a gentle compressor softens peaks — and no
##       low-pass below 2 kHz is left on the bus
##   S3  EVERY AudioStreamPlayer3D in both player scenes plays on SFX (a new sound added
##       on Master would skip the filter — this is what catches it)
##   S4  Master ends in a limiter at <= -1 dB (true-peak headroom)
##   S5  MICRO-VARIATION: the procedural player plays jump / land / light ONLY through
##       _play_varied (pitch ± semitones, volume ±1.5 dB, re-rolled per play) — no raw
##       .play() left on them; and every player sound has max_polyphony >= 2 (the
##       default 1 cuts an overlapping land off). (Not AudioStreamRandomizer: one on
##       Land crashed gate_step_up with unbounded recursion every run, cause not found.)
##   S6  COZY VARIANTS (Kenney CC0, assets/sounds/player/cozy): every pool file loads;
##       jump / land / footsteps play through _play_pool (a NO-REPEAT pick from 4-10
##       variants + the jitter); footsteps are live again (they were commented out as
##       "placeholder" — the metal steps); terrain vs hard surfaces pick their own pool
##
## Run: CI=true "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_sfx_bus.gd

var ok := true
var checks := 0


func _p(c: bool, m: String, got: Variant = "") -> void:
	checks += 1
	print(("ok   " if c else "FAIL: ") + m + ("" if str(got) == "" else "  (" + str(got) + ")"))
	if not c:
		ok = false


func _players(n: Node, out: Array) -> void:
	if n is AudioStreamPlayer3D or n is AudioStreamPlayer or n is AudioStreamPlayer2D:
		out.append(n)
	for c in n.get_children():
		_players(c, out)


func _init() -> void:
	var layout := load("res://default_bus_layout.tres") as AudioBusLayout
	_p(layout != null, "S1 res://default_bus_layout.tres loads")
	if layout != null:
		AudioServer.set_bus_layout(layout)
	var i := AudioServer.get_bus_index("SFX")
	_p(i > 0 and AudioServer.get_bus_send(i) == &"Master", "S1 bus SFX exists and sends to Master",
		"index %d send %s" % [i, AudioServer.get_bus_send(i) if i > 0 else "-"])
	var eq: AudioEffectEQ6 = null
	var shelf: AudioEffectHighShelfFilter = null
	var comp: AudioEffectCompressor = null
	var lowpass := ""
	for e in (AudioServer.get_bus_effect_count(i) if i > 0 else 0):
		var fx := AudioServer.get_bus_effect(i, e)
		if not AudioServer.is_bus_effect_enabled(i, e):
			continue
		if fx is AudioEffectEQ6: eq = fx
		elif fx is AudioEffectHighShelfFilter: shelf = fx
		elif fx is AudioEffectCompressor: comp = fx
		elif fx is AudioEffectLowPassFilter and (fx as AudioEffectLowPassFilter).cutoff_hz < 2000.0:
			lowpass = "%.0f Hz" % (fx as AudioEffectLowPassFilter).cutoff_hz
	_p(eq != null and eq.get_band_gain_db(4) <= -2.0 and eq.get_band_gain_db(5) <= -3.0,
		"S2 EQ6 cuts 3.2 kHz (<= -2 dB) and 10 kHz (<= -3 dB)",
		("%.1f / %.1f dB" % [eq.get_band_gain_db(4), eq.get_band_gain_db(5)]) if eq != null else "no EQ6")
	_p(shelf != null and shelf.cutoff_hz >= 5000.0 and shelf.cutoff_hz <= 9000.0 and shelf.gain <= 0.6,
		"S2 a high shelf at 5-9 kHz takes the air down (gain <= 0.6)",
		("%.0f Hz gain %.2f" % [shelf.cutoff_hz, shelf.gain]) if shelf != null else "no shelf")
	_p(comp != null and comp.ratio >= 1.5 and comp.ratio <= 4.0 and comp.threshold <= -12.0,
		"S2 a gentle compressor (ratio 1.5-4, threshold <= -12 dB)",
		("ratio %.1f thr %.0f" % [comp.ratio, comp.threshold]) if comp != null else "no compressor")
	_p(lowpass == "", "S2 no low-pass below 2 kHz left on SFX (it took the texture)", lowpass)
	var lim := false
	for e in AudioServer.get_bus_effect_count(0):
		var fx := AudioServer.get_bus_effect(0, e)
		if fx is AudioEffectHardLimiter and (fx as AudioEffectHardLimiter).ceiling_db <= -1.0:
			lim = true
	_p(lim, "S4 Master has a hard limiter at <= -1 dB")
	for path in ["res://scenes/player/player.tscn", "res://scenes/player/player_procedural.tscn"]:
		var inst := (load(path) as PackedScene).instantiate()
		var found: Array = []
		_players(inst, found)
		var off: Array = []
		for n in found:
			if String(n.bus) != "SFX":
				off.append("%s:%s" % [n.name, n.bus])
		_p(found.size() > 0 and off.is_empty(), "S3 every sound in %s plays on SFX (%d)" % [path.get_file(), found.size()], off)
		var mono: Array = []
		for n in found:
			if int(n.max_polyphony) < 2:
				mono.append(String(n.name))
		_p(mono.is_empty(), "S5 every sound in %s has max_polyphony >= 2" % path.get_file(), mono)
		inst.free()
	var src := FileAccess.get_file_as_string("res://scenes/player/player_procedural.gd")
	var raw: Array = []
	for snd in ["snd_jump", "snd_land", "snd_light"]:
		if src.contains(snd + ".play()"):
			raw.append(snd)
	_p(raw.is_empty() and src.contains("func _play_varied(") and src.contains("pitch_scale") and src.contains("randf_range(-1.5, 1.5)"),
		"S5 jump / land / light play only through _play_varied (pitch + volume jitter)", raw)
	var cozy := ["jump_0", "jump_1", "jump_2", "jump_3", "land_0", "land_1", "land_2", "land_3", "land_4"]
	for vi in 5: cozy.append("step_terrain_%d" % vi)
	for vi in 10: cozy.append("step_hard_%d" % vi)
	var unloaded: Array = []
	for nm: String in cozy:
		if not (load("res://assets/sounds/player/cozy/%s.ogg" % nm) is AudioStream):
			unloaded.append(nm)
	_p(unloaded.is_empty(), "S6 all %d cozy variants load" % cozy.size(), unloaded)
	_p(src.contains("func _play_pool(") and src.contains("_play_pool(snd_jump") and src.contains("_play_pool(snd_land")
			and src.contains("STEP_TERRAIN") and src.contains("STEP_HARD"),
		"S6 jump / land / footsteps play from variant pools (terrain and hard surfaces)")
	_p(not src.contains("# _footstep_sounds[randi()"), "S6 footsteps are live (no longer commented out)")
	print("\n%d checks — %s" % [checks, "PASS" if ok else "FAIL"])
	quit(0 if ok else 1)