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>
113 lines
3.8 KiB
C#
113 lines
3.8 KiB
C#
using System;
|
|
using Luneraria.Core;
|
|
|
|
namespace Luneraria.View
|
|
{
|
|
/// <summary>
|
|
/// Runs a bot match on the host (U2C/U2D) against wall-clock time: planning (the six bots plan
|
|
/// through the command interface), combat in fixed steps, a pause after the settlement, the
|
|
/// next round, until an Ancient falls. Only the core decides; time only paces the calls, so
|
|
/// the sequence of core calls is the one of LuneTrace.RunCore at every frame rate.
|
|
/// </summary>
|
|
public sealed class LuneMatchRunner
|
|
{
|
|
public enum Stage { Planning, Combat, Settled, Over }
|
|
|
|
public readonly LuneMatch Match;
|
|
public readonly LuneClock Clock;
|
|
public readonly string Profile;
|
|
public LuneTrace Trace;
|
|
public double PlanningSeconds = 1.0;
|
|
public double SettledSeconds = 2.0;
|
|
public int MaxRound = 60;
|
|
public Stage Current { get; private set; }
|
|
public int StepsThisFrame { get; private set; }
|
|
|
|
public event Action<LuneCombat> CombatStarted;
|
|
public event Action<Settlement> Settled;
|
|
public event Action<int> Finished;
|
|
|
|
double _stageTime;
|
|
|
|
public LuneMatchRunner(MatchOptions options, int startRound = 1, int extraGold = 0, string profile = "")
|
|
{
|
|
Match = NewMatch(options, startRound, extraGold);
|
|
Clock = new LuneClock(Match.Balance.SimStep);
|
|
Profile = profile;
|
|
EnterPlanning();
|
|
}
|
|
|
|
/// <summary>A match with the public fixtures the golden scenarios use (start round, gold).</summary>
|
|
public static LuneMatch NewMatch(MatchOptions options, int startRound, int extraGold)
|
|
{
|
|
var match = new LuneMatch();
|
|
match.Setup(options);
|
|
if (startRound > 1)
|
|
match.RoundNumber = startRound;
|
|
if (extraGold != 0)
|
|
foreach (Player player in match.Players)
|
|
player.Draft.Gold += extraGold;
|
|
return match;
|
|
}
|
|
|
|
void EnterPlanning()
|
|
{
|
|
Current = Stage.Planning;
|
|
_stageTime = 0.0;
|
|
for (int slot = 0; slot < 6; slot++)
|
|
LuneBot.Plan(Match, slot, Profile);
|
|
}
|
|
|
|
/// <summary>Advances by simulated seconds (frame time times match speed).</summary>
|
|
public void Advance(double seconds)
|
|
{
|
|
StepsThisFrame = 0;
|
|
switch (Current)
|
|
{
|
|
case Stage.Planning:
|
|
_stageTime += seconds;
|
|
if (_stageTime >= PlanningSeconds)
|
|
{
|
|
Match.BeginCombat();
|
|
Clock.Reset();
|
|
Current = Stage.Combat;
|
|
CombatStarted?.Invoke(Match.Combat);
|
|
}
|
|
break;
|
|
case Stage.Combat:
|
|
StepsThisFrame = Clock.Advance(seconds, StepOnce);
|
|
if (Match.Phase != Phase.Combat)
|
|
EndCombat();
|
|
break;
|
|
case Stage.Settled:
|
|
_stageTime += seconds;
|
|
if (_stageTime >= SettledSeconds)
|
|
EnterPlanning();
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool StepOnce()
|
|
{
|
|
Match.StepCombat();
|
|
Trace?.RecordStep(Match.Combat);
|
|
return Match.Phase == Phase.Combat;
|
|
}
|
|
|
|
void EndCombat()
|
|
{
|
|
Trace?.RecordSettlement(Match);
|
|
Settled?.Invoke(Match.LastSettlement);
|
|
_stageTime = 0.0;
|
|
if (Match.Phase == Phase.Over || Match.RoundNumber > MaxRound)
|
|
{
|
|
Current = Stage.Over;
|
|
Finished?.Invoke(Match.Winner);
|
|
}
|
|
else
|
|
{
|
|
Current = Stage.Settled;
|
|
}
|
|
}
|
|
}
|
|
}
|