Repository navigation
#474 stage 2: expose limit on assemble-and-score and in the series-episodes skill #497
Description
Stage 2 of #474. Section "Stage 2: expose it on assemble-and-score and in the series-episodes skill" of plan v2. The plan comment is authoritative; this is a restatement.
Filed by Claude Opus 5.5 (claude-opus-5-5, anthropic). Reshaped for plan v2 by the same model and provider.
Changed for plan v2
The v1 build is merged at 2122558; its template variable and skill lines passed (C-F136). It bounced only at C-F135: the limited mix holds −3.0 dBTP, but the AAC mux of the film measured −2.54 dBTP. Don approved v2 with Q6's default (a): the ceiling holds on the mix, not the muxed film. What this stage still owes:
- The template description (
workflows/templates/assemble-and-score.json) says where the ceiling holds: withlimit, −3 dBTP is held on the mix, and the AAC mux of the film can land up to about 1 dB above it on limited material. It stops promising −3 dBTP on the film. - Acceptance changes as below: the film is gated at ≤ −2.0 dBTP, and the −3.0 check moves to the
balancedstep's log.
Nothing else in the build moves. normalize_audio (stage 1, #496, verified) is not changed; no encode margin and no post-encode trim (Q6 b/c declined).
Builds (v1, already merged)
- A
limitvariable (defaultfalse) onworkflows/templates/assemble-and-score.json, passed to thebalancedstep beside Episodes can't be loudness-matched: assemble-and-score doesn't expose target_lufs; skill should match downward #467'starget_lufs.assemble-and-scoreonly (Q5 default, approved). plugins/dw/skills/series-episodes/SKILL.md(the normalize bullet and the loudness paragraph): match downward by default, as Episodes can't be loudness-matched: assemble-and-score doesn't expose target_lufs; skill should match downward #467 wrote it; passlimit: trueonly when the series should sit louder than its most dynamic episode allows (e.g. −16 for streaming); alimiter_heavywarning means lower the series target.- One line in
plugins/dw/skills/minimax-music3saying the same for a song master.
Owes
- The template test that pins
assemble-and-score's variables, plus one test thatlimitreaches thebalancedstep (merged). tests/test_plugin_skills.pyif it pins numbers in the skill text.- v2: the description change; if a test pins the description text, update it.
Deploy
Server (the template) and plugin (the skills).
Passes when
Its own repro works: assemble-and-score with target_lufs: -16, limit: true on a laugh-track episode lands within 0.5 LU of −16; the balanced step's log has output_true_peak_dbfs ≤ −3.0; the film's true_peak_dbfs ≤ −2.0 (mix ceiling plus a 1 dB encode allowance, still under −1 dBTP streaming ceilings); no audio_clipped; the template description no longer claims −3 dBTP on the film. And every pending: #<this issue> case the tester writes passes. The skill text is verified by reading the file at develop (no MCP surface); Don approves that line.
Blocked by
- Stage 1, #474 stage 1: normalize_audio(limit=true), a true-peak look-ahead limiter #496 (closed, verified).
- Episodes can't be loudness-matched: assemble-and-score doesn't expose target_lufs; skill should match downward #467 (closed, verified). Had it been declined, the plan's fallback was for this stage to add
target_lufsitself (+~$1).
Activity
dkackman commentedon Sep 27, 2026
Built by Claude Opus 5.5 (
claude-opus-5-5, anthropic).Shipped: branch
feat/474-b-assemble-limitmerged intodevelopas2122558. Deploy line:[deploy 07:34:02] deployed develop @ 2122558(healthy,0.5.0-alpha.1, mcp=true). The plugin tree followsorigin/develop.Plan items (stage 2 of plan v1):
limitvariable, defaultfalse, onworkflows/templates/assemble-and-score.json, passed tobalancedbesidetarget_lufs(withpeak_dbfs: -3.0unchanged). The template description now says what it does and when to pass it, and explainslimiter_heavyand the 12 dBtarget_lufs_capped/limited: true.assemble-and-scoreonly (Q5).plugins/dw/skills/series-episodes/SKILL.md, normalize bullet: match downward by default, as before. Passlimit: truebeside the same seriestarget_lufsonly when the series should sit louder than its most dynamic episode allows (e.g. -16 for streaming).limiter_heavymeans lower the series target. This replaces the "needs a limiter, which does not exist yet" line. The loudness paragraph under Run and judge now points at it.plugins/dw/skills/minimax-music3/SKILL.md, judging step 4: one line saying the same for a song master (limit: truefor -16, andlimiter_heavymeans lower the target). Don approves this line.tests/test_assemble_and_score_target_lufs.py:limitis declared asfalse;balanced, and the default leaves itfalse;balancedarguments tonormalize_audioon a bed with a +20 dB burst. Withoutlimitthe gain caps below -17 LUFS. With it the result is -16 ±0.5 LUFS, the true peak is ≤ -3 dBTP, and the length is unchanged.tests/test_plugin_skills.pypins no loudness numbers, so it needed no change. Full suite: 6818 passed; ruff is clean.Deviation: the minimax-music3 line is terse and I tightened the sentence before it, because that skill sits at the 12288-byte
SKILL_SIZE_LIMIT(now 12274). Nothing it says was dropped.No existing MCP shape changed: this adds a variable with a default that keeps today's result. Not
breaking-change.dkackman commentedon Sep 27, 2026
Verified by Claude Opus 5.5 (
claude-opus-5-5, anthropic) against lemdevelop @ 2122558. Bounced toowner:lead: C-F135 fails. C-F136 passes.C-F135 — FAIL on (c): the film's true peak goes over the ceiling after the mux
Step 1 passes.
list_workflows(shape="sequence")listslimitintemplates/assemble-and-score'svariable_names, andget_workflow(..., variables_only=true)shows"limit": false.validate_workflowis clean for all three argument sets, and (a) and (b) get the same plan fingerprint (sha256:427f0114…).I ran
run_workflow(name="templates/assemble-and-score", workspace="regression-complete", arguments={"shots": ["asset:qa-cast/ep11-coldopen.mp4"], "score": "asset:qa-cast/ep11-bed.wav", "total_frames": 472, "score_gain": 0.3, "world_gain": 1.0, "target_lufs": -16, …}, acknowledged_cost=<bound>, wait_seconds=55)three ways.get_gallery_metadataon eachfilm:balancedlogintegrated_lufstrue_peak_dbfstarget_lufs_capped(shortfall 3.45 LU)constraint: "peak_ceiling", gain −2.0 dBlimit: falselimit: trueconstraint: "limiter", gain +2.4 dB,max_gain_reduction_db4.46,limited_fraction0.162,output_true_peak_dbfs: -3.0,output_lufs: -16.0All three films are 472 frames at 24 fps, 960×544 and 19.666667 s. (a) and (b) are identical. (c) has no
audio_clippedand notarget_lufs_capped.The failure: the limiter holds −3.0 dBTP at the
balancedstep, but the muxed film measures −2.54 dBTP. The case allows at most −2.9, and the stage's own "Passes when" says ≤ −3.0. The unlimited runs lose nothing in the mux (−3.0 sample peak becomes −3.10 dBTP). The limited run gains about 0.46 dB, probably because a limited signal sits against the ceiling on about 16% of its samples, and lossy re-encoding overshoots dense near-ceiling material. The deliverable a caller gets is the muxed film, so the ceiling has to hold there. Ways to fix it (the lead's call):limit: true(e.g.peak_dbfsminus an encode margin);The template description should also stop promising −3 dBTP on the film until one of these is done.
C-F136 — PASS
I loaded both skills with
Skill(the plugin tree followsorigin/develop).dw:series-episodes, normalize bullet: matching downward is the default.limit: truegoes beside the same seriestarget_lufsonly when the series should sit louder than its most dynamic episode allows (the example given is −16 for streaming).limiter_heavymeans lower the series target. The loudness paragraph in Run and judge points back to this bullet. It namesassemble-and-score'slimit.dw:minimax-music3, judging step 4: "A master louder than its peak allows (-16 streaming): addlimit: true;limiter_heavymeans lower the target."limiton by default. Every variable and warning they name exists (limit,target_lufs,target_lufs_capped,limiter_heavy). Don still has to approve the minimax-music3 line, as the plan says.Cleanup
I deleted all three runs with
delete_output(job_id=…). Thepending: #497lines stay on C-F135 and C-F136 until the stage passes as a whole.dkackman commentedon Sep 27, 2026
Re-plan by Claude Opus 5.5 (
claude-opus-5-5, anthropic). What broke the plan: plan v1 assumed that a −3 dBTP ceiling held atbalancedstill holds on the muxed film. C-F135(c) shows it doesn't. The limiter lands the mix at −3.0 dBTP / −16.0 LUFS, and the AAC mux (encode_video, diffusers' default encoder settings) brings the film out at −2.54 dBTP. Dense material just under the ceiling (16% of samples limited) overshoots in the encoder, and unlimited material doesn't (−3.0 sample peak becomes −3.10 dBTP).Stage 2 can't fix this on its own terms:
limitis on (JSON has no conditionals), and lowering it always changes the defaults.normalize_audio(limit=true)changes what stage 1's verified argument holds.Each of these is a scope decision for Don, so I withdrew the approval on #474 and posted plan v2 there. Branch
feat/474-b-assemble-limitis already merged. Nothing new is unpushed.dkackman commentedon Sep 27, 2026
Reshaped for plan v2 of #474 by Claude Opus 5.5 (
claude-opus-5-5, anthropic). Don approved Q6's default (a): the −3 dBTP ceiling holds on the mix. This stage now also owes a template-description change saying that the AAC-muxed film can land up to about 1 dB above it. Acceptance moves the −3.0 check to thebalancedlog and gates the film at ≤ −2.0 dBTP with noaudio_clipped. The rest of the v1 build (merged at2122558) stands.dkackman commentedon Sep 27, 2026
Built by Claude Opus 5.5 (
claude-opus-5-5, anthropic). The v2 change for plan v2 of #474.Shipped: branch
feat/474-b-assemble-limit-v2merged intodevelopasb8afddf. Deploy line:[deploy 22:59:18] deployed develop @ b8afddf(healthy,0.6.0-alpha.1, mcp=true).Plan items (stage 2, v2):
workflows/templates/assemble-and-score.json): thelimitsentence now says where the ceiling holds. "That ceiling holds on the mix 'balanced' writes, not on the film: the AAC mux into the mp4 can land up to about 1 dB above it on limited material (a laugh-track episode limited to -3.0 dBTP came out of the mux at -2.54), still under the -1 dBTP ceiling of streaming delivery." Nothing in it promises -3 dBTP on the film any more. The earlier "always holds the peak at -3 dBFS" sentence is aboutbalancedand still stands.test_the_description_places_the_limit_ceiling_on_the_mix_not_the_filmtotests/test_assemble_and_score_target_lufs.py. Full suite: 6920 passed, 14 skipped, 1 xfailed. ruff check and format are clean.limitvariable, the skill lines, C-F136) is unchanged.normalize_audiois unchanged too, per Q6 (a).Deviations: none. No MCP shape changed, so this is not
breaking-change.For the re-run under the new acceptance: the −3.0 check now reads the
balancedstep's log (output_true_peak_dbfs). The film is gated attrue_peak_dbfs≤ −2.0 with noaudio_clipped. The bounced run (c) (77290dcf946c, −3.0 on the mix, −2.54 on the film) already satisfies those numbers.dkackman commentedon Sep 27, 2026
Verified by Claude Opus 5.5 (
claude-opus-5-5, anthropic) against lemdevelop @ b8afddf. Stage passes under plan v2. The counted cases are C-F150 (v2) and C-F136, and both pass. C-F135 was not counted: plan v2 changed its film check (≤ −2.9 dBTP) on purpose, and harnest#25 (open,suite+status:needs-approval) already asks to retire it in favour of C-F150. Itspending: #497line stays until that request is ruled on.C-F150: PASS
list_workflows(shape="sequence"):templates/assemble-and-score'svariable_namesincludelimitandtarget_lufs.get_workflow(variables_only=true)shows"limit": false. The fulldescriptionnow says: "That ceiling holds on the mix 'balanced' writes, not on the film: the AAC mux into the mp4 can land up to about 1 dB above it on limited material (… −2.54), still under the −1 dBTP ceiling of streaming delivery." Nothing in it promises −3 dBTP on the film. The template passeslimit: "variable:limit"tobalancedbesidetarget_lufsandpeak_dbfs: -3.0.validate_workflowis clean for all three argument sets. (a) and (b) share the fingerprintsha256:427f0114…, and (c) hassha256:3f9c3e72….run_workflow(name="templates/assemble-and-score", workspace="regression-complete", arguments={"shots": ["asset:qa-cast/ep11-coldopen.mp4"], "score": "asset:qa-cast/ep11-bed.wav", "total_frames": 472, "score_gain": 0.3, "world_gain": 1.0, "target_lufs": -16, …}, acknowledged_cost=<bound>, wait_seconds=55):balancedlogintegrated_lufstrue_peak_dbfstarget_lufs_capped(normalize_audio inbalanced, shortfall 3.45 LU)constraint: "peak_ceiling", gain −2.0 dBlimit: falselimit: trueconstraint: "limiter", gain +2.4 dB,max_gain_reduction_db4.46,limited_fraction0.1621,output_true_peak_dbfs−3.0,output_lufs−16.0peak_dbfs−3.109 on both.audio_clippedand notarget_lufs_capped. The encode overshoot is 0.46 dB (−3.0 on the mix, −2.537 on the film), which reproduces the v1 bounce's numbers exactly.delete_output(job_id=…).C-F136: PASS (re-run on
develop)dw:series-episodes, normalize bullet: matching downward is the default.limit: truegoes beside the sametarget_lufsonly when the series should sit louder than its most dynamic episode allows ("e.g. -16 for streaming").limiter_heavymeans lower the series target. The Run and judge loudness paragraph points back to that bullet.dw:minimax-music3, judging step 4: "A master louder than its peak allows (-16 streaming): addlimit: true;limiter_heavymeans lower the target."limiton by default. The variable and warning names match the template and the task.I removed
pending: #497from C-F136 and C-F150 inregression-suite-complete.md, which makes them ordinary cases. The skill wording is Don's to approve, per the stage body.feat(templates): dkackman#497 - say the limit ceiling holds on the mi…