Das G3-HUD ist abgenommen; der Meilenstein U3 ist damit fertig. - Prüfung (Port von verify_g3, Teile A–H plus I): 718 Prüfungen ohne Fehler. Teil I prüft die Feld-Darstellung um das HUD (U3-Zugabe aus dem Motivvergleich): Aufstellungsflächen, Grotteneingang, ruhender Boss, Namensschilder, Eigentümer-Ring, Ancient-Balken. - Feld um das HUD: Namensschilder für Helden (mit Stufe), Angriffs- champions und den eingesetzten Boss („für Blau“), Eigentümer-Ring, Ancient-Balken bei Schaden, ruhende Schlundkrone auf dem Podest, eigener Grotteneingang, Aufstellungsflächen als eigene Objekte. - Schriften korrigiert: Die Referenz deklariert Cantarell 560/700–760 und Cinzel 700, wendet die Gewichte aber nie an (variation_opentype mit dem String-Schlüssel „wght“ wird von Godot ignoriert). G3 zeigt Regular 400, der Port ebenso — Cinzel-Regular statt Bold, keine Kontur zur Aufdickung. Laufweite aus spacing_glyph umgerechnet; dunkler Saum als Schatten statt TextCore-Kontur. - Werkzeuge: HudViewsTests (14 G3-Motive), HudPlaytest (Spieltest über die echte Oberfläche per Befehlsdateien), ui_ms_per_frame im LuneBenchmark. Layout folgt den echten Panelhöhen, Kamera startet wie Godot _ready auf der eigenen Lane. - Tests: EditMode 23/23, PlayMode 13/13, Kerntests 402/402, Verify 718/0. Fingerabdrücke der Bot-Partien unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5). - Bilder: 14 Motive bei 1920×1080, Auflösungen 1152×720 bis 2560×1440, Prüf-Schlüsselbilder, Spieltest-Screenshots. - Leistung (macOS-Player mit HUD, 116 Einheiten): 169 fps Übersicht, 218 fps Mid nah; HUD-Skripte 0,090 bzw. 0,078 ms je Bild. - Dokumentation: U3-UI (U3D, Schriftbefund), PORT-QA (U3), PORT-STATUS (U3 fertig, U4 nächster Schritt), KNOWN-BEHAVIOR-DIFFERENCES. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
170 lines
7 KiB
C#
170 lines
7 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using Luneraria.Core;
|
||
using NUnit.Framework;
|
||
using UnityEngine;
|
||
using UnityEngine.TestTools;
|
||
|
||
namespace Luneraria.UI.Tests
|
||
{
|
||
/// <summary>
|
||
/// Interactive playtest of the real game (port of tools/luneraria/g3_playtest.gd). It never
|
||
/// touches the match state: every action is an input a player gives, at pixel coordinates of
|
||
/// the 1920×1080 picture (origin top left). A click reaches the HUD panel as pointer events
|
||
/// and the field as LuneGame.ClickField, which ignores points on the HUD, because in the
|
||
/// player both see the same mouse; game keys go through LuneGame.Key, the camera keys R, Q,
|
||
/// E and O through the rig as in its own key handling. Pointer moves give hover (tooltips).
|
||
/// A tester writes command batches and gets a screenshot back after each batch:
|
||
/// /tmp/lune_play/cmd-N.txt lines: click X Y · move X Y · key NAME · wait SECONDS · shot NAME · quit
|
||
/// /tmp/lune_play/out-N.png the screen after the batch; /tmp/lune_play/ok-N holds round and phase
|
||
/// The log of all commands with round and phase goes to docs/port/logs/u3/playtest-log.txt.
|
||
/// There is no main menu in the port yet, so the playtest starts in the match of the G1 scene
|
||
/// (seed of the scene, 2; LUNE_PLAYTEST_SEED overrides). It ends with quit or after 30 idle minutes.
|
||
/// Run: -testFilter "Luneraria.UI.Tests.HudPlaytest".
|
||
/// </summary>
|
||
[Explicit("Spieltest: wartet auf Befehlsdateien")]
|
||
public sealed class HudPlaytest
|
||
{
|
||
const string Dir = "/tmp/lune_play/";
|
||
const string LogFile = "docs/port/logs/u3/playtest-log.txt";
|
||
|
||
HudSession _s;
|
||
readonly List<string> _log = new List<string>();
|
||
float _started;
|
||
bool _quit;
|
||
|
||
[UnityTest, Timeout(int.MaxValue)]
|
||
public IEnumerator Play()
|
||
{
|
||
Directory.CreateDirectory(Dir);
|
||
foreach (string file in Directory.GetFiles(Dir))
|
||
File.Delete(file);
|
||
int seed = int.TryParse(Environment.GetEnvironmentVariable("LUNE_PLAYTEST_SEED"), out int value) ? value : 2;
|
||
_s = new HudSession();
|
||
yield return _s.Open(1920, 1080, seed: seed);
|
||
_started = Time.realtimeSinceStartup;
|
||
yield return new WaitForSecondsRealtime(.5f);
|
||
_s.Capture(Dir + "out-0.png");
|
||
File.WriteAllText(Dir + "ok-0", Status());
|
||
int next = 1;
|
||
float idle = 0f;
|
||
while (!_quit && idle < 1800f)
|
||
{
|
||
string path = Dir + "cmd-" + next + ".txt";
|
||
if (!File.Exists(path))
|
||
{
|
||
idle += Time.unscaledDeltaTime;
|
||
yield return null;
|
||
continue;
|
||
}
|
||
idle = 0f;
|
||
foreach (string raw in File.ReadAllLines(path))
|
||
{
|
||
string line = raw.Trim();
|
||
if (line.Length == 0 || line.StartsWith("#", StringComparison.Ordinal))
|
||
continue;
|
||
Write(next, line);
|
||
yield return Command(line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
|
||
if (_quit)
|
||
break;
|
||
}
|
||
yield return new WaitForSecondsRealtime(.35f);
|
||
_s.Capture(Dir + "out-" + next + ".png");
|
||
File.WriteAllText(Dir + "ok-" + next, Status());
|
||
next++;
|
||
}
|
||
_s.Close();
|
||
Assert.IsTrue(_quit, "the playtest ended with quit");
|
||
}
|
||
|
||
string Status()
|
||
{
|
||
LuneMatch match = _s.Match;
|
||
string phase = match.Phase == Phase.Planning ? "PLANUNG" : match.Phase == Phase.Combat ? "KAMPF" : "ENDE";
|
||
return "round=" + match.RoundNumber + " phase=" + phase;
|
||
}
|
||
|
||
void Write(int index, string line)
|
||
{
|
||
_log.Add(string.Format(CultureInfo.InvariantCulture, "{0,7:0.0} s #{1} [{2}] {3}", Time.realtimeSinceStartup - _started, index, Status(), line));
|
||
string file = Path.Combine(HudSession.Root, LogFile);
|
||
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
||
File.WriteAllText(file, string.Join("\n", _log) + "\n");
|
||
}
|
||
|
||
IEnumerator Command(string[] parts)
|
||
{
|
||
switch (parts[0])
|
||
{
|
||
case "click":
|
||
{
|
||
Vector2 pixel = Pixel(parts);
|
||
Vector2 at = _s.Hud.PixelToPanel(pixel);
|
||
_s.MoveTo(at);
|
||
yield return null;
|
||
_s.Pointer(at, EventType.MouseDown);
|
||
// The field sees the same press (LuneGame.HandleClicks); it ignores points on the HUD.
|
||
_s.Game.ClickField(new Vector2(pixel.x, _s.Height - pixel.y));
|
||
yield return null;
|
||
_s.Pointer(at, EventType.MouseUp);
|
||
yield return null;
|
||
break;
|
||
}
|
||
case "move":
|
||
_s.MoveTo(_s.Hud.PixelToPanel(Pixel(parts)));
|
||
yield return null;
|
||
break;
|
||
case "key":
|
||
Key(parts[1].ToUpperInvariant());
|
||
yield return null;
|
||
break;
|
||
case "wait":
|
||
yield return new WaitForSecondsRealtime(float.Parse(parts[1], CultureInfo.InvariantCulture));
|
||
break;
|
||
case "shot":
|
||
yield return null;
|
||
_s.Capture(Dir + parts[1] + ".png");
|
||
break;
|
||
case "quit":
|
||
_quit = true;
|
||
break;
|
||
default:
|
||
Debug.LogWarning("LUNE_PLAYTEST unknown command: " + string.Join(" ", parts));
|
||
break;
|
||
}
|
||
}
|
||
|
||
static Vector2 Pixel(string[] parts)
|
||
{
|
||
return new Vector2(float.Parse(parts[1], CultureInfo.InvariantCulture), float.Parse(parts[2], CultureInfo.InvariantCulture));
|
||
}
|
||
|
||
void Key(string name)
|
||
{
|
||
// Camera keys of the rig (LuneCameraRig.HandleInput), blocked while the options are open.
|
||
if (name == "R" || name == "Q" || name == "E" || name == "O")
|
||
{
|
||
if (_s.Hud.Settings.Open)
|
||
return;
|
||
if (name == "R")
|
||
_s.Rig.ResetRequested?.Invoke();
|
||
else if (name == "O")
|
||
_s.Rig.Overview();
|
||
else
|
||
_s.Rig.RotateStep(name == "Q" ? -1 : 1);
|
||
return;
|
||
}
|
||
KeyCode code;
|
||
if (name.Length == 1 && char.IsDigit(name[0]))
|
||
code = KeyCode.Alpha0 + (name[0] - '0');
|
||
else if (name == "ESC")
|
||
code = KeyCode.Escape;
|
||
else
|
||
code = (KeyCode)Enum.Parse(typeof(KeyCode), name, true);
|
||
_s.Game.Key(code);
|
||
}
|
||
}
|
||
}
|