diff --git a/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs b/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs
new file mode 100644
index 0000000..925b385
--- /dev/null
+++ b/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs
@@ -0,0 +1,8 @@
+using TeamsISO.Engine.Domain;
+
+namespace TeamsISO.Engine.Pipeline;
+
+public interface IFrameScaler
+{
+ ProcessedFrame Scale(RawFrame source, int targetWidth, int targetHeight, AspectMode aspect, long timestampTicks);
+}
diff --git a/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs b/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs
new file mode 100644
index 0000000..a26c5e4
--- /dev/null
+++ b/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs
@@ -0,0 +1,20 @@
+using TeamsISO.Engine.Domain;
+
+namespace TeamsISO.Engine.Pipeline;
+
+///
+/// Phase A scaler. Copies the source frame's pixel buffer through unchanged and tags the
+/// output with the requested target dimensions. Real scaling is added in Phase B against libyuv.
+///
+public sealed class PassthroughFrameScaler : IFrameScaler
+{
+ public ProcessedFrame Scale(RawFrame source, int targetWidth, int targetHeight, AspectMode aspect, long timestampTicks)
+ {
+ return new ProcessedFrame(
+ Width: targetWidth,
+ Height: targetHeight,
+ TimestampTicks: timestampTicks,
+ Pixels: source.Pixels,
+ Format: source.Format == PixelFormat.Bgra ? PixelFormat.Bgra : PixelFormat.Bgra);
+ }
+}
diff --git a/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs b/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs
new file mode 100644
index 0000000..7457d82
--- /dev/null
+++ b/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs
@@ -0,0 +1,20 @@
+namespace TeamsISO.Engine.Pipeline;
+
+///
+/// Generates a solid-color BGRA frame for use as a "no signal" slate.
+///
+public sealed class SolidFrameRenderer
+{
+ public ProcessedFrame Render(int width, int height, byte b, byte g, byte r, byte a, long timestampTicks)
+ {
+ var pixels = new byte[width * height * 4];
+ for (var i = 0; i < pixels.Length; i += 4)
+ {
+ pixels[i + 0] = b;
+ pixels[i + 1] = g;
+ pixels[i + 2] = r;
+ pixels[i + 3] = a;
+ }
+ return new ProcessedFrame(width, height, timestampTicks, pixels, PixelFormat.Bgra);
+ }
+}
diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs b/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs
new file mode 100644
index 0000000..5976e43
--- /dev/null
+++ b/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs
@@ -0,0 +1,24 @@
+using TeamsISO.Engine.Pipeline;
+
+namespace TeamsISO.Engine.Tests.Pipeline;
+
+public class SolidFrameRendererTests
+{
+ [Fact]
+ public void Render_ProducesBgraFrameOfTargetSize_FilledWithColor()
+ {
+ var renderer = new SolidFrameRenderer();
+ var frame = renderer.Render(width: 1920, height: 1080, b: 0x80, g: 0x80, r: 0x80, a: 0xFF, timestampTicks: 12345);
+
+ frame.Width.Should().Be(1920);
+ frame.Height.Should().Be(1080);
+ frame.Format.Should().Be(PixelFormat.Bgra);
+ frame.Pixels.Length.Should().Be(1920 * 1080 * 4);
+ frame.TimestampTicks.Should().Be(12345);
+
+ var span = frame.Pixels.Span;
+ span[0].Should().Be(0x80); span[1].Should().Be(0x80); span[2].Should().Be(0x80); span[3].Should().Be(0xFF);
+ var last = span.Length - 4;
+ span[last].Should().Be(0x80); span[last + 3].Should().Be(0xFF);
+ }
+}