From 056d7afef1e4dc4830cc2e42a79ad2f1de505488 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sat, 18 Apr 2026 15:18:55 +0000 Subject: [PATCH] test: add sidewall scenario builder fixtures --- .../SidewallParkourScenarioBuilder.cs | 89 +++++++++++++++++++ .../SidewallParkourScenarioBuilderTests.cs | 47 ++++++++++ 2 files changed, 136 insertions(+) create mode 100644 MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs create mode 100644 MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs diff --git a/MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs b/MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs new file mode 100644 index 00000000..43fe1220 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/Scenarios/SidewallParkourScenarioBuilder.cs @@ -0,0 +1,89 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Goals; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public static class SidewallParkourScenarioBuilder +{ + private const int SegmentCount = 3; + private const int BaseX = 100; + private const int BaseY = 80; + private const int BaseZ = 100; + private const int FloorY = BaseY - 1; + + public static IEnumerable AcceptedCases() + { + yield return ["sidewall-flat-gap2-wo0", 2, 0, 0]; + yield return ["sidewall-flat-gap3-wo1", 3, 0, 1]; + yield return ["sidewall-ascend-gap2-dy+1-wo0", 2, 1, 0]; + yield return ["sidewall-ascend-gap3-dy+1-wo1", 3, 1, 1]; + yield return ["sidewall-descend-gap2-dy-1-wo0", 2, -1, 0]; + yield return ["sidewall-descend-gap3-dy-1-wo1", 3, -1, 1]; + yield return ["sidewall-descend-gap2-dy-2-wo0", 2, -2, 0]; + yield return ["sidewall-descend-gap3-dy-2-wo1", 3, -2, 1]; + } + + public static IEnumerable RejectedCases() + { + yield return ["sidewall-flat-gap5-wo0", 5, 0, 0]; + yield return ["sidewall-flat-gap5-wo1", 5, 0, 1]; + yield return ["sidewall-ascend-gap4-dy+1-wo0", 4, 1, 0]; + yield return ["sidewall-ascend-gap4-dy+1-wo1", 4, 1, 1]; + yield return ["sidewall-descend-gap6-dy-1-wo0", 6, -1, 0]; + yield return ["sidewall-descend-gap6-dy-1-wo1", 6, -1, 1]; + yield return ["sidewall-descend-gap6-dy-2-wo0", 6, -2, 0]; + yield return ["sidewall-descend-gap6-dy-2-wo1", 6, -2, 1]; + } + + internal static PathingExecutionScenario Create(string scenarioId, int gap, int deltaY, int wallOffset, int maxExecutionTicks = 700) + { + return new PathingExecutionScenario + { + Id = scenarioId, + BuildWorld = () => BuildWorld(gap, deltaY, wallOffset), + Start = new Location(BaseX + 0.5, BaseY, BaseZ + 0.5), + Goal = new GoalBlock(BaseX - SegmentCount, FloorY + (deltaY * SegmentCount) + 1, BaseZ + (gap * SegmentCount)), + StartYaw = 0f, + MaxExecutionTicks = maxExecutionTicks, + }; + } + + internal static World BuildWorld(int gap, int deltaY, int wallOffset) + { + int maxZ = BaseZ + (gap * SegmentCount); + World world = FlatWorldTestBuilder.CreateStoneFloor(floorY: 0, min: 80, max: maxZ + 8); + FlatWorldTestBuilder.ClearBox(world, 90, 70, 90, 110, 96, maxZ + 8); + + int curX = BaseX; + int curY = FloorY; + int curZ = BaseZ; + + FlatWorldTestBuilder.FillSolid(world, curX, curY, curZ - 2, curX, curY, curZ); + + for (int segment = 0; segment < SegmentCount; segment++) + { + int landY = curY + deltaY; + int wallX = curX - 1; + + FlatWorldTestBuilder.FillSolid( + world, + wallX, + Math.Min(curY, landY) - 1, + curZ, + wallX, + Math.Max(curY, landY) + 7, + curZ + wallOffset); + + int landX = curX - 1; + int landZ = curZ + gap; + + FlatWorldTestBuilder.SetSolid(world, landX, landY, landZ); + + curX = landX; + curY = landY; + curZ = landZ; + } + + return world; + } +} diff --git a/MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs b/MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs new file mode 100644 index 00000000..13b985e1 --- /dev/null +++ b/MinecraftClient.Tests/Pathing/Execution/SidewallParkourScenarioBuilderTests.cs @@ -0,0 +1,47 @@ +using MinecraftClient.Mapping; +using Xunit; + +namespace MinecraftClient.Tests.Pathing.Execution; + +public sealed class SidewallParkourScenarioBuilderTests +{ + [Fact] + public void BuildWorld_FlatGap2Wo0_MatchesLiveRouteGeometry() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 2, deltaY: 0, wallOffset: 0); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 98)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 99)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(100, 79, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 79, 102)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(100, 79, 101)).Type); + } + + [Fact] + public void BuildWorld_FlatGap3Wo1_ExtendsWallByTwoBlocksAlongRunwaySide() + { + World world = SidewallParkourScenarioBuilder.BuildWorld(gap: 3, deltaY: 0, wallOffset: 1); + + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 100)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 78, 101)).Type); + Assert.Equal(Material.Air, world.GetBlock(new Location(99, 78, 102)).Type); + Assert.Equal(Material.Stone, world.GetBlock(new Location(99, 79, 103)).Type); + } + + [Fact] + public void Create_FlatGap2Wo0_UsesSameStartAndGoalAsLiveHarness() + { + PathingExecutionScenario scenario = SidewallParkourScenarioBuilder.Create( + "sidewall-flat-gap2-wo0", + gap: 2, + deltaY: 0, + wallOffset: 0); + + Assert.Equal(new Location(100.5, 80, 100.5), scenario.Start); + Assert.Equal(97, scenario.Goal.X); + Assert.Equal(80, scenario.Goal.Y); + Assert.Equal(106, scenario.Goal.Z); + Assert.Equal(0f, scenario.StartYaw); + } +}