diff --git a/RenderPipeline2.md b/RenderPipeline2.md
index 4cd12c9..3c7c18f 100644
--- a/RenderPipeline2.md
+++ b/RenderPipeline2.md
@@ -1073,6 +1073,8 @@ Timecode is the check this engine exists for. It is validated **against the sour
| 2 | Plate re-derived on the worker from the footage root + shot code (manifestVersion 1, or a timing plate that no longer resolves). If elements disagree, the one matching the render's frame count wins; still ambiguous → WARN, never PASS | PASS / FAIL, `reference: "derived"` |
| 3 | API `Shot.timecodeStart` **minus `handleFrames`** | **WARN only, never PASS** — `reference: "api"`, message states the plate could not be read |
+**Several delivered elements.** When the rank-1 timing plate does not match, the worker tries the manifest's other `plates[]` before failing — the panel's longest-duration pick is not always the element the comp was anchored to. A match against one of them is a PASS naming that element; no match still FAILs against the timing plate, listing the others tried. Only delivered footage qualifies: plates under a configured plate search root, or (with none configured) alongside the timing plate in its footage folder. `plates[]` also holds pre-renders, which agree with the render by construction and must never rescue it.
+
Rank 3 is a diagnostic, not a verification: it depends on the show's handle count being correct for that specific plate, which is exactly the assumption the defects above violate.
**Comparison, given the plate reference:**
diff --git a/RenderWorker/VFXReviewWorker.Tests/ValidationStageTests.cs b/RenderWorker/VFXReviewWorker.Tests/ValidationStageTests.cs
index 473204d..e93589c 100644
--- a/RenderWorker/VFXReviewWorker.Tests/ValidationStageTests.cs
+++ b/RenderWorker/VFXReviewWorker.Tests/ValidationStageTests.cs
@@ -109,6 +109,74 @@ public class ValidationStageTests : IDisposable
Assert.Contains("8-frame handle", tc.Message);
}
+ /// Writes a one-frame plate into a footage-style element folder.
+ private string WritePlate(string element, string timecode, int fps = 24)
+ {
+ var plate = Path.Combine(_dir, "footage", $"UNG_107_051_010_{element}_TT_v001", $"UNG_107_051_010_{element}_TT_v001.01001.exr");
+ Directory.CreateDirectory(Path.GetDirectoryName(plate)!);
+ File.Move(ExrFixture.Write(timecode, 3840, 2160, new[] { ("B", 1), ("G", 1), ("R", 1) }, fps, 4), plate);
+ return plate;
+ }
+
+ private static ManifestSource Source(string timingPlate, params string[] plates) => new()
+ {
+ TimingPlate = new PlateRef { Path = timingPlate, FrameStart = 1001 },
+ Plates = plates.Select(p => new PlateRef { Path = p, FrameStart = 1001 }).ToList(),
+ HandleFrames = 8,
+ };
+
+ [Fact]
+ public void AnotherDeliveredPlatePassesWhenTheTimingPlateDoesNot()
+ {
+ // The comp was anchored to FG01, but the panel named BG01 (longest) as
+ // the timing plate — the two carry unrelated camera timecodes.
+ WriteSequence(105, "09:12:00:00");
+ var bg = WritePlate("BG01", "14:26:31:08");
+ var fg = WritePlate("FG01", "09:12:00:00");
+
+ var manifest = Manifest(_dir, null, 1001);
+ manifest.Source = Source(bg, bg, fg);
+
+ var tc = Check(NewStage().Run(manifest, _dir, new ServerConfig()), "timecode_plate_match");
+ Assert.Equal("PASS", tc.Status);
+ Assert.Contains("UNG_107_051_010_FG01_TT_v001.01001.exr", tc.Message);
+ Assert.Contains("BG01", tc.Message);
+ }
+
+ [Fact]
+ public void NoMatchingPlateStillFailsAgainstTheTimingPlate()
+ {
+ WriteSequence(105, "14:26:31:16");
+ var bg = WritePlate("BG01", "14:26:31:08");
+ var fg = WritePlate("FG01", "09:12:00:00");
+
+ var manifest = Manifest(_dir, null, 1001);
+ manifest.Source = Source(bg, bg, fg);
+
+ var tc = Check(NewStage().Run(manifest, _dir, new ServerConfig()), "timecode_plate_match");
+ Assert.Equal("FAIL", tc.Status);
+ Assert.Equal("14:26:31:08", tc.Expected);
+ Assert.Contains("8-frame handle", tc.Message);
+ Assert.Contains("1 other delivered plate", tc.Message);
+ }
+
+ [Fact]
+ public void APreRenderIsNeverUsedAsTheFallbackPlate()
+ {
+ // plates[] is every file in the comp. A pre-render was rendered from
+ // this comp, so it agrees with the render whether or not either is right.
+ WriteSequence(105, "14:26:31:16");
+ var bg = WritePlate("BG01", "14:26:31:08");
+ var preRender = Path.Combine(_dir, "renders", "prerender", "UNG_107_051_010_PRERENDER_01001.exr");
+ Directory.CreateDirectory(Path.GetDirectoryName(preRender)!);
+ File.Move(ExrFixture.Write("14:26:31:16", 3840, 2160, new[] { ("B", 1), ("G", 1), ("R", 1) }, 24, 4), preRender);
+
+ var manifest = Manifest(_dir, null, 1001);
+ manifest.Source = Source(bg, bg, preRender);
+
+ Assert.Equal("FAIL", Check(NewStage().Run(manifest, _dir, new ServerConfig()), "timecode_plate_match").Status);
+ }
+
[Fact]
public void APlateAtADifferentRateFailsPlateFps()
{
diff --git a/RenderWorker/VFXReviewWorker/ValidationStage.cs b/RenderWorker/VFXReviewWorker/ValidationStage.cs
index 3756f19..5f3b4f7 100644
--- a/RenderWorker/VFXReviewWorker/ValidationStage.cs
+++ b/RenderWorker/VFXReviewWorker/ValidationStage.cs
@@ -350,6 +350,31 @@ public sealed class ValidationStage
return;
}
+ var deltaOf = (TimecodeReference r) =>
+ renderTc.ToFrames(fps) - (r.PlateTimecode.ToFrames(fps) + (renderFirstFrame - r.PlateFirstFrame));
+
+ // A shot with several delivered elements: the panel names the longest
+ // as the timing plate, but the comp may have been anchored to another
+ // (BG01 and FG01 carry unrelated camera timecodes). When the timing
+ // plate disagrees, the manifest's other delivered plates are tried
+ // before failing. The comparison is unchanged — it is still a plate, so
+ // a match is a real PASS and no match still FAILs against the timing plate.
+ var timingPlate = reference;
+ var alternatesTried = new List