Lesson 224: OBS MKV Gap Reencode Concat Receipt on BUILD_RECEIPT (2026)
Direct answer: When normalize + WAV concat pass audio checks but merged duration is short of fragment sum, file mkv_gap_reencode_receipt_v1.json after a +genpts MKV re-encode lane, set reencode_concat: true on playtest_vod_triage_receipt_v1.json, and promote playtest_vod_gap_recovery on BUILD_RECEIPT—after Lesson 220 batch discipline and distinct from Lesson 207 (full path tree).

Why this matters now (January 2027 post-fest VOD recovery)
January 2027 teams close the H2 fest hardening capstone and immediately batch October playtest VOD for Q1 issue triage. Discord repeats: “concat_ok was true on session 1, session 2 merge is two minutes short.” That is usually MKV timestamp gaps at Replay Buffer boundaries—not missing audio (zero-duration help) and not “pick per-clip Whisper” until gap recovery fails (Lesson 207).
The OBS MKV gap preflight is the ninety-second editor gate; this lesson is the BUILD_RECEIPT milestone that ops can grep on Thursday review. Planned blog #12 (ffmpeg decision tree) and help #5 (MKV concat fix) pair this row.
Beginner path (gap recovery night)
| Step | Action | Success check |
|---|---|---|
| 1 | Confirm normalize preflight GREEN | Audio on every fragment |
| 2 | Run duration delta probe (G2) | delta > 2 s → gap lane |
| 3 | Re-encode MKV with +genpts (G3) |
reencoded/ count = fragments |
| 4 | Re-normalize + concat (G4–G5) | Merge within 1 s of sum |
| 5 | File mkv_gap_reencode_receipt_v1.json |
gap_recovery_pass: true |
| 6 | Update BUILD_RECEIPT | playtest_vod_gap_recovery GREEN |
Time: ~20 min recovery + 15 min receipt—68 minutes first session with two facilitators.
Developer path (gates G1–G6)
| Gate | Check | Fail when |
|---|---|---|
| G1 | Normalize path documented | Skipped straight to genpts on video-only MKV |
| G2 | Duration delta measured | No duration_delta_seconds_before |
| G3 | +genpts re-encode complete |
Partial reencoded/ folder |
| G4 | WAV re-normalized from reencoded | Stale normalized/ from pre-gap MKV |
| G5 | Concat smoke + E4 parity | concat_ok true without duration check |
| G6 | BUILD_RECEIPT row | Ops runs batch Whisper without gap receipt |
G2 — Duration delta (copy-paste)
sum=0
for f in playtest-vod/fragments/*.mkv; do
d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f")
sum=$(echo "$sum + $d" | bc)
done
merged=$(ffprobe -v error -show_entries format=duration -of csv=p=0 playtest-vod/merged_playtest.wav)
echo "delta=$(echo "$sum - $merged" | bc)"
Gap lane when delta > 2 after honest WAV concat from N4.
mkv_gap_reencode_receipt_v1.json
{
"schema": "mkv_gap_reencode_receipt_v1",
"build_label": "playtest-vod-2027-01-08-facilitator-b",
"session_id": "session-2027-01-08-01",
"surface": "steam_playtest",
"fragments_count": 4,
"duration_delta_seconds_before": 38.6,
"duration_delta_seconds_after": 0.6,
"genpts_reencode": true,
"paired_receipts": {
"playtest_vod_triage": "release-evidence/playtest/vod/PLAYTEST_VOD_TRIAGE_RECEIPT.json",
"facilitator_vod_batch": "release-evidence/playtest/FACILITATOR_VOD_BATCH_RECEIPT.json"
},
"gates": {
"G1_normalize_green": "pass",
"G2_gap_detected": "pass",
"G3_genpts_reencode": "pass",
"G4_renormalize": "pass",
"G5_concat_smoke": "pass",
"G6_build_receipt": "pass"
},
"gap_recovery_pass": true
}
Pin under release-evidence/playtest/vod/MKV_GAP_REENCODE_RECEIPT.json.
Triage receipt extension
{
"schema": "playtest_vod_triage_receipt_v1",
"build_label": "playtest-vod-2027-01-08-facilitator-b",
"concat_ok": true,
"reencode_concat": true,
"mkv_gap_reencode_receipt_path": "release-evidence/playtest/vod/MKV_GAP_REENCODE_RECEIPT.json",
"whisper_batch_allowed": true
}
Batch rule: If Lesson 220 batch_manifest.csv lists multiple sessions, every session with gap recovery must link its own mkv_gap_reencode_receipt before whisper_batch_allowed on the batch aggregate.
BUILD_RECEIPT row (G6)
| Column | Pass when |
|---|---|
playtest_vod_gap_recovery |
mkv_gap_reencode_receipt_v1.json exists for build_label |
playtest_vod_triage |
reencode_concat matches gap receipt when used |
playtest_vod_batch |
No batch Whisper while any session gap receipt missing |
ALTER TABLE release_publish_gate ADD COLUMN IF NOT EXISTS
playtest_vod_gap_recovery_required BOOLEAN NOT NULL DEFAULT false;
CI verify_mkv_gap_reencode_v1 fails when duration_delta_seconds_before > 2 and no gap receipt path on triage JSON.
Proof table (Thursday review)
| Session | delta before | delta after | reencode_concat |
Batch OK? |
|---|---|---|---|---|
| fac-a-eve | 41.2 | 0.5 | true | yes |
| fac-b-eve | 0.1 | — | false | yes |
| fac-c-eve | 22.0 | (pending) | — | no |
Key takeaways
- Short merge ≠ no audio—run N-gates before G-gates.
+genptsre-encode is the default gap recovery—not the first concat attempt.mkv_gap_reencode_receiptis separate fromffmpeg_concat_decision_receipt(207).- Gap recovery is playtest triage lane—not trailer master exports.
- Guide G1–G6 must pass before filing this lesson’s receipt.
- Lesson 220 blocks batch Whisper until all sessions GREEN.
- Q1 capstone 229 will wire this receipt with Ren'Py, Wwise, review keys, and WebGL rows.
- Thursday BUILD_RECEIPT review should show
playtest_vod_gap_recoverywhen used.
Common mistakes
- Setting
concat_ok: truebefore G5 duration check after re-encode. - Re-encoding one fragment but concatting old normalized WAVs.
- Filing gap receipt without updating batch manifest session row.
- Using genpts on marketing trailer masters—wrong lane tag.
- Skipping link to 207 when gap recovery still fails after G5.
Troubleshooting
| Symptom | Lane |
|---|---|
| Still short after genpts | Lesson 207 per_clip_local |
| No audio stream | OBS zero-duration help |
| Batch blocked | Fix red session; refresh Lesson 220 manifest |
| ffprobe columns missing | OBS ffprobe concat_ok preflight |
Mini exercise (68 minutes)
- Seed three MKV fragments with intentional gap (stop/start Replay Buffer).
- Pass normalize; confirm G2 delta > 2 s.
- Run G3–G5; file
mkv_gap_reencode_receipt_v1.json. - Add row to BUILD_RECEIPT; link from triage receipt.
- Add second session without gap—build
batch_manifest.csv; confirm batch blocks until both receipts exist.
Continuity — Q1 2027 post-fest recovery (224–229)
| Lesson | Receipt focus |
|---|---|
| 224 (this) | MKV gap reencode on BUILD_RECEIPT |
| 225 | Ren'Py PO-hash language |
| 226 | Wwise DSP packaged |
| 227 | Review keys |
| 228 | FMOD WebGL fest |
| 229 | Q1 capstone |
Previous: Lesson 223 — H2 fest hardening capstone
Next: Lesson 225 — Ren'Py PO-hash language receipt.
FAQ
Same as Lesson 207?
207 documents all Whisper paths when concat fails; 224 promotes mkv_gap_reencode_receipt on BUILD_RECEIPT for the timestamp gap lane only.
Same as the OBS guide chapter?
Guide = ninety-second preflight; lesson = BUILD_RECEIPT + batch + CI gate.
Must every team hit this?
No—file receipt only when G2 detects gap; column stays n/a otherwise.
Public highlight reels?
Gap recovery is for triage audio; pair highlight clip consent preflight with Lesson 243 playtest clip consent receipt for marketing concat.
A two-minute-short merge without mkv_gap_reencode_receipt is not “close enough for Whisper”—it is a silent triage lie on BUILD_RECEIPT.