Lesson 200: Playtest VOD concat_ok Gate Before Whisper Batch Triage (2026)
Direct answer: Before you run Whisper on a playtest archive, prove the merged VOD is timeline-safe. This lesson adds a concat_ok boolean to playtest_vod_triage_receipt_v1.json and a publish gate playtest_vod_concat_blocked that stays red until ffprobe shows monotonic timestamps and stable audio on every Replay Buffer fragment.

Why this matters now (June 2026 playtest VOD stack)
Facilitators adopted OBS Replay Buffer clips for hour-one bug capture, but Whisper batch jobs exploded in June 2026 because teams skipped the merge step:
- Per-clip transcripts look fine while the concat demuxer drops segments or shifts timestamps.
- GitHub triage issues cite the wrong
build_labelminute because the merged file is shorter than wall clock. - Cloud Whisper APIs charge per minute on corrupt merges that should never have shipped.
This lesson is the course milestone for the same cluster as the OBS Replay Buffer evening pipeline, the 16-tool concat prep listicle, and the local Whisper VOD triage playbook—it wires concat_ok into live-ops gates instead of tribal ffmpeg knowledge.
Beginner path (30-minute smoke)
You will finish with: one folder of Replay Buffer .mkv files, one normalized .wav, and a receipt JSON where concat_ok": true.
| Step | Action | Success check |
|---|---|---|
| 1 | Lock OBS profile (Replay Buffer on, same sample rate) | Settings screenshot in release-evidence/playtest/obs_profile.txt |
| 2 | Record three short clips to fragments/ |
Three .mkv files, non-zero size |
| 3 | Run ffprobe table script (below) |
No N/A duration rows |
| 4 | Normalize each fragment to 48 kHz WAV | normalized/ has matching count |
| 5 | Concat with ffmpeg concat demuxer | merged_playtest.wav plays without skips |
| 6 | Write playtest_vod_triage_receipt_v1.json |
"concat_ok": true |
Time: about 30 minutes for first pass; 68 minutes if you also wire the SQL gate and CI job.
Developer path (gates + automation)
Gate map (C1–C6)
| Gate | Check | Fail closed when |
|---|---|---|
| C1 | Fragment count ≥ 1 | Empty fragments/ |
| C2 | ffprobe duration monotonic |
DTS regression between files |
| C3 | Audio stream sample rate uniform | Mixed 44.1 / 48 kHz without resample |
| C4 | Normalized WAV peak < −1 dBFS | Clipped normalize blowing LUFS later |
| C5 | Concat output duration ≈ sum of fragments ± 2s | Silent gap or duplicate segment |
| C6 | Receipt concat_ok true |
Any C1–C5 red |
playtest_vod_triage_receipt_v1.json (concat fields)
{
"schema": "playtest_vod_triage_receipt_v1",
"build_label": "fest_public_2026-07-12",
"surface": "steam_playtest",
"fragment_count": 3,
"ffprobe_table_sha256": "...",
"merged_wav_sha256": "...",
"concat_ok": true,
"whisper_batch_allowed": true,
"notes": "48 kHz mono normalize; genpts on concat"
}
Pin under release-evidence/playtest/vod/PLAYTEST_VOD_TRIAGE_RECEIPT.json.
Publish gate
ALTER TABLE release_publish_gate ADD COLUMN IF NOT EXISTS
playtest_vod_concat_blocked BOOLEAN NOT NULL DEFAULT false;
CREATE OR REPLACE FUNCTION enforce_playtest_vod_concat()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.playtest_vod_concat_blocked THEN
RAISE EXCEPTION 'playtest_vod_concat_blocked: concat_ok false on receipt';
END IF;
RETURN NEW;
END;
$$;
CI job verify_playtest_vod_concat_v1 reads the receipt and sets the column before Whisper workflows start.
ffprobe table (copy-paste)
for f in fragments/*.mkv; do
ffprobe -v error -show_entries format=duration:stream=codec_type,sample_rate \
-of csv=p=0 "$f" | paste - - | awk -v file="$f" '{print file","$0}'
done | tee release-evidence/playtest/ffprobe_fragments.csv
Red flags: missing audio stream, duration=N/A, sample rate hopping between files.
Normalize + concat sketch
mkdir -p normalized lists
for f in fragments/*.mkv; do
base=$(basename "$f" .mkv)
ffmpeg -y -i "$f" -ac 1 -ar 48000 "normalized/${base}.wav"
echo "file '../normalized/${base}.wav'" >> lists/concat.txt
done
ffmpeg -f concat -safe 0 -i lists/concat.txt -c copy merged_playtest.wav
If concat fails, add -fflags +genpts on a re-encode pass (document in receipt notes).
Wire into BUILD_RECEIPT row
Add a row to your BUILD_RECEIPT beginner pipeline (or team template):
| Column | Value when green |
|---|---|
playtest_vod |
merged |
concat_ok |
true |
whisper_batch |
pending |
Do not set whisper_batch=running when concat_ok is false—Lesson 201 triple-channel receipts assume honest timestamps.
Prerequisites
- OBS Replay Buffer enabled (see blog evening pipeline)
ffmpeg+ffprobeon PATH- Optional: local Whisper triage for downstream batch
Common mistakes
- Concatenating MKV directly without normalizing sample rates (Whisper hears speed-shift artifacts).
- Using cloud Whisper on per-clip files while issues reference merged timeline (misaligned quotes).
- Forgetting
build_labelon receipt (cannot correlate with playtest isolation playbook).
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Merged file shorter than expected | Non-monotonic DTS | Re-encode with +genpts |
| Whisper timestamps jump backward | Mixed fragment settings | Re-lock OBS profile |
concat_ok true but silent gaps |
Missing audio in one fragment | Re-record fragment; update table |
Mini exercise (45 minutes)
- Seed three synthetic fragments (or real Replay Buffer clips).
- Produce
ffprobe_fragments.csvand normalized WAVs. - Write receipt with
concat_ok: true. - Deliberately break C3 (export one fragment at 44.1 kHz); confirm gate blocks Whisper job.
- Link receipt path in facilitator README (pairs blog #11 contract template).
Continuity
- Lesson 199 — closed the Q1 2027 intake governance capstone.
- Next lesson: Lesson 201 — Triple-channel HTML5
channel_label_match. - Guides (planned): OBS normalize preflight (Guide queue #3).
- Help (planned): OBS MKV fragment concat fix (Help queue #5).
FAQ
Can we skip concat and run Whisper per clip?
Yes for debugging, but batch triage and facilitator summaries assume one timeline—set concat_ok: false and whisper_batch_allowed: false explicitly.
Does this replace the Whisper pipeline blog?
No—it adds a hard gate the blog references; keep using the blog for model choice and issue templates.
What if we use ShareX instead of OBS?
Same receipt fields; change fragment_source note—ffprobe rules still apply.
Whisper batch triage starts only after concat_ok is true—not when facilitators hope the merge worked.