champions-of-luneraria/Assets/Luneraria/Tests/UI/PlayMode/HudViewsTests.cs
Martin Stickelbroeck 66867f3332 U3D Abnahme: Prüfung, Feld um das HUD, Bilder, Leistung
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>
2026-10-02 12:55:06 +02:00

223 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Luneraria.Core;
using Luneraria.View;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Luneraria.UI.Tests
{
/// <summary>
/// The fourteen G3 motifs (port of tools/luneraria/g3_views.gd) from the real game with the
/// real HUD at one window size. Like Godot, fixtures only set public match state before or
/// between phases (gold, round, building HP, boss claim, the armies of round 14), the starter
/// army comes from the HUD's own button (its toast stays, as in Godot's pictures), further
/// commands go straight to the match followed by the preview rebuild (Godot command() and
/// _rebuild_preview()), and HUD views open through the HUD's public methods. After a click
/// the pointer leaves the HUD, so no tooltip covers a picture. A capture tool, not part of the
/// suite: -testFilter "Luneraria.UI.Tests.HudViewsTests" (all sizes) or one size by its test
/// name. 1920×1080 is written as PNG (the reference set next to Godot's), the other sizes as
/// JPEG to keep the repository small.
/// </summary>
[Explicit("Bildwerkzeug: nur gezielt per -testFilter")]
public sealed class HudViewsTests
{
/// <summary>The Bünde of Godot's g3_views.gd (the human seat 1 plays the Wurzelbund).</summary>
static readonly string[] Bunds = { "wurzelbund", "wurzelbund", "glutpakt", "glutpakt", "wurzelbund", "glutpakt" };
int _width;
int _height;
string _folder;
string _extension;
readonly List<string> _shots = new List<string>();
[UnityTest, Timeout(1800000)] public IEnumerator Motifs_1920x1080() { yield return Run(1920, 1080); }
[UnityTest, Timeout(1800000)] public IEnumerator Motifs_2560x1440() { yield return Run(2560, 1440); }
[UnityTest, Timeout(1800000)] public IEnumerator Motifs_1280x720() { yield return Run(1280, 720); }
[UnityTest, Timeout(1800000)] public IEnumerator Motifs_2560x1080() { yield return Run(2560, 1080); }
IEnumerator Run(int width, int height)
{
_width = width;
_height = height;
_folder = "docs/port/screenshots/u3/motive-" + width + "x" + height + "/";
_extension = width == 1920 && height == 1080 ? ".png" : ".jpg";
_shots.Clear();
yield return PlanningViews();
yield return BossViews();
yield return CombatViews();
yield return AncientViews();
Debug.Log("LUNE_VIEWS " + width + "x" + height + ": " + _shots.Count + " " + string.Join(", ", _shots));
Assert.AreEqual(14, _shots.Count, "all fourteen motifs");
}
IEnumerator Fresh(HudSession s)
{
yield return s.Open(_width, _height, seed: 4, bunds: Bunds);
}
IEnumerator Shot(HudSession s, string label)
{
yield return s.Shot(_folder + label + _extension);
_shots.Add(label);
}
static IEnumerator Wait(float seconds)
{
yield return new WaitForSecondsRealtime(seconds);
}
/// <summary>Godot command(): a command of the human seat straight to the match, then the preview.</summary>
static void Send(HudSession s, Command command)
{
CommandResult result = s.Match.Submit(HudSession.Human, command);
Assert.IsTrue(result.Ok, command.Type + ": " + result.Message);
s.Game.RebuildPreview();
s.Sync();
}
/// <summary>
/// Godot armies(): the given seats plan round n with plenty of gold (profile "ausgewogen"),
/// grotto orders go to the own lane; the human keeps an ordinary amount afterwards.
/// </summary>
static void Armies(HudSession s, int round)
{
LuneMatch match = s.Match;
match.RoundNumber = round;
for (int slot = 0; slot < 6; slot++)
{
Draft draft = match.Players[slot].Draft;
draft.Roster.Clear();
draft.Gold = 100000;
LuneBot.Plan(match, slot, "ausgewogen");
foreach (RosterEntry entry in draft.Roster)
{
if (entry.Lane == LuneMatch.Grotto)
entry.Lane = match.Players[slot].Lane;
}
if (draft.Hero != null && draft.Hero.Lane == LuneMatch.Grotto)
draft.Hero.Lane = match.Players[slot].Lane;
}
match.Players[HudSession.Human].Draft.Gold = 185;
}
// ---------------------------------------------------------------- planning
IEnumerator PlanningViews()
{
var s = new HudSession();
yield return Fresh(s);
// A normal planning round: round 3, starter army bought, some gold left.
s.Match.RoundNumber = 3;
s.Me.Draft.Gold = 420;
yield return s.Starter();
s.Leave();
yield return Wait(.4f);
Assert.AreEqual(3, s.Model.OwnRoster.Count, "starter army");
yield return Shot(s, "01-planung");
// Hero hired and shown.
s.Me.Draft.Gold = 900;
Send(s, Command.HeroBuy("held_ringborke"));
s.Hud.OpenHero();
yield return Wait(.4f);
yield return Shot(s, "02-held-ausgewaehlt");
// Item choice with one item equipped.
Send(s, Command.ItemBuy("glutklinge", s.Me.Draft.Hero.Uid));
s.Hud.OpenItems();
yield return Wait(.3f);
yield return Shot(s, "03-itemauswahl");
s.Hud.CloseModal();
// A normal unit selected.
s.Hud.SelectUnit(s.Model.OwnRoster.First().Uid);
yield return Wait(.3f);
yield return Shot(s, "04-einheit-ausgewaehlt");
s.Hud.ClearSelection();
// Attack champions: one booked for the next round.
Send(s, Command.ChampionBook("ch_belagerung", "mid"));
s.Hud.OpenChampions();
yield return Wait(.3f);
yield return Shot(s, "05-angriffschampion");
s.Close();
}
// ---------------------------------------------------------------- boss
IEnumerator BossViews()
{
var s = new HudSession();
yield return Fresh(s);
s.Match.RoundNumber = 10;
s.Match.Boss.NextAvailableRound = 10;
s.Game.FocusTarget("grotto");
s.Sync();
yield return Wait(1f);
yield return Shot(s, "06-boss-verfuegbar");
// The own team conquered the boss in round 11: deployment in round 12 on Mid, planning of 12.
s.Match.RoundNumber = 12;
s.Match.Boss.Claim = new BossClaim { Team = 0, WonRound = 11, DeployRound = 12, ChooserSlot = HudSession.Human, Lane = "mid" };
s.Match.Boss.NextAvailableRound = 15;
s.Game.FocusLane("mid", true);
s.Sync();
yield return Wait(1f);
yield return Shot(s, "07-boss-angekuendigt");
s.Close();
}
// ---------------------------------------------------------------- combat
IEnumerator CombatViews()
{
var s = new HudSession();
yield return Fresh(s);
Armies(s, 14);
s.Match.BuildingHp["rot_top_outer"] = 0.0;
s.Match.BuildingHp["blau_bot_outer"] = 0.0;
s.Game.Key(KeyCode.Space);
yield return HudSession.Frames(1);
Assert.AreEqual(LuneMatchRunner.Stage.Combat, s.Runner.Current, "combat of round 14");
// Godot advance_combat(22): the clock runs to 22 s, then holds.
yield return s.Fight(22.0);
s.Driver.Paused = true;
foreach (var spec in new[] { ("08-kampf-top", "top"), ("09-kampf-mid", "mid"), ("10-kampf-bottom", "bot") })
{
s.Game.FocusLane(spec.Item2, true);
yield return Wait(.5f);
yield return Shot(s, spec.Item1);
}
Vec2 ruin = s.Match.Map.BuildingDef("blau_bot_outer").Position;
s.Rig.LookAt(ruin, 22f, true);
yield return Wait(.5f);
yield return Shot(s, "13-turm-zerstoert");
s.Rig.LookAt(Vec2.Zero, 46f, true);
yield return Wait(.5f);
yield return Shot(s, "14-grosse-kampfansicht");
s.Close();
}
// ---------------------------------------------------------------- Ancients
IEnumerator AncientViews()
{
var s = new HudSession();
yield return Fresh(s);
s.Match.RoundNumber = 21;
foreach (string id in new[] { "blau_mid_outer", "blau_mid_inner", "blau_base_1", "blau_base_2" })
s.Match.BuildingHp[id] = 0.0;
s.Match.BuildingHp["blau_ancient"] = s.Match.Map.BuildingDef("blau_ancient").MaxHp * .22;
s.Game.FocusTarget("base");
s.Sync();
yield return Wait(1.2f);
yield return Shot(s, "11-eigener-ancient-kritisch");
s.Close();
s = new HudSession();
yield return Fresh(s);
s.Match.RoundNumber = 137;
foreach (string id in new[] { "rot_top_outer", "rot_top_inner", "rot_base_1", "rot_base_2" })
s.Match.BuildingHp[id] = 0.0;
s.Match.BuildingHp["rot_ancient"] = s.Match.Map.BuildingDef("rot_ancient").MaxHp * .18;
s.Rig.LookAt(s.Match.Map.CorePosition(1), 26f, true);
s.Sync();
yield return Wait(1.2f);
yield return Shot(s, "12-gegnerischer-ancient-kritisch");
s.Close();
}
}
}