#!/usr/bin/env bash # check-sfxmix.sh - Host-cc differential harness for the shared SFX # overlay mixer (src/core/audioSfxMix.c), built on the check-blank.sh / # check-millis.sh pattern. Compiles the CURRENT mixer TU with the host # cc plus tests/host/sfxMixHost.c and runs a fixed-seed scenario set # (fixed-buffer + streaming slots, rate up/down, multi-slot, mid-mix # refill and end-of-stream, amplitude 0/max, >64k read positions). # # First run captures tests/goldens/sfxmix-baseline.txt (capture it # against the CURRENT mixer (the go-forward reference)); later runs diff per scenario: # default exact mode: FNV-1a hash + raw bytes must match # the baseline (gate for findings #7 and #33, # which must be bit-identical). # JL_SFX_TOLERANCE=1 per-sample mode for finding #6: prints each # scenario's max-abs-delta and allows a deviation # of tolerance * slots per output sample (the # 1-LSB interp deviation stacks across # simultaneous slots before the clamp). # # The harness binary is also run twice and the outputs compared, so a # nondeterministic harness (or mixer reading uninitialized memory) # fails loudly instead of producing a flaky baseline. set -euo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) cc=${CC:-cc} work=$(mktemp -d -t joeylib-sfxmix.XXXXXX) trap 'rm -rf "$work"' EXIT baseline="$repo/tests/goldens/sfxmix-baseline.txt" cd "$repo" # BLANK platform: audioSfxMix.c is portable C with no jlp* hooks; the # stream fill callback is mocked inside sfxMixHost.c. "$cc" -DJOEYLIB_PLATFORM_BLANK \ -Iinclude -Iinclude/joey -Isrc/core -Wall \ src/core/audioSfxMix.c tests/host/sfxMixHost.c \ -o "$work/sfxMixHost" "$work/sfxMixHost" > "$work/out1.txt" "$work/sfxMixHost" > "$work/out2.txt" if ! cmp -s "$work/out1.txt" "$work/out2.txt"; then echo "check-sfxmix: FAIL - harness output differs across two runs (nondeterministic)" >&2 exit 1 fi if [ ! -f "$baseline" ]; then mkdir -p "$(dirname "$baseline")" cp "$work/out1.txt" "$baseline" count=$(grep -c '^name=' "$baseline") echo "check-sfxmix: baseline captured ($count scenarios) -> tests/goldens/sfxmix-baseline.txt" exit 0 fi tolerance="${JL_SFX_TOLERANCE:-}" if [ -n "$tolerance" ]; then echo "check-sfxmix: comparing against baseline (per-sample, tolerance ${tolerance} LSB * slots)" else echo "check-sfxmix: comparing against baseline (exact hash mode)" fi if python3 - "$baseline" "$work/out1.txt" "$tolerance" <<'PY' import sys def parse(path): order = [] scen = {} name = None with open(path) as f: for line in f: line = line.strip() if line.startswith("name="): fields = dict(p.split("=", 1) for p in line.split()) name = fields["name"] order.append(name) scen[name] = {"hash": fields["hash"], "slots": int(fields["slots"]), "raw": b""} elif line.startswith("raw=") and name is not None: scen[name]["raw"] = bytes.fromhex(line[4:]) return order, scen baseOrder, base = parse(sys.argv[1]) curOrder, cur = parse(sys.argv[2]) tolerance = int(sys.argv[3]) if sys.argv[3] else None fail = False if curOrder != baseOrder: print("check-sfxmix: scenario list changed vs baseline") print(" baseline: " + " ".join(baseOrder)) print(" current: " + " ".join(curOrder)) fail = True for name in baseOrder: if name not in cur: continue b = base[name] c = cur[name] label = " %-24s" % (name + ":") if len(b["raw"]) != len(c["raw"]): print("%s FAIL (length %d -> %d)" % (label, len(b["raw"]), len(c["raw"]))) fail = True continue if tolerance is None: ok = (b["hash"] == c["hash"]) and (b["raw"] == c["raw"]) if ok: print("%s PASS (hash %s)" % (label, c["hash"])) else: print("%s FAIL (hash %s -> %s)" % (label, b["hash"], c["hash"])) fail = True else: maxDelta = 0 for x, y in zip(b["raw"], c["raw"]): d = abs(x - y) if d > maxDelta: maxDelta = d allowed = tolerance * b["slots"] ok = maxDelta <= allowed print("%s maxAbsDelta=%d (allowed %d) %s" % (label, maxDelta, allowed, "PASS" if ok else "FAIL")) fail = fail or not ok sys.exit(1 if fail else 0) PY then echo "check-sfxmix: PASS" else echo "check-sfxmix: FAIL (mixer output diverged from tests/goldens/sfxmix-baseline.txt)" >&2 exit 1 fi