diff --git a/samples/TpsPlugin/TpsPlugin.cs b/samples/TpsPlugin/TpsPlugin.cs
new file mode 100644
index 00000000..824155b2
--- /dev/null
+++ b/samples/TpsPlugin/TpsPlugin.cs
@@ -0,0 +1,149 @@
+using Minecraft.Server.FourKit;
+using Minecraft.Server.FourKit.Command;
+using Minecraft.Server.FourKit.Entity;
+using Minecraft.Server.FourKit.Plugin;
+
+namespace TpsPlugin;
+
+///
+/// Minimal informational plugin. Exposes /tps (server tick rate) and /ping
+/// (caller's connection latency).
+///
+public class TpsPlugin : ServerPlugin
+{
+ public override string name => "TpsPlugin";
+ public override string version => "1.0.0";
+ public override string author => "LCE-Revelations";
+
+ public override void onEnable()
+ {
+ TpsProbe.Start();
+
+ var tps = FourKit.getCommand("tps");
+ tps.setDescription("Show server TPS over 1s/5s/30s/60s windows.");
+ tps.setUsage("/tps");
+ tps.setExecutor(new TpsExecutor());
+
+ var ping = FourKit.getCommand("ping");
+ ping.setDescription("Show your connection ping in milliseconds.");
+ ping.setUsage("/ping");
+ ping.setExecutor(new PingExecutor());
+ }
+
+ public override void onDisable()
+ {
+ TpsProbe.Stop();
+ }
+}
+
+internal static class TpsProbe
+{
+ private static readonly object _lock = new();
+ private static readonly LinkedList<(double elapsed, int tick)> _samples = new();
+ private static System.Threading.Timer? _timer;
+ private static System.Diagnostics.Stopwatch? _sw;
+
+ public static void Start()
+ {
+ _sw = System.Diagnostics.Stopwatch.StartNew();
+ Sample();
+ _timer = new System.Threading.Timer(_ => Sample(), null, 1000, 1000);
+ }
+
+ public static void Stop()
+ {
+ _timer?.Dispose();
+ _timer = null;
+ _sw?.Stop();
+ _sw = null;
+ lock (_lock) _samples.Clear();
+ }
+
+ private static void Sample()
+ {
+ var sw = _sw;
+ if (sw == null) return;
+ try
+ {
+ int tick = FourKit.getServerTick();
+ double elapsed = sw.Elapsed.TotalSeconds;
+ lock (_lock)
+ {
+ _samples.AddLast((elapsed, tick));
+ // 65s window so the 60s average has full data.
+ while (_samples.Count > 66) _samples.RemoveFirst();
+ }
+ }
+ catch
+ {
+ // Probe is best-effort; never let sampling errors take down the host.
+ }
+ }
+
+ public static (int samples, double tps1, double tps5, double tps30, double tps60) Read()
+ {
+ (double elapsed, int tick)[] arr;
+ lock (_lock)
+ {
+ if (_samples.Count < 2) return (_samples.Count, 0, 0, 0, 0);
+ arr = _samples.ToArray();
+ }
+
+ var last = arr[^1];
+
+ double Window(double seconds)
+ {
+ for (int j = arr.Length - 2; j >= 0; j--)
+ {
+ if (last.elapsed - arr[j].elapsed >= seconds)
+ {
+ var first = arr[j];
+ double dt = last.elapsed - first.elapsed;
+ return dt > 0 ? (last.tick - first.tick) / dt : 0;
+ }
+ }
+ var oldest = arr[0];
+ double dt0 = last.elapsed - oldest.elapsed;
+ return dt0 > 0 ? (last.tick - oldest.tick) / dt0 : 0;
+ }
+
+ return (arr.Length, Window(1), Window(5), Window(30), Window(60));
+ }
+}
+
+internal sealed class TpsExecutor : CommandExecutor
+{
+ public bool onCommand(CommandSender sender, Command command, string label, string[] args)
+ {
+ var (samples, t1, t5, t30, t60) = TpsProbe.Read();
+ if (samples < 2)
+ {
+ sender.sendMessage($"TPS probe warming up ({samples}/2 samples). Try again in a few seconds.");
+ return true;
+ }
+ int tick = FourKit.getServerTick();
+ sender.sendMessage($"TPS 1s={t1:F2} 5s={t5:F2} 30s={t30:F2} 60s={t60:F2}");
+ sender.sendMessage($"server tick={tick} samples={samples}");
+ return true;
+ }
+}
+
+internal sealed class PingExecutor : CommandExecutor
+{
+ public bool onCommand(CommandSender sender, Command command, string label, string[] args)
+ {
+ if (sender is not Player player)
+ {
+ sender.sendMessage("/ping must be run by a player.");
+ return true;
+ }
+ int ping = player.getPing();
+ if (ping < 0)
+ {
+ sender.sendMessage("Ping unavailable.");
+ return true;
+ }
+ sender.sendMessage($"Your ping: {ping}ms");
+ return true;
+ }
+}
diff --git a/samples/TpsPlugin/TpsPlugin.csproj b/samples/TpsPlugin/TpsPlugin.csproj
new file mode 100644
index 00000000..3e53b85a
--- /dev/null
+++ b/samples/TpsPlugin/TpsPlugin.csproj
@@ -0,0 +1,17 @@
+
+
+ net10.0
+ enable
+ enable
+ TpsPlugin
+ TpsPlugin
+ true
+
+
+
+
+ false
+ runtime
+
+
+