Add Ehwrj clean-room live map
Some checks failed
build / build-test-publish (push) Has been cancelled

This commit is contained in:
2026-06-02 22:49:24 +09:00
parent c93ab38cbd
commit cba5243ce4
71 changed files with 5990 additions and 9 deletions

View File

@@ -0,0 +1,322 @@
using Ehwrj.App.Models;
using Ehwrj.Core.Geometry;
using Ehwrj.Core.Models;
using Ehwrj.Core.Services;
namespace Ehwrj.Tests;
internal static class Program
{
private static int Main()
{
try
{
ParsesMapInfoGridSize();
ParsesMapInfoBoundsAndStringNumbers();
ParsesObjectsAndClassifiesAircraft();
ParsesObjectCoordinateAndDirectionArrays();
ClassifiesTeamsAndObjectives();
ParsesHeadingInDegrees();
ParsesFlightState();
ParsesBattleMessages();
AnalyzesCaptureFixtureQuality();
EnforcesLoopbackNetworkScope();
LocalizesUiTextAndStatus();
ParsesWrappedObjectList();
ProjectsNormalizedCoordinates();
ProjectsBoundedCoordinates();
Console.WriteLine("All tests passed.");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
return 1;
}
}
private static void ParsesMapInfoGridSize()
{
var info = MapInfo.FromJson("""{"grid_size":[10,20],"grid_steps":[1,2]}""");
AssertEqual(0, info.MinX, "MinX");
AssertEqual(10, info.MaxX, "MaxX");
AssertEqual(0, info.MinY, "MinY");
AssertEqual(20, info.MaxY, "MaxY");
AssertEqual(1, info.GridStepX, "GridStepX");
AssertEqual(2, info.GridStepY, "GridStepY");
}
private static void ParsesMapInfoBoundsAndStringNumbers()
{
var info = MapInfo.FromJson(
"""
{
"bounds": [["-1,5","-2.25"],["3,5","4.25"]],
"grid_steps": ["0,5","1.25"]
}
""");
AssertEqual(-1.5, info.MinX, "bounds MinX");
AssertEqual(3.5, info.MaxX, "bounds MaxX");
AssertEqual(-2.25, info.MinY, "bounds MinY");
AssertEqual(4.25, info.MaxY, "bounds MaxY");
AssertEqual(0.5, info.GridStepX, "string GridStepX");
AssertEqual(1.25, info.GridStepY, "string GridStepY");
}
private static void ParsesObjectsAndClassifiesAircraft()
{
var tracker = new ObjectTracker();
var objects = MapObject.FromJson(
"""
[
{"id":"self","type":"player_aircraft","x":0.5,"y":0.5},
{"id":"bandit","type":"aircraft","x":0.6,"y":0.6},
{"id":"zone","type":"capture_zone","x":0.2,"y":0.3}
]
""",
tracker);
AssertEqual(3, objects.Count, "object count");
AssertTrue(objects[0].IsPlayer, "player classification");
AssertTrue(objects[1].IsAircraft, "aircraft classification");
AssertTrue(!objects[2].IsAircraft, "zone classification");
}
private static void ParsesObjectCoordinateAndDirectionArrays()
{
var tracker = new ObjectTracker();
var objects = MapObject.FromJson(
"""
[
{
"id":"array-bandit",
"icon":"fighter",
"team":"enemy",
"pos":["0,25","0.75"],
"direction":["1","0"],
"heading":"180"
}
]
""",
tracker);
AssertEqual(0.25, objects[0].X, "array x");
AssertEqual(0.75, objects[0].Y, "array y");
AssertEqual(1, objects[0].DirectionX, "array direction x");
AssertEqual(0, objects[0].DirectionY, "array direction y");
AssertEqual(Math.PI, objects[0].HeadingRadians.GetValueOrDefault(), "string heading radians");
AssertEqual((int)MapObjectKind.Enemy, (int)objects[0].Kind, "array enemy kind");
}
private static void ClassifiesTeamsAndObjectives()
{
var tracker = new ObjectTracker();
var objects = MapObject.FromJson(
"""
[
{"id":"ally","type":"aircraft","team":"ally","x":0.1,"y":0.2},
{"id":"enemy","type":"aircraft","team":"enemy","x":0.2,"y":0.3},
{"id":"squad","type":"aircraft","team":"squad","x":0.3,"y":0.4},
{"id":"target","type":"bombing_point","team":"enemy","x":0.4,"y":0.5}
]
""",
tracker);
AssertEqual((int)MapObjectKind.Ally, (int)objects[0].Kind, "ally kind");
AssertEqual((int)MapObjectKind.Enemy, (int)objects[1].Kind, "enemy kind");
AssertEqual((int)MapObjectKind.Squad, (int)objects[2].Kind, "squad kind");
AssertTrue(objects[3].IsEnemyBombingPoint, "enemy bombing point");
}
private static void ParsesHeadingInDegrees()
{
var tracker = new ObjectTracker();
var objects = MapObject.FromJson("""[{"id":"p","type":"player_aircraft","heading":90,"x":0.5,"y":0.5}]""", tracker);
AssertTrue(objects[0].HeadingRadians.HasValue, "heading exists");
AssertEqual(Math.PI / 2, objects[0].HeadingRadians.GetValueOrDefault(), "heading radians");
}
private static void ParsesFlightState()
{
var state = FlightState.FromJson("""{"TAS, km/h":720,"IAS, km/h":"650","Vy, m/s":36,"H, m":2500}""");
AssertEqual(720, state.TrueAirspeedKmh, "tas");
AssertEqual(650, state.IndicatedAirspeedKmh, "ias");
AssertTrue(state.Mach.HasValue, "mach");
AssertTrue(state.ClimbAngleDegrees.HasValue, "climb angle");
}
private static void ParsesBattleMessages()
{
var messages = BattleMessage.FromJson(
"""
{
"events": [
{"text":"<b>Enemy spotted</b>","enemy":true},
{"message":"Ally captured A","enemy":false}
]
}
""");
AssertEqual(2, messages.Count, "message count");
AssertEqual("Enemy spotted", messages[0].Text, "cleaned message");
AssertTrue(messages[0].Enemy == true, "enemy flag");
AssertTrue(messages[1].Enemy == false, "ally flag");
}
private static void AnalyzesCaptureFixtureQuality()
{
var directory = Path.Combine(Path.GetTempPath(), $"ehwrj-capture-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(directory);
try
{
File.WriteAllText(Path.Combine(directory, "map_info.json"), """{"grid_size":[10,20],"grid_steps":[1,2]}""");
File.WriteAllText(
Path.Combine(directory, "map_obj.json"),
"""
[
{"id":"self","type":"player_aircraft","team":"ally","x":0.5,"y":0.5},
{"id":"wing","type":"aircraft","team":"ally","x":0.4,"y":0.5},
{"id":"bandit","type":"aircraft","team":"enemy","x":0.6,"y":0.5},
{"id":"target","type":"bombing_point","team":"enemy","x":0.7,"y":0.5},
{"id":"mystery","type":"supply_marker","x":0.2,"y":0.8,"custom_flag":"sample"}
]
""");
File.WriteAllBytes(Path.Combine(directory, "map.img"), [1, 2, 3, 4]);
File.WriteAllText(Path.Combine(directory, "state.json"), """{"TAS, km/h":720,"H, m":2500}""");
File.WriteAllText(Path.Combine(directory, "hudmsg.json"), """{"events":[{"text":"Enemy spotted","enemy":true}]}""");
File.WriteAllText(Path.Combine(directory, "gamechat.json"), """["Good luck"]""");
var report = CaptureFixtureAnalyzer.AnalyzeDirectory(directory);
AssertTrue(report.IsUsableForReplay, "capture replay usable");
AssertEqual(5, report.ObjectCount, "capture object count");
AssertEqual(1, report.PlayerCount, "capture player count");
AssertEqual(2, report.AlliedAircraftCount, "capture allied aircraft count");
AssertEqual(1, report.EnemyAircraftCount, "capture enemy aircraft count");
AssertEqual(1, report.ObjectiveCount, "capture objective count");
AssertEqual(1, report.UnknownObjectCount, "capture unknown object count");
AssertTrue(report.ObjectFields.Any(static f => f.Name == "custom_flag" && f.Count == 1), "capture object field coverage");
AssertTrue(report.UnknownObjectSamples.Any(static s => s.Contains("supply_marker", StringComparison.Ordinal)), "capture unknown sample");
AssertTrue(report.StateFieldNames.Contains("TAS, km/h", StringComparer.Ordinal), "capture state field names");
AssertTrue(report.HasSpeedTelemetry, "capture speed telemetry");
AssertTrue(report.HasAltitudeTelemetry, "capture altitude telemetry");
AssertEqual(1, report.HudMessageCount, "capture hud messages");
AssertEqual(1, report.ChatMessageCount, "capture chat messages");
AssertTrue(report.ToText().Contains("Replay usable: yes", StringComparison.Ordinal), "capture report text");
AssertTrue(report.ToText().Contains("Object field coverage:", StringComparison.Ordinal), "capture field report text");
}
finally
{
Directory.Delete(directory, recursive: true);
}
}
private static void EnforcesLoopbackNetworkScope()
{
AssertTrue(LoopbackGuard.IsLoopbackHttp(new Uri("http://127.0.0.1:8111/")), "127 loopback allowed");
AssertTrue(LoopbackGuard.IsLoopbackHttp(new Uri("http://localhost:8111/")), "localhost allowed");
AssertTrue(LoopbackGuard.IsLoopbackHost("127.0.0.1"), "127 host allowed");
AssertTrue(LoopbackGuard.IsLoopbackHost("localhost"), "localhost host allowed");
AssertTrue(!LoopbackGuard.IsLoopbackHttp(new Uri("https://127.0.0.1:8111/")), "https rejected");
AssertTrue(!LoopbackGuard.IsLoopbackHttp(new Uri("http://example.com:8111/")), "external host rejected");
AssertTrue(!LoopbackGuard.IsLoopbackHost("0.0.0.0"), "wildcard host rejected");
}
private static void LocalizesUiTextAndStatus()
{
var english = UiText.For("en-US");
var korean = UiText.For("ko-KR");
AssertEqual("Language", english.Language, "english language label");
AssertEqual("언어", korean.Language, "korean language label");
AssertEqual("127.0.0.1:8111 연결됨", korean.FormatStatus("Connected to 127.0.0.1:8111"), "localized connected status");
AssertTrue(UiText.LanguageOptions.Any(static o => o.Code == "ko-KR"), "korean language option");
}
private static void ProjectsNormalizedCoordinates()
{
var point = CoordinateProjector.Project(
new MapObject { Id = "p", X = 0.25, Y = 0.75 },
MapInfo.Empty,
new MapViewport(0, 0, 400, 200));
AssertTrue(point.HasValue, "projection exists");
var projected = point.GetValueOrDefault();
AssertEqual(100, projected.X, "projected x");
AssertEqual(150, projected.Y, "projected y");
}
private static void ParsesWrappedObjectList()
{
var tracker = new ObjectTracker();
var objects = MapObject.FromJson(
"""
{
"objects": [
{"id":"a","icon":"fighter","x":"0.1","y":"0.9"}
]
}
""",
tracker);
AssertEqual(1, objects.Count, "wrapped object count");
AssertTrue(objects[0].IsAircraft, "wrapped aircraft classification");
AssertEqual(0.1, objects[0].X, "wrapped x");
AssertEqual(0.9, objects[0].Y, "wrapped y");
}
private static void ProjectsBoundedCoordinates()
{
var info = new MapInfo(-100, 100, -50, 50, null, null, null);
var point = CoordinateProjector.Project(
new MapObject { Id = "p", X = 0, Y = 25 },
info,
new MapViewport(10, 20, 200, 100));
AssertTrue(point.HasValue, "bounded projection exists");
var projected = point.GetValueOrDefault();
AssertEqual(110, projected.X, "bounded x");
AssertEqual(95, projected.Y, "bounded y");
}
private static void AssertTrue(bool condition, string name)
{
if (!condition)
{
throw new InvalidOperationException($"Assertion failed: {name}");
}
}
private static void AssertEqual(double expected, double? actual, string name)
{
if (!actual.HasValue || Math.Abs(expected - actual.Value) > 0.0001)
{
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
}
}
private static void AssertEqual(double expected, double actual, string name)
{
if (Math.Abs(expected - actual) > 0.0001)
{
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
}
}
private static void AssertEqual(int expected, int actual, string name)
{
if (expected != actual)
{
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
}
}
private static void AssertEqual(string expected, string actual, string name)
{
if (!string.Equals(expected, actual, StringComparison.Ordinal))
{
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
}
}
}