champions-of-luneraria/Assets/Luneraria/Tests/View/PlayMode/SceneFrameRateTests.cs
Martin Stickelbroeck 3871b3e2e6 U2C Simulation/View Binding
Fester Simulationstakt und Ansichten, die nur lesen; Nachweis, dass die Partie
bei jeder Bildrate dieselbe ist.

- LuneClock: Akkumulator, ganze Schritte zu 0,05 s, Alpha zum Interpolieren.
- LuneMatchRunner: Host-Ablauf einer Bot-Partie (Planung über die
  Befehlsschnittstelle, Kampf im festen Takt, Abrechnung, nächste Runde);
  LuneMatchDriver reicht in Update nur Bildzeit weiter.
- LuneTrace: Fingerabdruck der Partie (jeder Schritt, jede Abrechnung) und der
  reine Kernlauf zum Vergleich.
- Ansichten: LuneMatchView, LuneUnitView (interpolierte Position, Blickrichtung,
  Clips aus Kampfereignissen, Teamring, Lebensbalken), LuneBuildingView mit
  Einsturz und Ruine aus den autoritativen HP, Turmblitze, LuneCameraRig wie
  lune_camera.gd, vorläufige Debug-Anzeige (kein HUD; U3 portiert das G3-HUD).
- Einheitenbibliothek: Kohlenbeißer aus 131 Teilen zu einem Mesh, Animator-
  Controller mit den Blender-Clips, Ring- und Balkenmeshes, Markermaterialien.
- Tests: EditMode (30/60/120 fps, unbegrenzt, Ruckler, 8x Tempo; Seed 2 und
  Seed 9 ab Runde 21) und PlayMode in der Szene bei echten Bildraten 30, 60,
  120 und unbegrenzt: überall derselbe Fingerabdruck wie der Kern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:31:18 +02:00

73 lines
3.9 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Luneraria.Core;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Luneraria.View.Tests
{
/// <summary>
/// U2C in the real scene: the bot match of seed 2 runs with every view attached while the
/// player loop runs at 30, 60, 120 FPS and unlimited (Application.targetFrameRate, VSync off).
/// Each run must reproduce the core's fingerprint. The measured frame rates show that the
/// frame timing really differed. Writes docs/port/logs/u2c/scene-frame-rates.json.
/// </summary>
public sealed class SceneFrameRateTests
{
const float Speed = 24f;
[UnityTest]
public IEnumerator SceneMatchIsTheSameAtRealFrameRates()
{
var options = new MatchOptions { Seed = 2, HumanSlot = -1 };
LuneTrace core = LuneTrace.RunCore(options, 1, 0, "");
var report = new StringBuilder("{\n \"match\": \"seed 2, bots, speed " + Speed + "x\",\n \"core_hash\": \"" + core.Hash.ToString("X16") + "\",\n \"core_steps\": " + core.Steps + ",\n \"runs\": [\n");
var failures = new List<string>();
int[] rates = { 30, 60, 120, -1 };
for (int index = 0; index < rates.Length; index++)
{
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = rates[index];
yield return PlayModeSupport.Load();
var driver = Object.FindAnyObjectByType<LuneMatchDriver>();
driver.Seed = 2;
driver.StartRound = 1;
driver.ExtraGold = 0;
driver.Speed = Speed;
driver.Restart();
driver.Runner.PlanningSeconds = .5;
driver.Runner.SettledSeconds = .5;
var rig = Object.FindAnyObjectByType<LuneCameraRig>();
rig.Interactive = false;
rig.LookAt(new Vec2(0, 0), 60f, true);
float start = Time.realtimeSinceStartup;
int frames = 0;
int maxSteps = 0;
while (driver.Runner.Current != LuneMatchRunner.Stage.Over && Time.realtimeSinceStartup - start < 600f)
{
yield return null;
frames++;
maxSteps = Mathf.Max(maxSteps, driver.Runner.StepsThisFrame);
}
float seconds = Time.realtimeSinceStartup - start;
LuneTrace trace = driver.Runner.Trace;
string label = rates[index] > 0 ? rates[index] + " fps" : "unlimited";
bool same = trace.Hash == core.Hash && trace.Steps == core.Steps;
if (!same)
failures.Add(label + ": " + trace.Hash.ToString("X16") + " / " + trace.Steps + " steps");
Debug.Log(string.Format("LUNE_SCENE_FPS {0}: {1} frames in {2:0.0} s = {3:0.0} fps, up to {4} steps per frame, hash {5:X16}, same={6}", label, frames, seconds, frames / seconds, maxSteps, trace.Hash, same));
report.AppendFormat(" {{\"target\": \"{0}\", \"frames\": {1}, \"seconds\": {2}, \"measured_fps\": {3}, \"max_steps_per_frame\": {4}, \"steps\": {5}, \"hash\": \"{6:X16}\", \"winner\": {7}, \"same_as_core\": {8}}}{9}\n",
label, frames, PlayModeSupport.Number(seconds), PlayModeSupport.Number(frames / seconds), maxSteps, trace.Steps, trace.Hash, driver.Runner.Match.Winner, same ? "true" : "false", index < rates.Length - 1 ? "," : "");
}
Application.targetFrameRate = -1;
report.Append(" ]\n}\n");
string logs = Path.Combine(PlayModeSupport.Root, "docs/port/logs/u2c");
Directory.CreateDirectory(logs);
File.WriteAllText(Path.Combine(logs, "scene-frame-rates.json"), report.ToString());
Assert.IsEmpty(failures, string.Join("\n", failures));
}
}
}