diff --git a/engine/Age.Engine.Tests/FrameClockTests.cs b/engine/Age.Engine.Tests/FrameClockTests.cs
new file mode 100644
index 0000000..6a30797
--- /dev/null
+++ b/engine/Age.Engine.Tests/FrameClockTests.cs
@@ -0,0 +1,29 @@
+using Age.Engine.Hosting;
+using Xunit;
+
+public class FrameClockTests
+{
+ [Fact]
+ public void Advance_AtSpeed1_AddsRealMilliseconds()
+ {
+ var c = new FrameClock(); // Speed defaults to 1.0
+ c.Advance(0.016); // one ~60fps frame
+ Assert.Equal(16, c.NowMs);
+ }
+
+ [Fact]
+ public void Advance_ScalesBySpeed()
+ {
+ var c = new FrameClock { Speed = 4.0 };
+ c.Advance(0.016);
+ Assert.Equal(64, c.NowMs); // 4x virtual time
+ }
+
+ [Fact]
+ public void EffectiveBudget_ScalesBySpeed_AndFloorsAtOne()
+ {
+ Assert.Equal(30, new FrameClock { OpsPerFrame = 30, Speed = 1.0 }.EffectiveBudget);
+ Assert.Equal(120, new FrameClock { OpsPerFrame = 30, Speed = 4.0 }.EffectiveBudget);
+ Assert.Equal(1, new FrameClock { OpsPerFrame = 0, Speed = 1.0 }.EffectiveBudget);
+ }
+}
diff --git a/engine/Age.Engine/Hosting/FrameClock.cs b/engine/Age.Engine/Hosting/FrameClock.cs
new file mode 100644
index 0000000..3ff0433
--- /dev/null
+++ b/engine/Age.Engine/Hosting/FrameClock.cs
@@ -0,0 +1,23 @@
+namespace Age.Engine.Hosting;
+
+/// Host-owned virtual clock + per-frame op budget. Pure (no threading): the Godot host
+/// advances it once per rendered frame and consults it to pace the VM. The one
+/// factor is the future (unwired) Ctrl fast-forward multiplier — scaling it scales the throttle
+/// budget, sleeps, and the anim tween together. See docs/superpowers/specs/2026-07-08-frame-stepped-vm-design.md.
+public sealed class FrameClock
+{
+ /// Monotonic virtual time in milliseconds (scaled by Speed).
+ public long NowMs { get; private set; }
+
+ /// Speed multiplier. 1.0 = normal. The future Ctrl hook (ADV-scoped); leave at 1.0 for now.
+ public double Speed = 1.0;
+
+ /// Base per-frame interpreter op budget (tunable by eye; ~30 ≈ 1,800 ops/sec at 60fps).
+ public int OpsPerFrame = 30;
+
+ /// Advance the clock by one rendered frame's real delta (seconds), scaled by Speed.
+ public void Advance(double realDeltaSeconds) => NowMs += (long)(realDeltaSeconds * 1000.0 * Speed);
+
+ /// Ops the VM may run before yielding a frame, scaled by Speed (min 1).
+ public int EffectiveBudget => System.Math.Max(1, (int)System.Math.Round(OpsPerFrame * Speed));
+}