champions-of-luneraria/Assets/Luneraria/Tests/UI/PlayMode/FigureViewsTests.cs
Martin Stickelbroeck c386206876 U5.5-pilot Figuren: Meshy-Kohlenbeißer, Lava-Glut, GPU-Skinning
Pilot für eine generierte Figur: der Kohlenbeißer g01 kommt als Meshy-Ergebnis
in den Port und ersetzt dort das Phase-4-Modell. Eine Figur genügt, um Pipeline,
Bild und Leistung zu bewerten; Anatomie, Gesichter und Haare bleiben
Künstler-Sculpts oder lizenzierte Packs (Vorgabe vom 1. Oktober).

- Meshy-Pipeline unter tools/meshy/: API-Client (meshy.py, Ledger, Schlüssel
  nie geloggt) und Blender-Generator (build_figure.py) aus den unveränderten
  Downloads in assets-source/meshy/g01/. Export über den Manifest-Eintrag
  kohlenbeisser mit "replaces": "g01"; LuneUnitLibraryBuilder hängt die Figur
  zusätzlich unter der Godot-ID ein.
- UV-Material AI_Kohlenbeisser (Familie authored): die Karten der Figur selbst,
  Lava-Glut aus der Albedo-Alpha (Meshy 7.1 ohne Emission-Map) mit _Glow 1,25.
- Schatten ohne Normalen-Versatz (LuneShadowBias, _ShadowNormalScale 0): die
  UV-Naht-Kopien tragen geknickte Normalen; URP ApplyShadowBias zog helle
  Nähte durch den Schatten. Die Figur wirft nur mit Tiefen-Bias.
- GPU-Batched-Skinning (PlayerSettings.meshDeformation, LuneProjectSetup):
  35 Meshy-Kohlenbeißer kosteten mit CPU-Skinning rund 1 ms Haupt- und
  1,7 ms Render-Thread je Bild.
- LuneBenchmark: -luneBenchmarkRound und -luneBenchmarkArmy (Figuren-Stress).
  LuneAssetPreview: LUNE_ONLY für Vorher/Nachher, Trefferframe aus der
  Kontaktzeit des Manifests. FigureViewsTests (Explicit) für die vier
  Spielmomente der Figur.
- Tests: EditMode 29/29, PlayMode 14/14 (6 Explicit), Verify 718/0,
  Player-Selbsttest 30/30, Kerntests 457/457, Fingerabdrücke unverändert
  (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).
- Leistung (A/B im selben Fenster, 35x g01): Meshy -21 % Mid (109,7 zu 138,8)
  und -13 % Karte (91,7 zu 105,6) gegenüber Phase-4. GPU-Skinning hebt Mid
  um 7-27 % und entlastet Haupt- und Render-Thread. Standard-Partie ab Runde 21
  ohne g01 im Bild: drei Varianten bis 6 %.
- Doku: U5-VISUAL (U5.5), PORT-QA, PORT-STATUS, KNOWN-BEHAVIOR-DIFFERENCES;
  Bilder screenshots/u5.5/ und u5.5-gpu/, Rohdaten logs/u5.5/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:14:59 +02:00

92 lines
4.1 KiB
C#

using System;
using System.Collections;
using System.Linq;
using Luneraria.Core;
using Luneraria.View;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Luneraria.UI.Tests
{
/// <summary>
/// U5.5 pilot pictures of one figure in the real game (the Meshy Kohlenbeißer against the
/// Phase-4 model it replaces): the human seat plays the Glutpakt, buys the starter army and
/// four Kohlenbeißer (g01) in round 3, and the camera shows them in the planning formation,
/// in the HUD portrait and in the fight on the own lane, close (zoom 12) and at the benchmark's
/// lane distance (zoom 22). Like HudViewsTests, commands go straight to the match followed by
/// the preview rebuild. A capture tool, not part of the suite:
/// -testFilter "Luneraria.UI.Tests.FigureViewsTests"; LUNE_EVIDENCE names the folder below
/// docs/port/screenshots (default u5.5/spiel).
/// </summary>
[Explicit("Bildwerkzeug: nur gezielt per -testFilter")]
public sealed class FigureViewsTests
{
/// <summary>The human seat 1 plays the Glutpakt, as do the bots of seats 2 and 5.</summary>
static readonly string[] Bunds = { "wurzelbund", "glutpakt", "glutpakt", "wurzelbund", "wurzelbund", "glutpakt" };
string _folder;
IEnumerator Shot(HudSession s, string label)
{
yield return s.Shot(_folder + label + ".png");
}
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>The own Kohlenbeißer on the field: the one nearest to the enemy (the front of the fight).</summary>
static LuneUnit Front(HudSession s)
{
int team = s.Me.Team;
return s.Match.Combat.Units.Where(u => u.Alive && u.Team == team && u.Def.Id == "g01")
.OrderByDescending(u => u.Progress).FirstOrDefault();
}
[UnityTest, Timeout(900000)]
public IEnumerator Kohlenbeisser()
{
string evidence = Environment.GetEnvironmentVariable("LUNE_EVIDENCE");
_folder = "docs/port/screenshots/" + (string.IsNullOrEmpty(evidence) ? "u5.5/spiel" : evidence) + "/";
var s = new HudSession();
yield return s.Open(1920, 1080, seed: 4, bunds: Bunds);
s.Match.RoundNumber = 3;
s.Me.Draft.Gold = 900;
yield return s.Starter();
s.Leave();
for (int i = 0; i < 4; i++)
Send(s, Command.Buy("g01", s.Me.Lane));
yield return new WaitForSecondsRealtime(.4f);
RosterEntry first = s.Me.Draft.Roster.First(e => e.TypeId == "g01");
// The planning preview stands the army in its formation on the own lane.
LuneUnit waiting = s.View.Preview?.Units.FirstOrDefault(u => u.Team == s.Me.Team && u.Def.Id == "g01");
Assert.IsNotNull(waiting, "Kohlenbeißer in the planning preview");
s.Rig.LookAt(waiting.Position, 12f, true);
yield return new WaitForSecondsRealtime(.5f);
yield return Shot(s, "01-aufstellung-nah");
s.Hud.SelectUnit(first.Uid);
yield return new WaitForSecondsRealtime(.4f);
yield return Shot(s, "02-portraet");
s.Hud.ClearSelection();
s.Game.Key(KeyCode.Space);
yield return HudSession.Frames(1);
Assert.AreEqual(LuneMatchRunner.Stage.Combat, s.Runner.Current, "combat of round 3");
yield return s.Fight(7.0);
s.Driver.Paused = true;
LuneUnit front = Front(s);
Assert.IsNotNull(front, "a Kohlenbeißer on the field");
s.Rig.LookAt(front.Position, 12f, true);
yield return new WaitForSecondsRealtime(.5f);
yield return Shot(s, "03-kampf-nah");
s.Rig.LookAt(front.Position, 22f, true);
yield return new WaitForSecondsRealtime(.5f);
yield return Shot(s, "04-kampf-lane");
s.Close();
}
}
}