diff --git a/docs/audio-doctrine.md b/docs/audio-doctrine.md index e461cf3..da5dc43 100644 --- a/docs/audio-doctrine.md +++ b/docs/audio-doctrine.md @@ -366,6 +366,15 @@ banks through `layer`, `harmonics`, and `spectrum`. Simple oscillator blips without one of these owners are chip-distress-risk contrast material, not a strong lesson for the music generator. +Song-scene curriculum admission is producer apprenticeship, not syntax +attendance. A candidate that names `meter`, `sequence`, `kick`, `hat`, or +`texture` has not learned music until it leaves a producer brief, listening +journal, Aqua gap ledger, and compact studio lesson tying those choices to the +reference. Distillation should promote only rendered candidates with audible +form, target-specific roles, and low template/noise-percussion risk. Stock +four-lane loops and raw noise percussion belong in failure documents even when +they keep RMS and motion metrics nonzero. + Do not confuse high metric agreement with musical success. The game context, the reference sound, and the user’s ear still outrank the spreadsheet. Annoying, but civilization has survived worse. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index dfedb5b..b226941 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -263,3 +263,4 @@ {"date":"2026-05-31","area":"song-corpus-three-pass-curriculum","summary":"A three-pass 10-second corpus curriculum run across D:\\Music\\Reinier completed with 25 official assignments per pass. Pass 1 produced 25 official candidates, 23 rendered, 2 render failures, best cosine 0.299361, mean cosine 0.162788, mean score 0.198594, and 34 promoted music knowledge records. Pass 2 produced 25 official candidates, 20 rendered, 5 render failures, best cosine 0.351068, mean cosine 0.137059, mean score 0.170592, and 56 promoted music knowledge records. Pass 3 produced 25 official candidates, 15 rendered, 10 render failures, best cosine 0.304571, mean cosine 0.115347, mean score 0.128224, and 58 promoted music knowledge records. The learned signal is mixed: knowledge injection increased structured artifacts and reusable abstraction ledgers, but later-pass render failure and score degradation must be treated as quality pressure, not progress.","verification":"pass-001/pass-002/pass-003 each have 25 challenge.json files, 25 official candidates, 25 official summary.csv files, top-scoring-candidates.m3u, distillation-report.md, semantic-search-after.md, vector-index-after.md, music-production-knowledge.cc, music-production-knowledge-report.md, and music-production-quality-standard.md. Pass 3 was manually finished after external Codex jobs failed by promoting existing attempt-05 patches and running official scoring/promotion with IpaTrialWorker.exe.","evidence":"artifacts/parity/song-snippet-swarms/20260531T171802519/pass-001/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-002/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/music-production-knowledge.cc; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/top-scoring-candidates.m3u; artifacts/parity/song-snippet-swarms/20260531T171802519/pass-003/distillation-report.md"} {"date":"2026-05-31","area":"song-mode-collapse-evaluator","summary":"The corpus run exposed a scoring blind spot: agents could write a short opening phrase and coast for the remaining clip on low-motion texture/noise while still receiving weak-but-ranked evidence. `song-score` now emits song-continuity metrics for active coverage, motion coverage, first-second energy share, tail energy share, and mode-collapse risk; high collapse risk changes verdict to `weak-mode-collapse`, lowers distillation ranking, and is carried into music knowledge failure modes. Swarm prompts now require later-section motifs/events and explicitly reject one-second riff plus nine seconds of texture as submitted song evidence.","verification":"dotnet build E:\\Projects\\AquaSynth\\tools\\IpaTrialWorker\\IpaTrialWorker.csproj --no-restore: passed; dotnet test E:\\Projects\\AquaSynth\\tests\\AquaSynth.Dsl.Tests\\AquaSynth.Dsl.Tests.csproj --no-restore --filter CultCache: 7 passed; rescoring pass-003 agent_02__retrievedmesh__target_04_a05 produced verdict weak-mode-collapse, candidate_first_second_energy_share 0.883627, candidate_motion_coverage 0.573295, mode_collapse_risk 1","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; artifacts/parity/song-continuity-smoke/20260531Tmodecollapse-v2/continuity-smoke/summary.csv"} {"timestamp":"2026-05-31T22:25:00+01:00","topic":"composition-curriculum-redirect","claim":"The song CultCache run learned transferable sound-production role owners, but not composition. Next passes should require meter, progression or tonal center, note-degree sampling, instrument-lane gates, section events, composition-scale automation, and mix movement. AquaSynth now has small Strudel-ish sugar for `meter`, `sequence`, `chords`, and `mix`, all lowering to visible `param`/`curve` owners alongside existing `pattern` and `scale`.","evidence":"Trawl of pass-003 music-production quality standard, distillation report, and abstraction ledgers showed 56 of 75 official candidates had mode-collapse risk while ledgers repeatedly converged on role owners and sugar candidates. Added parser/test coverage for composition-scale sugar and redirected the song swarm prompt toward Composition Map evidence."} +{"timestamp":"2026-05-31T23:15:00+01:00","topic":"producer-apprenticeship-curriculum-gate","claim":"Song curriculum admission must be owned by producer evidence and audible structure, not syntax attendance. `song-score` now records studio document coverage, producer musicianship, composition section score, template-loop risk, noise-percussion risk, and Aqua gap count; verdicts can fail as `weak-producer-missing` or `weak-slop-template`. `music-distill` promotes candidate and role doctrine only from rendered rows that clear the producer gate, while slop/noise/template rows become failure pressure.","evidence":"tools/IpaTrialWorker/Program.cs; tools/run-song-snippet-trial-swarm.ps1; docs/audio-doctrine.md; state/scratch.md"} diff --git a/state/scratch.md b/state/scratch.md index 9dc3909..e5463ef 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -1932,3 +1932,23 @@ Next likely slice: - Verification: CultLib `BackingStoreTests` pass 14/14, including a cold-record timestamp test; AquaSynth `dotnet build tools/IpaTrialWorker/IpaTrialWorker.csproj --no-restore` passes; AquaSynth `--filter CultCache` tests pass 7/7. + +2026-05-31 producer-apprenticeship admission slice: + +- User listening exposed a second owner fault after composition sugar: agents + could still converge on the same voiced burst plus static/noisy weak + percussion because syntax, activity, and broad role names were still enough to + become ranked curriculum pressure. +- `song-score` now analyzes a production profile beside the instrument and + continuity profiles: required studio document coverage, producer + musicianship score, composition section score, template loop risk, + noise-percussion risk, and Aqua gap count. +- `music-distill` now promotes candidate-pattern and role doctrine only from + rendered rows that clear the producer/curriculum gate. Missing producer + evidence, high template-loop risk, high noise-percussion risk, and weak-slop + verdicts become failure-mode documents instead of reusable examples. +- `run-song-snippet-trial-swarm.ps1` is redirected from parity-worker posture + to producer apprenticeship: each agent must write `producer-brief.md`, + `listening-journal.md`, `aqua-gap-ledger.md`, and `studio-lesson.md`, and use + the self-iteration loop as a listening/revision record rather than a metric + casino. diff --git a/tools/IpaTrialWorker/Program.cs b/tools/IpaTrialWorker/Program.cs index 7837e26..5d8b76b 100644 --- a/tools/IpaTrialWorker/Program.cs +++ b/tools/IpaTrialWorker/Program.cs @@ -235,6 +235,7 @@ static async Task SongScoreAsync(Dictionary options) var candidateWav = Path.Combine(audioDir, "candidate.wav"); var metrics = new List(); var instrumentProfile = AnalyzeSongInstrumentProfile(script); + var productionProfile = AnalyzeSongProductionProfile(script, scriptCopy, artifactRoot); var artifacts = new List { Artifact("song-challenge", challengePath), @@ -244,6 +245,7 @@ static async Task SongScoreAsync(Dictionary options) }; metrics.AddRange(SongTargetMetrics(challenge, challengeEvidence)); metrics.AddRange(SongInstrumentMetrics(instrumentProfile)); + metrics.AddRange(SongProductionMetrics(productionProfile)); artifacts.AddRange(SongChallengeArtifacts(challenge)); artifacts.AddRange(challengeEvidence.Select(SongChallengeEvidenceArtifact)); var timelineFacts = Array.Empty(); @@ -290,10 +292,11 @@ static async Task SongScoreAsync(Dictionary options) var continuity = AnalyzeSongContinuity(comparison); metrics.AddRange(SongComparisonMetrics(comparison)); metrics.AddRange(SongContinuityMetrics(continuity)); - verdict = SongVerdict(comparison, instrumentProfile, continuity); - evaluation = SongEvaluationSentence(challenge, comparison, verdict, instrumentProfile, continuity); + verdict = SongVerdict(comparison, instrumentProfile, productionProfile, continuity); + evaluation = SongEvaluationSentence(challenge, comparison, verdict, instrumentProfile, continuity) + + $" Production profile: {productionProfile.Summary}."; var comparisonPath = Path.Combine(audioDir, "comparison.txt"); - await File.WriteAllTextAsync(comparisonPath, SongComparisonReport(challenge, candidate, comparison, verdict, instrumentProfile, continuity), Encoding.UTF8); + await File.WriteAllTextAsync(comparisonPath, SongComparisonReport(challenge, candidate, comparison, verdict, instrumentProfile, continuity) + Environment.NewLine + $"productionSummary={productionProfile.Summary}" + Environment.NewLine, Encoding.UTF8); artifacts.Add(Artifact("comparison-report", comparisonPath)); } } @@ -335,7 +338,10 @@ static async Task SongScoreAsync(Dictionary options) "Full-patch FM/AM/noise/scene modeling is allowed; primitive timeline facts remain diagnostic when present.", instrumentProfile.ChipDistressRisk >= .6f ? "Instrument profile is chip-distress-risky: simple oscillator/noise roles are not enough curriculum evidence for the music generator." - : "Instrument profile uses at least some owned musical role evidence instead of only simple oscillator distress." + : "Instrument profile uses at least some owned musical role evidence instead of only simple oscillator distress.", + productionProfile.MusicianshipScore < .45f + ? "Production profile is weak: the agent did not leave enough producer brief, listening journal, gap ledger, section form, or anti-template evidence to teach future runs." + : "Production profile includes studio evidence and arrangement/form pressure, not just syntax tokens." ], [ new SpeechTimingReceipt( @@ -1686,8 +1692,11 @@ static IEnumerable BuildMusicKnowledgeDocument .Where(row => !row.Verdict.Equals("render-failed", StringComparison.OrdinalIgnoreCase) && row.AudioScore > 0) .OrderByDescending(row => MusicSignalScore(row)) .ToArray(); + var admissible = rendered.Where(IsMusicCurriculumCandidate).ToArray(); var failed = rows.Where(row => row.Verdict.Equals("render-failed", StringComparison.OrdinalIgnoreCase)).ToArray(); var collapsed = rows.Where(row => row.ModeCollapseRisk >= .45f).ToArray(); + var slopRejected = rows.Where(row => row.TemplateLoopRisk >= .55f || row.NoisePercussionRisk >= .55f).ToArray(); + var missingProducerEvidence = rows.Where(row => row.RequiredStudioDocsPresent < .5f || row.ProducerMusicianshipScore < .45f).ToArray(); yield return new MusicProductionKnowledgeDocument( "music-production-quality-standard-v1", @@ -1697,10 +1706,11 @@ static IEnumerable BuildMusicKnowledgeDocument "The music curriculum store owns distilled production knowledge; raw trials and giant feature dumps are only evidence, not curriculum.", "Fresh agents should retrieve role owners, transfer rules, failure modes, and AquaSynth patterns before writing patches. New runs must add compact, evidence-backed documents, not whole spectrogram dumps or undigested trial chatter.", [ - "Admit a candidate as reusable knowledge only when it renders, has explicit instrument-role ownership, and states what production job it owns.", + "Admit a candidate as reusable knowledge only when it renders, has explicit instrument-role ownership, includes producer/listening/gap evidence, and states what production job it owns.", "Reject one-phrase-plus-texture arrangements as failure pressure; continuity across the clip is part of the production contract.", "Require an explicit composition map: meter, progression or tonal center, instrument lanes, section events, composition-scale automation, and mix movement.", "Prefer compact role abstractions over one-off target mimicry; a reusable owner sentence is more valuable than a small metric bump.", + "Reject stock loop skeletons and raw-noise percussion as failure pressure unless the listening journal proves target-specific role behavior.", "Keep failed renders as failure-mode pressure only; do not let syntax wreckage dominate retrieval.", "Store full artifacts on disk and cite them; CultCache curriculum records should carry bounded summaries, metrics, and transfer rules." ], @@ -1718,17 +1728,20 @@ static IEnumerable BuildMusicKnowledgeDocument "Instrument timbre without phrase, progression, lane entrances, and mix movement is sound design, not composition." ], rows.Select(row => row.TrialId).Take(24).ToArray(), - rendered.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(24).ToArray(), + admissible.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(24).ToArray(), [ new SpeechScoreMetric("input_trial_count", rows.Length, 0), new SpeechScoreMetric("rendered_trial_count", rendered.Length, 0), + new SpeechScoreMetric("admissible_curriculum_candidate_count", admissible.Length, 0), new SpeechScoreMetric("render_failed_count", failed.Length, 0), - new SpeechScoreMetric("mode_collapsed_count", collapsed.Length, 0) + new SpeechScoreMetric("mode_collapsed_count", collapsed.Length, 0), + new SpeechScoreMetric("slop_template_rejected_count", slopRejected.Length, 0), + new SpeechScoreMetric("missing_producer_evidence_count", missingProducerEvidence.Length, 0) ], [artifactRoot], created); - foreach (var role in MusicRoleDocuments(rendered, created)) + foreach (var role in MusicRoleDocuments(admissible, created)) { yield return role; } @@ -1738,7 +1751,7 @@ static IEnumerable BuildMusicKnowledgeDocument yield return document; } - foreach (var row in rendered.Take(maxCandidates)) + foreach (var row in admissible.Take(maxCandidates)) { var patchPath = CandidatePatchPath(row); var analysisPath = CandidateAnalysisPath(row); @@ -1773,7 +1786,7 @@ static IEnumerable MusicRoleDocuments(IReadOnl yield return RoleDocument( "music-role-composition-form", "composition form, lanes, and mix motion", - "The composition role owns meter, chord/progression movement, note sampling, instrument-lane gates, section events, automation, and mix motion so a ten-second clip behaves like arranged music rather than a timbre demo.", + "The composition role owns meter, chord/progression movement, note sampling, instrument-lane gates, section events, automation, and mix motion so a target behaves like arranged music rather than a timbre demo.", [ "Start every song candidate with `meter` and at least one progression or tonal-center declaration.", "Use `sequence`/`pattern` for lane gates and `scale`/`chords` for pitch material before hand-writing isolated frequencies.", @@ -1894,39 +1907,65 @@ static IEnumerable MusicFailureDocuments( [artifactRoot], created); - if (collapsed.Length == 0) + if (collapsed.Length > 0) { - yield break; + yield return new MusicProductionKnowledgeDocument( + "music-failure-mode-collapse-pressure", + "failure-mode", + "one-phrase-plus-texture candidates are not song exemplars", + "failure-pressure", + "The mode-collapse failure record owns evidence that a rendered candidate can satisfy static audio metrics while failing song continuity.", + $"This run produced {collapsed.Length} official candidates with mode-collapse risk >= 0.45. These candidates transfer only as negative pressure unless their abstractions explicitly add later motifs, section changes, or eventful motion.", + [ + "Do not promote candidates that spend most musical energy in the first second and coast on low-motion texture.", + "Require motif mutations or distinct musical events across the target duration, with checkpoints scaled to the assigned clip length.", + "Use motion coverage and first-second energy share beside cosine; similarity to a moment is not similarity to the song." + ], + ["mode_collapse_risk", "candidate_motion_coverage", "candidate_first_second_energy_share", "motif mutation", "section event"], + [ + "front-loaded musical energy", + "low-motion pink-noise or pad tail", + "high cosine that copies only the opening phrase" + ], + collapsed.Select(row => row.TrialId).Take(20).ToArray(), + collapsed.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(20).ToArray(), + [ + new SpeechScoreMetric("mode_collapsed_count", collapsed.Length, 0), + new SpeechScoreMetric("input_trial_count", all.Count, 0), + new SpeechScoreMetric("max_mode_collapse_risk", collapsed.Select(row => row.ModeCollapseRisk).DefaultIfEmpty(0).Max(), 0), + new SpeechScoreMetric("mean_first_second_energy_share", collapsed.Select(row => row.CandidateFirstSecondEnergyShare).DefaultIfEmpty(0).Average(), 0) + ], + ExistingPaths(collapsed.Select(row => row.SummaryPath).Concat([artifactRoot]).Take(24)), + created); } - yield return new MusicProductionKnowledgeDocument( - "music-failure-mode-collapse-pressure", - "failure-mode", - "one-phrase-plus-texture candidates are not song exemplars", - "failure-pressure", - "The mode-collapse failure record owns evidence that a rendered candidate can satisfy static audio metrics while failing song continuity.", - $"This run produced {collapsed.Length} official candidates with mode-collapse risk >= 0.45. These candidates transfer only as negative pressure unless their abstractions explicitly add later motifs, section changes, or eventful motion.", - [ - "Do not promote candidates that spend most musical energy in the first second and coast on low-motion texture.", - "Require motif mutations or distinct musical events after seconds 2, 4, 6, and 8 for ten-second curriculum clips.", - "Use motion coverage and first-second energy share beside cosine; similarity to a moment is not similarity to the song." - ], - ["mode_collapse_risk", "candidate_motion_coverage", "candidate_first_second_energy_share", "motif mutation", "section event"], - [ - "front-loaded musical energy", - "low-motion pink-noise or pad tail", - "high cosine that copies only the opening phrase" - ], - collapsed.Select(row => row.TrialId).Take(20).ToArray(), - collapsed.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(20).ToArray(), - [ - new SpeechScoreMetric("mode_collapsed_count", collapsed.Length, 0), - new SpeechScoreMetric("input_trial_count", all.Count, 0), - new SpeechScoreMetric("max_mode_collapse_risk", collapsed.Select(row => row.ModeCollapseRisk).DefaultIfEmpty(0).Max(), 0), - new SpeechScoreMetric("mean_first_second_energy_share", collapsed.Select(row => row.CandidateFirstSecondEnergyShare).DefaultIfEmpty(0).Average(), 0) - ], - ExistingPaths(collapsed.Select(row => row.SummaryPath).Concat([artifactRoot]).Take(24)), - created); + var slop = all.Where(row => row.TemplateLoopRisk >= .55f || row.NoisePercussionRisk >= .55f || row.Verdict.Equals("weak-slop-template", StringComparison.OrdinalIgnoreCase)).ToArray(); + if (slop.Length > 0) + { + yield return new MusicProductionKnowledgeDocument( + "music-failure-template-noise-slop", + "failure-mode", + "stock loops and noise percussion are not production knowledge", + "failure-pressure", + "Template/noise failures own the lesson that naming kick, snare, hat, dust, or texture does not prove the patch learned the reference artist.", + $"This run produced {slop.Length} candidates with high template_loop_risk, high noise_percussion_risk, or weak-slop-template verdicts. Keep them as negative pressure against the voiced-burst-plus-static-noise attractor.", + [ + "A drum lane needs a pitched/body owner, a filtered skin owner, a gate, and target-specific timing evidence.", + "A texture lane needs band limits and musical motion; a full-duration noise wash is not a mix.", + "A reusable pattern must cite the producer brief or listening journal that proves why it belongs to this reference." + ], + ["template_loop_risk", "noise_percussion_risk", "producer_musicianship_score", "required_studio_docs_present"], + ["four-on-floor skeleton copied across targets", "raw noise pretending to be hats or room", "syntax sugar mined from unlistened patches"], + slop.Select(row => row.TrialId).Take(20).ToArray(), + slop.Select(row => row.CandidateId).Distinct(StringComparer.OrdinalIgnoreCase).Take(20).ToArray(), + [ + new SpeechScoreMetric("slop_candidate_count", slop.Length, 0), + new SpeechScoreMetric("worst_template_loop_risk", slop.Select(row => row.TemplateLoopRisk).DefaultIfEmpty(0).Max(), 0), + new SpeechScoreMetric("worst_noise_percussion_risk", slop.Select(row => row.NoisePercussionRisk).DefaultIfEmpty(0).Max(), 0) + ], + ExistingPaths(slop.Select(row => row.SummaryPath).Concat([artifactRoot]).Take(24)), + created); + } } static IEnumerable MusicAbstractionLedgerDocuments(string artifactRoot, string created) @@ -2136,6 +2175,13 @@ static IEnumerable ReadSongSummaryRows(string path) Float("instrument_voice_syrinx"), Float("instrument_drum_subtractive"), Float("instrument_pad_additive"), + Float("producer_musicianship_score"), + Float("required_studio_docs_present"), + Float("required_studio_doc_coverage"), + Float("template_loop_risk"), + Float("noise_percussion_risk"), + Float("composition_section_score"), + Float("aqua_gap_count"), Float("candidate_active_coverage", 1), Float("active_coverage_ratio", 1), Float("candidate_motion_coverage", .75f), @@ -2153,11 +2199,23 @@ static float MusicSignalScore(SongSummaryRow row) => row.LogMelCosine * 2.5f + row.AudioScore + row.MusicalInstrumentScore * .35f + + row.ProducerMusicianshipScore * .75f - row.ChipDistressRisk * .5f - row.ModeCollapseRisk * .9f + - row.TemplateLoopRisk * .8f + - row.NoisePercussionRisk * .8f + - (row.RequiredStudioDocsPresent < .5f ? .35f : 0) - MathF.Max(0, .55f - row.CandidateMotionCoverage) * .35f - (row.Verdict.Equals("render-failed", StringComparison.OrdinalIgnoreCase) ? 100f : 0f); +static bool IsMusicCurriculumCandidate(SongSummaryRow row) => + !row.Verdict.Equals("render-failed", StringComparison.OrdinalIgnoreCase) && + !row.Verdict.StartsWith("weak-", StringComparison.OrdinalIgnoreCase) && + row.ModeCollapseRisk < .45f && + row.TemplateLoopRisk < .55f && + row.NoisePercussionRisk < .55f && + row.ProducerMusicianshipScore >= .45f; + static bool IsOfficialMusicArtifactPath(string path) { var segments = Path.GetFullPath(path).Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar], StringSplitOptions.RemoveEmptyEntries); @@ -2186,6 +2244,13 @@ static SpeechScoreMetric[] RowMetrics(SongSummaryRow row) => new("articulation_score", row.ArticulationScore, 1), new("musical_instrument_score", row.MusicalInstrumentScore, 1), new("chip_distress_risk", row.ChipDistressRisk, 1), + new("producer_musicianship_score", row.ProducerMusicianshipScore, 1), + new("required_studio_docs_present", row.RequiredStudioDocsPresent, 1), + new("required_studio_doc_coverage", row.RequiredStudioDocCoverage, 1), + new("template_loop_risk", row.TemplateLoopRisk, 1), + new("noise_percussion_risk", row.NoisePercussionRisk, 1), + new("composition_section_score", row.CompositionSectionScore, 1), + new("aqua_gap_count", row.AquaGapCount, 1), new("candidate_active_coverage", row.CandidateActiveCoverage, 1), new("active_coverage_ratio", row.ActiveCoverageRatio, 1), new("candidate_motion_coverage", row.CandidateMotionCoverage, 1), @@ -2201,7 +2266,7 @@ static SpeechScoreMetric[] RowMetrics(SongSummaryRow row) => ]; static string CandidateKnowledgeSummary(SongSummaryRow row, SongInstrumentProfile profile, string analysisExcerpt) => - $"Rendered candidate `{row.CandidateId}` against `{row.ReferenceId}` with cosine {row.LogMelCosine:0.######}, score {row.AudioScore:0.######}, instrument score {row.MusicalInstrumentScore:0.###}, chip risk {row.ChipDistressRisk:0.###}, motion coverage {row.CandidateMotionCoverage:0.###}, first-second energy share {row.CandidateFirstSecondEnergyShare:0.###}, collapse risk {row.ModeCollapseRisk:0.###}. Instrument profile: {profile.Summary}. Candidate analysis excerpt: {TrimForReport(analysisExcerpt, 900)}"; + $"Rendered candidate `{row.CandidateId}` against `{row.ReferenceId}` with cosine {row.LogMelCosine:0.######}, score {row.AudioScore:0.######}, instrument score {row.MusicalInstrumentScore:0.###}, producer musicianship {row.ProducerMusicianshipScore:0.###}, studio doc coverage {row.RequiredStudioDocCoverage:0.###}, chip risk {row.ChipDistressRisk:0.###}, template risk {row.TemplateLoopRisk:0.###}, noise-percussion risk {row.NoisePercussionRisk:0.###}, motion coverage {row.CandidateMotionCoverage:0.###}, first-second energy share {row.CandidateFirstSecondEnergyShare:0.###}, collapse risk {row.ModeCollapseRisk:0.###}. Instrument profile: {profile.Summary}. Candidate analysis excerpt: {TrimForReport(analysisExcerpt, 900)}"; static string[] CandidateTransferRules(SongSummaryRow row, SongInstrumentProfile profile) { @@ -2210,8 +2275,11 @@ static string[] CandidateTransferRules(SongSummaryRow row, SongInstrumentProfile if (profile.HasSubtractiveDrums) rules.Add("Transfer the drum role as separate pitched body and filtered-noise skin gates."); if (profile.HasAdditivePad) rules.Add("Transfer the bed role as additive/PAD harmonic material with slow control motion."); if (profile.HasTexture) rules.Add("Transfer texture as a shaped role with band limits, gates, and motion."); + if (row.RequiredStudioDocsPresent >= .5f) rules.Add("Transfer only the lesson tied to this candidate's producer brief, listening journal, gap ledger, and studio lesson."); if (row.RmsRatio < .35f) rules.Add("Candidate is quiet against the reference; normalize loudness after role selection."); if (row.ModeCollapseRisk >= .45f) rules.Add("Do not transfer the arrangement as-is: it concentrates too much musical action near the opening and must be rewritten with distributed sections."); + if (row.TemplateLoopRisk >= .55f) rules.Add("Do not transfer the stock loop skeleton; rewrite the arrangement form before mining syntax sugar."); + if (row.NoisePercussionRisk >= .55f) rules.Add("Do not transfer the noise-percussion layer unless it is rebuilt as body plus filtered skin with evidence."); if (rules.Count == 0) rules.Add("Use as weak pressure only; demand a clearer role owner before promoting this pattern."); return rules.ToArray(); } @@ -2231,9 +2299,13 @@ static string[] CandidateFailureModes(SongSummaryRow row) if (row.RmsRatio < .35f) modes.Add("under-loud candidate can hide arrangement ideas behind weak level"); if (row.ZeroCrossingRatio > 8f) modes.Add("excess zero-crossing ratio suggests noisy or too-bright material"); if (row.ChipDistressRisk > 0) modes.Add("chip-distress risk must be justified as deliberate style before transfer"); + if (row.RequiredStudioDocsPresent < .5f) modes.Add("missing producer apprenticeship evidence; syntax alone cannot become curriculum"); + if (row.ProducerMusicianshipScore < .45f) modes.Add("low producer musicianship score; candidate did not carry enough arrangement, listening, and gap evidence"); + if (row.TemplateLoopRisk >= .55f) modes.Add("template loop risk: stock drum/lane skeleton is masquerading as composition"); + if (row.NoisePercussionRisk >= .55f) modes.Add("noise-percussion risk: raw or under-shaped noise is carrying the drum job"); if (row.ModeCollapseRisk >= .45f) modes.Add("mode collapse: short opening phrase followed by low-motion texture or noise"); if (row.CandidateFirstSecondEnergyShare >= .35f) modes.Add("front-loaded energy; require later motifs or section changes before promotion"); - if (row.CandidateMotionCoverage > 0 && row.CandidateMotionCoverage < .45f) modes.Add("low musical motion coverage across the ten-second target"); + if (row.CandidateMotionCoverage > 0 && row.CandidateMotionCoverage < .45f) modes.Add("low musical motion coverage across the target duration"); return modes.Count == 0 ? ["still pressure, not mastered parity"] : modes.ToArray(); } @@ -3480,6 +3552,17 @@ static IReadOnlyList SongInstrumentMetrics(SongInstrumentProf new("chip_distress_risk", profile.ChipDistressRisk, .10f) ]; +static IReadOnlyList SongProductionMetrics(SongProductionProfile profile) => +[ + new("producer_musicianship_score", profile.MusicianshipScore, .15f), + new("required_studio_docs_present", profile.RequiredStudioDocsPresent ? 1 : 0, .10f), + new("required_studio_doc_coverage", profile.RequiredStudioDocCoverage, .08f), + new("template_loop_risk", profile.TemplateLoopRisk, .12f), + new("noise_percussion_risk", profile.NoisePercussionRisk, .12f), + new("composition_section_score", profile.CompositionSectionScore, .08f), + new("aqua_gap_count", profile.AquaGapCount, .03f) +]; + static SongInstrumentProfile AnalyzeSongInstrumentProfile(string script) { var lower = script.ToLowerInvariant(); @@ -3533,6 +3616,165 @@ static SongInstrumentProfile AnalyzeSongInstrumentProfile(string script) return new SongInstrumentProfile(hasSyrinxVoice, hasSubtractiveDrums, hasAdditivePad, hasTexture, score, chipRisk, summary); } +static SongProductionProfile AnalyzeSongProductionProfile(string script, string scriptPath, string artifactRoot) +{ + var lower = script.ToLowerInvariant(); + var evidenceRoots = EvidenceSearchRoots(scriptPath, artifactRoot).ToArray(); + var producerBrief = FindEvidenceFile(evidenceRoots, "producer-brief.md", "hypotheses.md"); + var listeningJournal = FindEvidenceFile(evidenceRoots, "listening-journal.md"); + var aquaGapLedger = FindEvidenceFile(evidenceRoots, "aqua-gap-ledger.md"); + var studioLesson = FindEvidenceFile(evidenceRoots, "studio-lesson.md", "producer-lesson.md", "abstraction-ledger.md", "instrument-conventions.md"); + var docCoverage = + (FileExistsWithText(producerBrief) ? .25f : 0) + + (FileExistsWithText(listeningJournal) ? .25f : 0) + + (FileExistsWithText(aquaGapLedger) ? .25f : 0) + + (FileExistsWithText(studioLesson) ? .25f : 0); + + var curveCount = CountOccurrences(lower, "curve "); + var mixCount = CountOccurrences(lower, "mix "); + var sequenceCount = CountOccurrences(lower, "sequence "); + var sectionWordCount = + CountOccurrences(lower, "section") + + CountOccurrences(lower, "verse") + + CountOccurrences(lower, "chorus") + + CountOccurrences(lower, "bridge") + + CountOccurrences(lower, "break") + + CountOccurrences(lower, "drop") + + CountOccurrences(lower, "automation"); + var lateEventHints = + CountOccurrences(lower, "bar=2") + + CountOccurrences(lower, "bar=4") + + CountOccurrences(lower, "bar=8") + + CountOccurrences(lower, "2.") + + CountOccurrences(lower, "4.") + + CountOccurrences(lower, "8."); + var sectionScore = Math.Clamp( + sequenceCount * .08f + + curveCount * .035f + + mixCount * .08f + + sectionWordCount * .08f + + lateEventHints * .04f, + 0, + 1); + + var commonPatternCount = + CountOccurrences(lower, "pattern=x..x") + + CountOccurrences(lower, "pattern=x...") + + CountOccurrences(lower, "pattern=..x.") + + CountOccurrences(lower, "pattern=....x") + + CountOccurrences(lower, "x..x") + + CountOccurrences(lower, "x.x."); + var stockDrumWords = lower.Contains("kick", StringComparison.Ordinal) && + lower.Contains("snare", StringComparison.Ordinal) && + (lower.Contains("hat", StringComparison.Ordinal) || lower.Contains("dust", StringComparison.Ordinal)); + var templateRisk = Math.Clamp( + (stockDrumWords ? .30f : 0) + + MathF.Min(.30f, commonPatternCount * .08f) + + (sectionScore < .35f ? .25f : 0) + + (sequenceCount <= 2 ? .15f : 0), + 0, + 1); + + var rawNoiseCount = CountOccurrences(lower, "wave=noise") + CountOccurrences(lower, "wave = noise"); + var hasNoiseBed = rawNoiseCount > 0 && + (lower.Contains("sustain=30", StringComparison.Ordinal) || + lower.Contains("sustain = 30", StringComparison.Ordinal) || + lower.Contains("duration_seconds", StringComparison.Ordinal) || + lower.Contains("air_wash", StringComparison.Ordinal)); + var hasDrumNoise = rawNoiseCount > 0 && + (lower.Contains("snare", StringComparison.Ordinal) || + lower.Contains("hat", StringComparison.Ordinal) || + lower.Contains("dust", StringComparison.Ordinal)); + var hasPitchedBody = lower.Contains("pitch_ramp", StringComparison.Ordinal) || + lower.Contains("body", StringComparison.Ordinal) && + (lower.Contains("wave=sine", StringComparison.Ordinal) || + lower.Contains("wave=triangle", StringComparison.Ordinal) || + lower.Contains("wave = sine", StringComparison.Ordinal) || + lower.Contains("wave = triangle", StringComparison.Ordinal)); + var hasBandLimits = lower.Contains("hpf", StringComparison.Ordinal) || + lower.Contains("lpf", StringComparison.Ordinal) || + lower.Contains("bpf", StringComparison.Ordinal); + var noiseRisk = Math.Clamp( + rawNoiseCount * .12f + + (hasNoiseBed ? .35f : 0) + + (hasDrumNoise && !hasPitchedBody ? .30f : 0) - + (hasBandLimits ? .12f : 0), + 0, + 1); + + var aquaGapCount = CountEvidenceItems(aquaGapLedger); + var musicianship = Math.Clamp( + docCoverage * .35f + + sectionScore * .25f + + (1 - templateRisk) * .20f + + (1 - noiseRisk) * .20f, + 0, + 1); + var summary = + $"studio_docs={(docCoverage >= .99f ? "complete" : docCoverage > 0 ? "partial" : "missing")}; " + + $"doc_coverage={docCoverage:0.###}; section_score={sectionScore:0.###}; " + + $"template_loop_risk={templateRisk:0.###}; noise_percussion_risk={noiseRisk:0.###}; " + + $"aqua_gap_count={aquaGapCount}; producer_musicianship_score={musicianship:0.###}"; + return new SongProductionProfile( + FileExistsWithText(producerBrief), + FileExistsWithText(listeningJournal), + FileExistsWithText(aquaGapLedger), + FileExistsWithText(studioLesson), + docCoverage, + sectionScore, + templateRisk, + noiseRisk, + aquaGapCount, + musicianship, + summary); +} + +static IEnumerable EvidenceSearchRoots(string scriptPath, string artifactRoot) +{ + foreach (var root in new[] { Path.GetDirectoryName(Path.GetFullPath(scriptPath)), Path.GetFullPath(artifactRoot) }) + { + var current = string.IsNullOrWhiteSpace(root) ? null : new DirectoryInfo(root); + for (var depth = 0; current is not null && depth < 7; depth++) + { + yield return current.FullName; + current = current.Parent; + } + } +} + +static string FindEvidenceFile(IEnumerable roots, params string[] names) +{ + foreach (var root in roots.Distinct(StringComparer.OrdinalIgnoreCase)) + { + foreach (var name in names) + { + var path = Path.Combine(root, name); + if (File.Exists(path)) + { + return path; + } + } + } + + return ""; +} + +static bool FileExistsWithText(string path) => + !string.IsNullOrWhiteSpace(path) && File.Exists(path) && new FileInfo(path).Length > 16; + +static int CountEvidenceItems(string path) +{ + if (!FileExistsWithText(path)) + { + return 0; + } + + return File.ReadLines(path) + .Count(line => line.TrimStart().StartsWith("-", StringComparison.Ordinal) || + line.Contains("missing", StringComparison.OrdinalIgnoreCase) || + line.Contains("gap", StringComparison.OrdinalIgnoreCase)); +} + static int CountOccurrences(string haystack, string needle) { var count = 0; @@ -3546,24 +3788,38 @@ static int CountOccurrences(string haystack, string needle) return count; } -static string SongVerdict(AudioComparison comparison, SongInstrumentProfile profile, SongContinuityProfile continuity) +static string SongVerdict( + AudioComparison comparison, + SongInstrumentProfile profile, + SongProductionProfile production, + SongContinuityProfile continuity) { if (continuity.ModeCollapseRisk >= .60f) { return "weak-mode-collapse"; } + if (production.NoisePercussionRisk >= .65f || production.TemplateLoopRisk >= .70f) + { + return "weak-slop-template"; + } + + if (!production.RequiredStudioDocsPresent && production.MusicianshipScore < .45f) + { + return "weak-producer-missing"; + } + if (profile.ChipDistressRisk >= .75f && comparison.LogMelCosineSimilarity < .60f) { return "weak"; } - if (comparison.LogMelCosineSimilarity >= .70f && comparison.Score >= .45f) + if (comparison.LogMelCosineSimilarity >= .70f && comparison.Score >= .45f && production.MusicianshipScore >= .55f) { return "promising"; } - if (comparison.LogMelCosineSimilarity >= .35f || comparison.Score >= .30f) + if ((comparison.LogMelCosineSimilarity >= .35f || comparison.Score >= .30f) && production.MusicianshipScore >= .40f) { return "pressure"; } @@ -3893,7 +4149,7 @@ static string SongComparisonReport(SongChallenge challenge, IpaTrialScriptCandid static string SongSummaryCsv(IReadOnlyList results) { var builder = new StringBuilder(); - builder.AppendLine("trial_id,candidate_id,reference_id,verdict,log_mel_cosine,log_mel_distance,audio_score,envelope_distance,rms_ratio,centroid_ratio,zero_crossing_ratio,articulation_score,musical_instrument_score,chip_distress_risk,instrument_voice_syrinx,instrument_drum_subtractive,instrument_pad_additive,candidate_active_coverage,active_coverage_ratio,candidate_motion_coverage,motion_coverage_ratio,candidate_first_second_energy_share,first_second_energy_excess,candidate_tail_energy_share,tail_energy_ratio,mode_collapse_risk"); + builder.AppendLine("trial_id,candidate_id,reference_id,verdict,log_mel_cosine,log_mel_distance,audio_score,envelope_distance,rms_ratio,centroid_ratio,zero_crossing_ratio,articulation_score,musical_instrument_score,chip_distress_risk,instrument_voice_syrinx,instrument_drum_subtractive,instrument_pad_additive,producer_musicianship_score,required_studio_docs_present,required_studio_doc_coverage,template_loop_risk,noise_percussion_risk,composition_section_score,aqua_gap_count,candidate_active_coverage,active_coverage_ratio,candidate_motion_coverage,motion_coverage_ratio,candidate_first_second_energy_share,first_second_energy_excess,candidate_tail_energy_share,tail_energy_ratio,mode_collapse_risk"); foreach (var result in results) { builder.Append(Escape(result.TrialId)).Append(','); @@ -3913,6 +4169,13 @@ static string SongSummaryCsv(IReadOnlyList results) builder.Append(Metric(result, "instrument_voice_syrinx")).Append(','); builder.Append(Metric(result, "instrument_drum_subtractive")).Append(','); builder.Append(Metric(result, "instrument_pad_additive")).Append(','); + builder.Append(Metric(result, "producer_musicianship_score")).Append(','); + builder.Append(Metric(result, "required_studio_docs_present")).Append(','); + builder.Append(Metric(result, "required_studio_doc_coverage")).Append(','); + builder.Append(Metric(result, "template_loop_risk")).Append(','); + builder.Append(Metric(result, "noise_percussion_risk")).Append(','); + builder.Append(Metric(result, "composition_section_score")).Append(','); + builder.Append(Metric(result, "aqua_gap_count")).Append(','); builder.Append(Metric(result, "candidate_active_coverage")).Append(','); builder.Append(Metric(result, "active_coverage_ratio")).Append(','); builder.Append(Metric(result, "candidate_motion_coverage")).Append(','); @@ -3934,11 +4197,11 @@ static string SongEvaluatorReport(SongChallenge challenge, IReadOnlyList MetricValue(result, "log_mel_cosine"))) { - builder.AppendLine(CultureInfo.InvariantCulture, $"| {result.CandidateId} | {result.Verdict} | {MetricValue(result, "log_mel_cosine"):0.######} | {MetricValue(result, "audio_score"):0.######} | {MetricValue(result, "musical_instrument_score"):0.######} | {MetricValue(result, "chip_distress_risk"):0.######} | {MetricValue(result, "candidate_motion_coverage"):0.######} | {MetricValue(result, "candidate_first_second_energy_share"):0.######} | {MetricValue(result, "mode_collapse_risk"):0.######} | {MetricValue(result, "rms_ratio"):0.######} | {MetricValue(result, "centroid_ratio"):0.######} |"); + builder.AppendLine(CultureInfo.InvariantCulture, $"| {result.CandidateId} | {result.Verdict} | {MetricValue(result, "log_mel_cosine"):0.######} | {MetricValue(result, "audio_score"):0.######} | {MetricValue(result, "musical_instrument_score"):0.######} | {MetricValue(result, "producer_musicianship_score"):0.######} | {MetricValue(result, "required_studio_docs_present"):0.######} | {MetricValue(result, "template_loop_risk"):0.######} | {MetricValue(result, "noise_percussion_risk"):0.######} | {MetricValue(result, "candidate_motion_coverage"):0.######} | {MetricValue(result, "candidate_first_second_energy_share"):0.######} | {MetricValue(result, "mode_collapse_risk"):0.######} | {MetricValue(result, "rms_ratio"):0.######} | {MetricValue(result, "centroid_ratio"):0.######} |"); } return builder.ToString(); @@ -4134,6 +4397,22 @@ sealed record SongInstrumentProfile( float ChipDistressRisk, string Summary); +sealed record SongProductionProfile( + bool HasProducerBrief, + bool HasListeningJournal, + bool HasAquaGapLedger, + bool HasStudioLesson, + float RequiredStudioDocCoverage, + float CompositionSectionScore, + float TemplateLoopRisk, + float NoisePercussionRisk, + int AquaGapCount, + float MusicianshipScore, + string Summary) +{ + public bool RequiredStudioDocsPresent => HasProducerBrief && HasListeningJournal && HasAquaGapLedger && HasStudioLesson; +} + sealed record SongContinuityProfile( float TargetActiveCoverage, float CandidateActiveCoverage, @@ -4166,6 +4445,13 @@ sealed record SongSummaryRow( float InstrumentVoiceSyrinx, float InstrumentDrumSubtractive, float InstrumentPadAdditive, + float ProducerMusicianshipScore, + float RequiredStudioDocsPresent, + float RequiredStudioDocCoverage, + float TemplateLoopRisk, + float NoisePercussionRisk, + float CompositionSectionScore, + float AquaGapCount, float CandidateActiveCoverage, float ActiveCoverageRatio, float CandidateMotionCoverage, diff --git a/tools/run-song-snippet-trial-swarm.ps1 b/tools/run-song-snippet-trial-swarm.ps1 index 3ac1b97..efaf329 100644 --- a/tools/run-song-snippet-trial-swarm.ps1 +++ b/tools/run-song-snippet-trial-swarm.ps1 @@ -394,9 +394,9 @@ for ($pass = 1; $pass -le $Passes; $pass++) { $promptPath = Join-Path $agentDir "hypothesis-agent.prompt.md" $logPath = Join-Path $logRoot "$passId-$agentId-hypothesis-agent.log" $prompt = @" -You are an AquaSynth song-snippet parity worker. +You are an AquaSynth producer-apprenticeship worker. -The dataset is on trial. Your patches, hypotheses, failures, and evaluator scores become curriculum evidence in CultCache and the vector database. Write useful sample data and reusable reasoning, not just a one-off trick that happens to flatter one metric. +The dataset is on trial. Your patches, producer briefs, listening journals, failures, and evaluator scores become curriculum evidence in CultCache and the vector database. Write useful studio knowledge and reusable reasoning, not just a one-off trick that happens to flatter one metric. Challenge: $challengeList @@ -415,17 +415,25 @@ $agentSeedList - required duration: $durationLabel Goal: -Write exactly one parseable AquaSynth `.aqua` patch for each frozen $targetKind reference assigned to you. This is scene-audio parity, not IPA articulation. You may use ordinary voices, FM, AM/tremolo, filters, formants, noise layers, acoustic vocal/syrinx primitives, additive/PAD layers, curves, and helper voices. +Write exactly one parseable AquaSynth `.aqua` patch for each frozen $targetKind reference assigned to you. This is scene-audio parity and producer apprenticeship, not IPA articulation. You may use ordinary voices, FM, AM/tremolo, filters, formants, noise layers, acoustic vocal/syrinx primitives, additive/PAD layers, curves, and helper voices. Composition objective: - The previous corpus learned useful sound-production roles but collapsed into short phrases plus texture. Your job is now composition parity: declare meter, tonal center or progression, instrument lanes, section events, automation, and mix motion before polishing timbre. - Every final patch must include a composition spine using today's implemented sugar: `meter`, `sequence`, `chords` or `scale`, and `mix` where appropriate. These lower to ordinary `param` and `curve` owners; they are not hidden magic. -- Treat the target as arranged music across time. There should be distinct musical events or motif mutations after the first second, preferably near seconds 2, 4, 6, and 8 for ten-second clips. +- Treat the target as arranged music across time. There should be distinct musical events or motif mutations after the first second, distributed across the full assigned duration. For 10-second clips, seconds 2, 4, 6, and 8 are useful checkpoints; for 30-second or full-song targets, use section entrances, drops, fills, swells, or mix moves across the beginning, middle, and ending. +- Do not submit the stock attractor: a voiced burst at the start, a copied four-lane kick/snare/hat loop, then textured noise. If the reference really demands that shape, cite target artifacts and listening evidence proving it. Generalization: - You own $SongsPerAgent target patches, and your report must compare all assigned targets and state what reusable scene/instrument knowledge transfers between them. - The patches may share instrument roles, texture roles, control idioms, and DSL conventions, but each patch should fit its own target's tempo/register/spectral evidence. -- Future agents will receive distilled evidence from this trial when asked to zero-shot an audio production request. Make your `hypotheses.md` useful retrieval context: name what transferred, what did not, and what evidence would change your next patch. +- Future agents will receive distilled evidence from this trial when asked to zero-shot an audio production request. Make your studio reports useful retrieval context: name what transferred, what did not, what sounded alive, what sounded fake, and what evidence would change your next patch. + +Producer evidence: +- Write $agentDir/producer-brief.md before patching. For each target, include the artistic reading, likely genre/tempo feel, emotional/energy contour, section map, instrument role map, mix priorities, and the exact challenge artifacts you are trusting. +- Write $agentDir/listening-journal.md during self-iteration. For every attempt, record what you expected, what the evaluator/audio facts said, what sounded alive, what sounded fake or static, and the exact revision you made. +- Write $agentDir/aqua-gap-ledger.md. List missing primitives, syntax sugar, control surfaces, analysis views, or renderer features that made the production harder. Each gap needs current workaround, evidence path, and whether it blocks composition or only polish. +- Write $agentDir/studio-lesson.md at the end. This is the compact lesson for the next musician: keep/cut verdicts, transferable production ideas, rejected tricks, and which AquaSynth abstractions should be mined next. +- The evaluator records `producer_musicianship_score`, `required_studio_docs_present`, `template_loop_risk`, `noise_percussion_risk`, `composition_section_score`, and `aqua_gap_count`. Candidates with missing studio evidence, stock-loop risk, or noise-percussion risk are failure pressure, not curriculum exemplars. Self-iteration loop: - For each target, run exactly $IterationsPerTarget local attempts before publishing the final `.aqua` file. @@ -435,7 +443,8 @@ Self-iteration loop: - After each score, read the attempt's `evaluator-report.md` and `summary.csv`. If the attempt rendered, also read `audio/comparison.txt`, `audio/candidate-analysis.md`, `audio/candidate-logmel-band-stats.csv`, `audio/candidate-rms-envelope-autocorr.csv`, and `audio/candidate-whitened-spectral-autocorr.csv`. If the attempt is `render-failed`, do not try to read missing candidate audio analysis files; treat the parse/lowering/render failure as negative syntax evidence and fix that before changing the composition. - Use those candidate-side facts the same way you use target facts: compare spectral bands, envelope autocorrelation, whitened spectral autocorrelation, centroid/RMS, instrument-role metrics, and chip-distress risk. Move timings, filters, gains, sources, roles, and patterns between attempts. - The evaluator records song-continuity metrics: `candidate_motion_coverage`, `motion_coverage_ratio`, `candidate_first_second_energy_share`, `first_second_energy_excess`, `candidate_tail_energy_share`, and `mode_collapse_risk`. A patch that plays a short phrase in the first second and then coasts on low-motion texture/noise is failed song evidence even if its RMS or log-mel score looks tolerable. -- If an attempt has `mode_collapse_risk >= .45`, the next attempt must add later-section motifs or eventful motion after seconds 2, 4, 6, and 8. Do not merely raise the noise bed or pad gain; that is hiding, not composing. +- If an attempt has `mode_collapse_risk >= .45`, the next attempt must add later-section motifs or eventful motion across the target's full duration. Do not merely raise the noise bed or pad gain; that is hiding, not composing. +- If an attempt has high `template_loop_risk` or `noise_percussion_risk`, rebuild the role ownership before touching level: drums need pitched body plus filtered skin and target-specific gates; texture needs band limits and musical motion. - Keep intermediate candidates inside `$iterationRoot`; publish only one final `.aqua` per target to the official target patch output directory. Reusable abstraction mining: @@ -455,7 +464,7 @@ Reusable abstraction mining: Noise and texture: - Do not model background or recording color as a full-duration raw `voice wave=noise` bed. That produces static white-noise wash and will be treated as broken subtractive synthesis, not scene modeling. -- Use the shaped texture owner for noise roles: `texture name=dust_hat role=dust pattern=x..x step= gain=.08 sustain=`, `texture name=air_wash role=air gain=.035 sustain=`, `texture name=codec_bed role=codec gain=.04 sustain=`. +- Use the shaped texture owner for noise roles, but make it target-specific: `texture name=dust_hat role=dust pattern= step= gain=.08 sustain=`, `texture name=air_wash role=air gain=.035 sustain=`, `texture name=codec_bed role=codec gain=.04 sustain=`. - `texture` lowers to a gated noise voice with role-specific bandpass/highpass/lowpass/tremolo defaults and a control curve or pattern gate. Raw `voice wave=noise` is only acceptable for short transients with explicit gates and narrow filters. Instrument ownership: @@ -509,11 +518,12 @@ Instrument and DSL convention invention: Evidence contract: - Read every assigned challenge report and $preEvidence. -- Write $agentDir/hypotheses.md with: per-target reference features, shared cross-target invariants, cited analysis artifacts, tempo/rhythm plan, register/scale plan, scene voices, synthesis owners, invented instrument roles, expected metric movement on both targets, known risks. +- Write $agentDir/hypotheses.md with: per-target reference features, shared cross-target invariants, cited analysis artifacts, tempo/rhythm plan, register/scale plan, scene voices, synthesis owners, invented instrument roles, expected metric movement on assigned targets, known risks. - Include a `Composition Map` section in $agentDir/hypotheses.md: meter/time signature, progression or tonal center, instrument lanes, section events after the opening, automation/mix moves, and which evaluator metrics should prove the arrangement did not collapse. +- Also write $agentDir/producer-brief.md, $agentDir/listening-journal.md, $agentDir/aqua-gap-ledger.md, and $agentDir/studio-lesson.md. These reports are now curriculum admission evidence, not decorative paperwork. - Write exactly one `.aqua` file under each target patch output directory named `____.aqua`. - Include at least three scene voices or layers in the patch: a primary voice/body, a rhythmic drum/transient/noise role, and a pad/bed/recording-color role. Prefer syrinx for the primary voice when it is voice-like, subtractive for the drum/transient, and additive/PAD or shaped texture for the bed. -- The ten-second clip must have full-form continuity: at least four distinct musical events or motif mutations after the first second, with audible activity distributed across the beginning, middle, and ending. A one-second riff followed by nine seconds of gently textured pink noise is mode collapse and should not be submitted. +- The target must have full-form continuity: distinct musical events or motif mutations after the first second, with audible activity distributed across the beginning, middle, and ending. A one-second riff followed by gently textured pink noise is mode collapse and should not be submitted. - Background/recording-color layers must use `texture` or an equivalently gated and band-shaped explicit voice; a static full-duration white-noise bed is failed evidence. - Make each patch duration/gates cover its matching target duration from the challenge report.