champions-of-luneraria/Assets/Luneraria/Core/Model/Vec2.cs
Martin Stickelbroeck 4d6ed17dca U1: Regelkern in C#, verhaltensgleich zur Godot-Referenz
Vollständiger, engineunabhängiger Regelkern (Luneraria.Core, ohne
UnityEngine): Partie mit sechs Plätzen und zwei Teams, private Planung mit
atomarer Übernahme, Gold, Käufe, Aufwertungen, Formation, drei Lanes mit
zwei Laneturmen und zwei Basistürmen je Team, Ancients, unbegrenzte Wellen,
Helden mit Stufen, Items und Talenten, Angriffschampions, Bossgrotte mit
Anspruch, Entsendung, Kopfgeld und dreirundiger Sperre, Bots und
Siegbedingung (nur Ancient-Fall). Serverautoritative Befehlsschnittstelle.

- Deterministisch: fester Takt, geordnete Maps, fortlaufende IDs, kein
  Zufall, keine Uhr, keine Threads
- Tabellen einmal aus Godot erzeugt (tools/port/gen_content.py)
- Golden-Referenzen aus Godot (tools/godot-golden): 19 Szenarien,
  120 Snapshots, 51.221 Werte, Abweichung 0 (Toleranzen dokumentiert)
- Tests: content 75, core 91, m2 73, golden 157, determinism 6 = 402/402
- Leistung (M5): Simulationsschritt 0,259 ms statt 1,587 ms in GDScript
- Referenzfehler R1 im Bot-Gedächtnis bewusst übernommen und dokumentiert
- Geplante Helden (Lucy, Ghaliga, Siegfried, Feena) im Datenmodell, inaktiv
- Doku unter docs/port (U1-RULES, PORT-STATUS, PORT-QA, U2 bis U5)

Unity-Editor-Lauf offen: Die Unity-Lizenz ist noch nicht aktiviert; gebaut
und getestet mit dem .NET 8 SDK auf Unitys Sprachstand (C# 9, .NET
Standard 2.1).

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

143 lines
4.7 KiB
C#

using System;
namespace Luneraria.Core
{
/// <summary>
/// Ground vector (x, z) with 32-bit components and plain single-precision arithmetic, like
/// Godot's Vector2 (tools/godot-golden/float_probe.gd: 4000 of 4000 samples identical).
/// Scalars of the rules are doubles; they are rounded to float where they meet a vector.
/// Matching Godot's arithmetic keeps the golden comparisons tight; the product requirement
/// is determinism on one machine, not identical bits across platforms (docs/port/U1-RULES.md).
/// </summary>
[Serializable]
public struct Vec2 : IEquatable<Vec2>
{
public float X;
public float Y;
public static readonly Vec2 Zero = new Vec2(0f, 0f);
public static readonly Vec2 Right = new Vec2(1f, 0f);
public static readonly Vec2 Left = new Vec2(-1f, 0f);
public Vec2(float x, float y)
{
X = x;
Y = y;
}
/// <summary>From doubles: each component rounded to float.</summary>
public Vec2(double x, double y)
{
X = (float)x;
Y = (float)y;
}
public static Vec2 operator +(Vec2 a, Vec2 b) { return new Vec2(a.X + b.X, a.Y + b.Y); }
public static Vec2 operator -(Vec2 a, Vec2 b) { return new Vec2(a.X - b.X, a.Y - b.Y); }
public static Vec2 operator -(Vec2 a) { return new Vec2(-a.X, -a.Y); }
public static Vec2 operator *(Vec2 a, double s)
{
float f = (float)s;
return new Vec2(a.X * f, a.Y * f);
}
public static Vec2 operator *(double s, Vec2 a)
{
float f = (float)s;
return new Vec2(f * a.X, f * a.Y);
}
public static Vec2 operator /(Vec2 a, double s)
{
float f = (float)s;
return new Vec2(a.X / f, a.Y / f);
}
public static bool operator ==(Vec2 a, Vec2 b) { return a.X == b.X && a.Y == b.Y; }
public static bool operator !=(Vec2 a, Vec2 b) { return !(a == b); }
public float Length() { return MathF.Sqrt(X * X + Y * Y); }
public float LengthSquared() { return X * X + Y * Y; }
public float DistanceTo(Vec2 p)
{
float dx = X - p.X;
float dy = Y - p.Y;
return MathF.Sqrt(dx * dx + dy * dy);
}
public float DistanceSquaredTo(Vec2 p)
{
float dx = X - p.X;
float dy = Y - p.Y;
return dx * dx + dy * dy;
}
public float Dot(Vec2 p) { return X * p.X + Y * p.Y; }
public Vec2 Normalized()
{
float l = X * X + Y * Y;
if (l != 0f)
{
l = MathF.Sqrt(l);
return new Vec2(X / l, Y / l);
}
return this;
}
/// <summary>from + (to - from) * weight per component, weight rounded to float.</summary>
public Vec2 Lerp(Vec2 to, double weight)
{
float w = (float)weight;
return new Vec2(X + (to.X - X) * w, Y + (to.Y - Y) * w);
}
public Vec2 LimitLength(double length)
{
float limit = (float)length;
float l = Length();
Vec2 v = this;
if (l > 0f && limit < l)
{
v.X /= l;
v.Y /= l;
v.X *= limit;
v.Y *= limit;
}
return v;
}
public Vec2 Rotated(double angle)
{
float a = (float)angle;
float sine = MathF.Sin(a);
float cosi = MathF.Cos(a);
return new Vec2(X * cosi - Y * sine, X * sine + Y * cosi);
}
public bool Equals(Vec2 other) { return X.Equals(other.X) && Y.Equals(other.Y); }
public override bool Equals(object obj) { return obj is Vec2 other && Equals(other); }
public override int GetHashCode() { return (X.GetHashCode() * 397) ^ Y.GetHashCode(); }
public override string ToString() { return "(" + X.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + ", " + Y.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + ")"; }
}
/// <summary>Integer grid cell for the spatial buckets of a combat step.</summary>
public struct Vec2I : IEquatable<Vec2I>
{
public int X;
public int Y;
public Vec2I(int x, int y)
{
X = x;
Y = y;
}
public bool Equals(Vec2I other) { return X == other.X && Y == other.Y; }
public override bool Equals(object obj) { return obj is Vec2I other && Equals(other); }
public override int GetHashCode() { return (X * 73856093) ^ (Y * 19349663); }
}
}