mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
test: add zero-replan live pathing harness
This commit is contained in:
parent
b0fab26c66
commit
aefc7235ee
7 changed files with 3444 additions and 146 deletions
|
|
@ -258,8 +258,57 @@ The new regression harness in `tools/test-pathing-template-regressions.sh` codif
|
|||
4. A 3×1 no-run-up rejection to prevent non-executable plans from sneaking through.
|
||||
5. Mixed ascend/descend/climb smoke cases so that both vertical transitions and ladder climbs respect the reliable support requirement.
|
||||
|
||||
## Deterministic live route contract
|
||||
|
||||
For the short-route and long-route `1.21.11-Vanilla` live harnesses, accepted routes must complete with all of the following:
|
||||
|
||||
- `A* result: Success`
|
||||
- `0 replan`
|
||||
- `0` template segment failures
|
||||
- final position inside the intended goal support block
|
||||
- `PathMgr` reporting `Navigation complete!`
|
||||
|
||||
For rejection scenarios, the requirement is stricter:
|
||||
|
||||
- `A* result: Failed` or `No path found`
|
||||
- no navigation start
|
||||
- no executor-driven `replan`
|
||||
|
||||
Residual speed carried from one movement to the next inside a route is expected and must not be normalized away just to satisfy the harness. The route is only considered reliable if that natural speed carry still produces `0 replan`.
|
||||
|
||||
## Baritone Reference Notes For Zero-Replan Work
|
||||
|
||||
MCC can borrow specific ideas from the local Baritone reference under `ThirdpartyReference/baritone/`, but not its looser success semantics.
|
||||
|
||||
Borrow:
|
||||
|
||||
- landing-aware completion, where movement logic keeps controlling after touchdown instead of failing immediately
|
||||
- next-movement-aware descend and ascend handoff behavior
|
||||
- conservative parkour admissibility, especially around run-up, overshoot, and blocked landing shapes
|
||||
- executor timeout and movement-stuck heuristics as diagnostic input, not as acceptance criteria
|
||||
|
||||
Do not borrow:
|
||||
|
||||
- `GoalBlock` occupancy semantics as a substitute for deterministic execution quality
|
||||
- executor repath tolerance as proof that a movement is reliable
|
||||
- any behavior that lets accepted deterministic harness routes succeed only by falling back to `replan`
|
||||
|
||||
For this work, Baritone is a movement-control reference, not a correctness oracle. MCC's accepted live routes must still finish with `0 replan` in the deterministic harness.
|
||||
|
||||
Keeping the rule explicit here reminds future contributors that the planner should never promise a move that physically cannot finish with block contact.
|
||||
|
||||
## Regression Harness Workflow
|
||||
|
||||
The scripts in `tools/` now match the `mcc-dev-workflow` defaults: they call
|
||||
`source tools/mcc-env.sh`, rely on a shared `mc-*` server running `1.21.11-Vanilla`,
|
||||
and launch MCC through `mcc-build`, `mcc-debug`, and `mcc-cmd` wrappers. The
|
||||
harnesses reuse the existing server session instead of stopping and restarting
|
||||
it, which keeps shared test infrastructure stable and honors the instruction to
|
||||
keep `mc-*` servers running unless another version or explicit reset is required.
|
||||
When editing or extending the harness, preserve the `mcc-*` invocation pattern
|
||||
and the existing log/tail helpers so the scripts stay compatible with the updated
|
||||
workflow.
|
||||
|
||||
## References
|
||||
|
||||
- [Minecraft Parkour Wiki: Blip](https://www.mcpk.wiki/wiki/Blip)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
736
docs/superpowers/plans/2026-04-13-zero-replan-live-pathing.md
Normal file
736
docs/superpowers/plans/2026-04-13-zero-replan-live-pathing.md
Normal file
|
|
@ -0,0 +1,736 @@
|
|||
# Zero Replan Live Pathing Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Eliminate executor-driven replans in deterministic live harness scenarios on `1.21.11-Vanilla`, and add longer-route stability coverage that proves accepted routes finish with `0 replan`.
|
||||
|
||||
**Architecture:** Treat any `replan` in accepted deterministic harness scenarios as a bug, not an acceptable recovery path. First lock down harness isolation and `0 replan` assertions so failures are explicit. Then remove per-move-family completion/failure gaps in `WalkTemplate`, `AscendTemplate`, and `DescendTemplate`, and finally add longer multi-segment live routes that combine operations without planner partials or executor retries.
|
||||
|
||||
**Tech Stack:** C# 14 / .NET 10, xUnit, MCC local `1.21.11-Vanilla` harness via `tools/mcc-env.sh`, tmux/RCON-driven live tests, MCC pathing core and execution templates.
|
||||
|
||||
---
|
||||
|
||||
## Current Facts
|
||||
|
||||
Latest live evidence from `/tmp/mcc-debug/*/mcc-debug.log`:
|
||||
|
||||
- `Flat final stop`: `2` replans
|
||||
- `Traverse` failed at `(101.56, 80.00, 100.74)`
|
||||
- `Traverse/FinalStop` failed at `(103.36, 80.00, 100.50)`
|
||||
- `Parkour into turn`: `1` replan
|
||||
- final `Traverse/FinalStop` failed around `(122.50, 80.00, 111.36)`
|
||||
- `Corner ascend around wall`: `1` replan
|
||||
- single `Ascend/FinalStop` failed around `(191.38, 81.00, 171.38)`
|
||||
- `Wall-adjacent descend`: `1` replan
|
||||
- single `Descend/FinalStop` failed at `(201.50, 80.00, 200.50)`
|
||||
- `Ascend chain smoke`: `3` replans
|
||||
- all failures occur on chained `Ascend` segments before the partial fallback stops at `(177.46, 83.00, 162.50)`
|
||||
- `Rejected 3x1 no-run-up gap`: currently not a clean reject
|
||||
- planner returns a `Partial` path, then execution replans before failing
|
||||
- `Rejected 2x1 side-wall jump`: already correct, `0 replan`, direct `A* Failed`
|
||||
|
||||
## User Constraints
|
||||
|
||||
These constraints override earlier assumptions in this plan:
|
||||
|
||||
- MCC currently has no entity collision implementation that would make other players or mobs perturb these tests.
|
||||
- Other players being online is not itself a movement interference source for this work.
|
||||
- Residual yaw/pitch between independent scenarios is not a fix target for this plan; templates already steer every tick.
|
||||
- Residual speed should be recorded for diagnosis, but not “normalized away” inside a route.
|
||||
- For long accepted routes, residual speed between internal actions is expected and must not be treated as test interference. The route should still finish with `0 replan`.
|
||||
|
||||
## Interference Inventory
|
||||
|
||||
The harness already disables or controls the environmental variables that matter for this work:
|
||||
|
||||
- `difficulty peaceful`
|
||||
- `gamerule doMobSpawning false`
|
||||
- fixed test geometry with `fill`/`setblock`
|
||||
- explicit `tp` before each scenario
|
||||
|
||||
Remaining sources of ambiguity that still matter before claiming `0 replan`:
|
||||
|
||||
1. Shared live server state
|
||||
- The workflow uses shared `mc-*` tmux sessions.
|
||||
- This matters for repeatability and logging, but not because of entity collision.
|
||||
|
||||
2. Reused MCC session state across scenarios
|
||||
- The harness runs multiple scenarios in the same MCC session.
|
||||
- Residual speed can carry across scenario boundaries if the next scenario starts too early.
|
||||
- Yaw/pitch carryover is not considered a blocker for this plan.
|
||||
|
||||
3. Planner timeout / partial-path behavior
|
||||
- Some “reject” or long-route scenarios currently hit `A* result: Partial`.
|
||||
- Those cases cannot be used as `0 replan` executor proofs until the route size is kept below timeout, or the test is explicitly categorized as a planner-partial case.
|
||||
|
||||
4. Current live harness acceptance is weaker than target behavior
|
||||
- The harness was updated to accept “already in goal block” completion.
|
||||
- That is useful for keeping live validation running, but it currently masks the stronger requirement that accepted deterministic routes should need `0 replan`.
|
||||
|
||||
5. Residual-speed observability is currently weak
|
||||
- The harness captures final location, but it does not systematically record pre-route speed and per-route terminal speed.
|
||||
- We should measure speed, not try to zero it between internal actions.
|
||||
|
||||
## Zero-Replan Contract
|
||||
|
||||
For this plan, an accepted live pathing scenario passes only if all of the following are true:
|
||||
|
||||
- `A* result: Success`
|
||||
- no `A* result: Partial`
|
||||
- no `Replan #`
|
||||
- no `Segment .* FAILED`
|
||||
- no `Replan failed -- no path found`
|
||||
- final MCC location is inside the intended goal support block
|
||||
- `PathMgr` reaches `Navigation complete!`
|
||||
|
||||
For rejection scenarios, pass only if:
|
||||
|
||||
- `A* result: Failed` or `No path found`
|
||||
- no `Navigation started`
|
||||
- no `Replan #`
|
||||
|
||||
## File Structure
|
||||
|
||||
### Production code
|
||||
|
||||
- `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs`
|
||||
- Traverse and diagonal segment runtime; primary target for straight-line and turn-entry `0 replan`.
|
||||
- `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- Single-step ascend execution; primary target for ascend landing and chained ascend stability.
|
||||
- `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs`
|
||||
- Landing/final-stop descend execution.
|
||||
- `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- Shared grounded completion and braking gate; likely the common source of “already good enough, but segment still fails one tick later”.
|
||||
- `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs`
|
||||
- Shared geometry and settle helpers.
|
||||
- `MinecraftClient/Pathing/Execution/PathSegmentManager.cs`
|
||||
- Replan handling and live navigation orchestration.
|
||||
- `MinecraftClient/Pathing/Core/AStarPathFinder.cs`
|
||||
- Planner timeout and partial-path behavior.
|
||||
- `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs`
|
||||
- Run-up / jump-feasibility checks for the `3x1` rejection case.
|
||||
- `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs`
|
||||
- Supporting parkour admissibility logic.
|
||||
- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/path/PathExecutor.java`
|
||||
- Reference executor behavior for timeout, splice/repath, and movement handoff semantics.
|
||||
- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java`
|
||||
- Reference traverse completion semantics.
|
||||
- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementAscend.java`
|
||||
- Reference ascend landing and post-jump settle logic.
|
||||
- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementDescend.java`
|
||||
- Reference descend safe-mode and landing behavior.
|
||||
- `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementParkour.java`
|
||||
- Reference parkour admissibility and handoff behavior.
|
||||
|
||||
### Tests and harness
|
||||
|
||||
- `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- Deterministic convergence tests for walk, descend, and future ascend/diagonal cases.
|
||||
- `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs`
|
||||
- Manager-level replan and already-in-goal behavior.
|
||||
- `MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs`
|
||||
- Multi-segment executor behavior.
|
||||
- `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
- Parkour and landing handoff cases.
|
||||
- `tools/test-transition-braking.sh`
|
||||
- Short accepted-route live harness.
|
||||
- `tools/test-pathing-template-regressions.sh`
|
||||
- Current mixed live regression suite.
|
||||
- `tools/test-pathing-long-routes.sh`
|
||||
- New long-route `0 replan` live suite.
|
||||
- `docs/guide/pathfinding-research.md`
|
||||
- Pathing behavior contract and live test expectations.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Freeze the Zero-Replan Harness Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `tools/test-transition-braking.sh`
|
||||
- Modify: `tools/test-pathing-template-regressions.sh`
|
||||
- Create: `tools/test-pathing-long-routes.sh`
|
||||
- Modify: `docs/guide/pathfinding-research.md`
|
||||
|
||||
- [ ] **Step 1: Add live log helpers that fail on any accepted-route replan**
|
||||
|
||||
Add shell helpers to both existing harness scripts:
|
||||
|
||||
```bash
|
||||
count_replans_since() {
|
||||
local from_line="$1"
|
||||
log_since "$from_line" | grep -Ec "\\[PathMgr\\] Replan #|\\[PathExec\\] Segment .* FAILED" || true
|
||||
}
|
||||
|
||||
assert_no_replans_since() {
|
||||
local from_line="$1"
|
||||
local count
|
||||
count="$(count_replans_since "$from_line")"
|
||||
if [[ "$count" != "0" ]]; then
|
||||
echo "Expected 0 replans, saw $count" >&2
|
||||
log_since "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_no_partial_since() {
|
||||
local from_line="$1"
|
||||
if log_since "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then
|
||||
echo "Expected full success path, saw partial path" >&2
|
||||
log_since "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Normalize only the state that should differ between independent scenarios**
|
||||
|
||||
For accepted live scenarios, add:
|
||||
|
||||
```bash
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME 100.5 80 100.5" >/dev/null
|
||||
sleep 2
|
||||
send_mcc "debug state"
|
||||
```
|
||||
|
||||
Do not add special yaw/pitch normalization. Record the pre-route state instead.
|
||||
Do reset position and allow enough time that residual speed from the previous independent scenario is observable in logs.
|
||||
|
||||
- [ ] **Step 3: Separate accepted-route assertions from rejection-route assertions**
|
||||
|
||||
Update accepted-route scenarios to require:
|
||||
|
||||
```bash
|
||||
wait_for_navigation "$start_line" 30
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
```
|
||||
|
||||
Update rejection scenarios to require:
|
||||
|
||||
```bash
|
||||
wait_for_failure_signal "$start_line" 20
|
||||
if log_since "$start_line" | grep -Eq "\\[PathMgr\\] Replan #|\\[PathExec\\] Segment .* FAILED"; then
|
||||
echo "Expected direct rejection, saw execution replan" >&2
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Create a new long-route live harness**
|
||||
|
||||
Create `tools/test-pathing-long-routes.sh` with three accepted-route buckets:
|
||||
|
||||
```bash
|
||||
run_same_move_routes
|
||||
run_mixed_move_routes
|
||||
run_turn_density_routes
|
||||
```
|
||||
|
||||
Each accepted route must assert:
|
||||
|
||||
```bash
|
||||
wait_for_navigation "$start_line" 45
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "$goal_x" "$goal_y" "$goal_z"
|
||||
```
|
||||
|
||||
Each accepted route must also log:
|
||||
|
||||
```bash
|
||||
capture_debug_state_before_route
|
||||
capture_debug_state_after_route
|
||||
```
|
||||
|
||||
to record start/end speed and location for diagnosis. Do not reset any state between internal actions of a single route.
|
||||
|
||||
- [ ] **Step 5: Document the zero-replan live contract**
|
||||
|
||||
Add a short section to `docs/guide/pathfinding-research.md`:
|
||||
|
||||
```md
|
||||
### Deterministic live route contract
|
||||
|
||||
For the short-route and long-route 1.21.11 live harnesses, accepted routes must complete with:
|
||||
|
||||
- `A* result: Success`
|
||||
- `0 replan`
|
||||
- `0` template segment failures
|
||||
- final position inside the goal support block
|
||||
|
||||
Rejection scenarios must fail before execution starts.
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Run harness baselines and record current failures**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh
|
||||
bash tools/test-transition-braking.sh 1.21.11-Vanilla
|
||||
bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected right now: FAIL because the accepted scenarios still produce replans.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Baritone Reference Pass Before Code Changes
|
||||
|
||||
**Files:**
|
||||
- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/path/PathExecutor.java`
|
||||
- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java`
|
||||
- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementAscend.java`
|
||||
- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementDescend.java`
|
||||
- Read: `ThirdpartyReference/baritone/src/main/java/baritone/pathing/movement/movements/MovementParkour.java`
|
||||
- Modify: `docs/guide/pathfinding-research.md`
|
||||
|
||||
- [ ] **Step 1: Extract only the behavior that applies to MCC’s zero-replan goal**
|
||||
|
||||
Summarize, in MCC terms:
|
||||
|
||||
- when Baritone considers a movement complete
|
||||
- when it keeps controlling after landing instead of immediately failing
|
||||
- how it handles path executor timeout and repath
|
||||
- which parkour and descend transitions depend on next-movement awareness
|
||||
|
||||
- [ ] **Step 2: Write down the allowed and disallowed Baritone borrow list**
|
||||
|
||||
Add to `docs/guide/pathfinding-research.md`:
|
||||
|
||||
```md
|
||||
### Baritone reference notes for zero-replan work
|
||||
|
||||
Borrow:
|
||||
- landing-aware completion
|
||||
- next-movement-aware descend/ascend handoff
|
||||
- conservative parkour admissibility
|
||||
|
||||
Do not borrow:
|
||||
- GoalBlock occupancy semantics that allow success with sloppy live execution
|
||||
- executor repath tolerance as a substitute for deterministic harness stability
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Do not change MCC code in this task**
|
||||
|
||||
This task is design grounding only. The output is a short written comparison that later tasks can cite.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Reproduce Each Replan Family With Deterministic Tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/PathExecutorCompletionTests.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/PathSegmentManagerTests.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/SprintJumpTemplateScenarioTests.cs`
|
||||
|
||||
- [ ] **Step 1: Add a walk final-stop red test for the live `(103.36, 80.00, 100.50)` case**
|
||||
|
||||
Add a test that seeds physics at the live near-goal position and expects completion without failure:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void WalkTemplate_FinalStop_Completes_FromLiveNearGoalState_WithoutFailure()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 95, max: 115);
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(102.5, 80, 100.5),
|
||||
End = new Location(103.5, 80, 100.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new WalkTemplate(segment, null);
|
||||
var physics = new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(103.36, 80.0, 100.50),
|
||||
DeltaMovement = new Vec3d(0.0346, 0.0, 0.0),
|
||||
OnGround = true,
|
||||
MovementSpeed = 0.1f,
|
||||
Yaw = 270f
|
||||
};
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, 40, out _);
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add an ascend final-stop red test for the live `(191.38, 81.00, 171.38)` case**
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void AscendTemplate_FinalStop_Completes_FromLiveLandingState_WithoutFailure()
|
||||
{
|
||||
// build the same corner-ascend world as the live harness
|
||||
// seed physics at the live failure position
|
||||
// expect TemplateState.Complete within a short horizon
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add a descend final-stop red test for the live `(201.50, 80.00, 200.50)` case**
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void DescendTemplate_FinalStop_Completes_FromLiveLandingState_WithoutFailure()
|
||||
{
|
||||
// build the wall-adjacent descend world
|
||||
// seed physics at the live failure position
|
||||
// expect TemplateState.Complete
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add a manager-level red test that accepted routes finish with zero replans**
|
||||
|
||||
Extend `PathSegmentManagerTests.cs` with a short accepted path:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void Tick_ShortAcceptedPath_CompletesWithoutIncrementingReplanCount()
|
||||
{
|
||||
// start manager with a 3-node flat path
|
||||
// run ticks until completion
|
||||
// assert manager.ReplanCount == 0
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run the focused red test set**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~GroundedTemplateConvergenceTests|FullyQualifiedName~PathExecutorCompletionTests|FullyQualifiedName~PathSegmentManagerTests|FullyQualifiedName~SprintJumpTemplateScenarioTests" -v minimal
|
||||
```
|
||||
|
||||
Expected initially: FAIL on the new live-parity cases.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Remove Zero-Replan Gaps In Walk And FinalStop Execution
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/TransitionBrakingPlanner.cs`
|
||||
|
||||
- [ ] **Step 1: Make `WalkTemplate` prefer completion over fail once goal support is already valid**
|
||||
|
||||
Before the template returns `TemplateState.Failed`, ensure it checks an explicit “good enough for this segment” predicate first:
|
||||
|
||||
```csharp
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
|
||||
if (_stuckTicks > 40 || _tickCount > maxTicks)
|
||||
return TemplateState.Failed;
|
||||
```
|
||||
|
||||
The final implementation must preserve that order even when the bot is slightly off center but already inside valid goal support.
|
||||
|
||||
- [ ] **Step 2: Tighten final-stop control so the last traverse segment stops without lateral drift**
|
||||
|
||||
Update planner/controller logic for `PathTransitionType.FinalStop` to penalize cross-axis drift near the end plane:
|
||||
|
||||
```csharp
|
||||
double lateralError = TemplateHelper.CrossTrackDistance(pos, segment);
|
||||
if (segment.ExitTransition == PathTransitionType.FinalStop && lateralError > 0.10)
|
||||
{
|
||||
// reduce forward carry and bias facing back to segment heading
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Ensure `ContinueStraight -> FinalStop` handoff drops sprint early enough**
|
||||
|
||||
Use the final live traces as the acceptance reference:
|
||||
|
||||
- flat final stop must not replan at segment `0`
|
||||
- final stop segment must complete before the `(103.36, 80.00, 100.50)` failure state
|
||||
- do not rely on yaw/pitch reset to make this pass
|
||||
- residual speed within the route is allowed as long as the route still completes with `0 replan`
|
||||
|
||||
- [ ] **Step 4: Run focused deterministic tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~WalkTemplate_FinalStop|FullyQualifiedName~PathExecutorCompletionTests|FullyQualifiedName~PathSegmentManagerTests" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS with the new walk/final-stop tests green.
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Remove Zero-Replan Gaps In Ascend Execution
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/GroundedSegmentController.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
|
||||
- [ ] **Step 1: Add a dedicated post-landing settle phase in `AscendTemplate`**
|
||||
|
||||
Current code always sets:
|
||||
|
||||
```csharp
|
||||
input.Forward = true;
|
||||
input.Sprint = true;
|
||||
```
|
||||
|
||||
Replace that with a landing-aware phase:
|
||||
|
||||
```csharp
|
||||
bool landedOnTargetLevel = physics.OnGround && Math.Abs(dy) < 0.2;
|
||||
if (landedOnTargetLevel)
|
||||
{
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
else
|
||||
{
|
||||
input.Forward = true;
|
||||
input.Sprint = true;
|
||||
if (physics.OnGround && dy > 0.1)
|
||||
input.Jump = true;
|
||||
}
|
||||
```
|
||||
|
||||
The acceptance bar is not “zero residual speed after each ascend”. The acceptance bar is “the accepted route continues without a replan”.
|
||||
|
||||
- [ ] **Step 2: Add live-parity ascend tests**
|
||||
|
||||
Add deterministic tests for:
|
||||
|
||||
- corner ascend final stop
|
||||
- chained ascend middle segment
|
||||
- chained ascend final segment
|
||||
|
||||
- [ ] **Step 3: Run the focused ascend suite**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Ascend|FullyQualifiedName~Corner ascend|FullyQualifiedName~PathSegmentManagerTests" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS with no new regressions in existing ascend tests.
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Remove Zero-Replan Gaps In Descend Execution
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs`
|
||||
- Modify: `MinecraftClient.Tests/Pathing/Execution/GroundedTemplateConvergenceTests.cs`
|
||||
|
||||
- [ ] **Step 1: Make landing-state final stop complete immediately when support is already valid**
|
||||
|
||||
Preserve the landing-phase completion ordering:
|
||||
|
||||
```csharp
|
||||
if (physics.OnGround && Math.Abs(dy) < 1.0)
|
||||
{
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
```
|
||||
|
||||
The acceptance case is the exact live failure state at `(201.50, 80.00, 200.50)`.
|
||||
|
||||
- [ ] **Step 2: Add descend live-parity tests**
|
||||
|
||||
Add tests that seed the exact live landing state and assert `TemplateState.Complete` rather than `Failed`.
|
||||
|
||||
- [ ] **Step 3: Run the focused descend suite**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~DescendTemplate|FullyQualifiedName~GroundedTemplateConvergenceTests" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS with the new descend test green.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Make Rejection Scenarios Reject Before Execution Starts
|
||||
|
||||
**Files:**
|
||||
- Modify: `MinecraftClient/Pathing/Core/AStarPathFinder.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs`
|
||||
- Modify: `MinecraftClient/Pathing/Moves/ParkourFeasibility.cs`
|
||||
- Modify: `tools/test-pathing-template-regressions.sh`
|
||||
|
||||
- [ ] **Step 1: Add a deterministic rejection test for the `3x1 no-run-up` case**
|
||||
|
||||
Create or extend a pathfinder-level test that asserts:
|
||||
|
||||
```csharp
|
||||
Assert.Equal(PathStatus.Failed, result.Status);
|
||||
Assert.Empty(PathSegmentBuilder.FromPath(result.Path));
|
||||
```
|
||||
|
||||
for the exact live `141.5 -> 144.5/81/138.5` layout.
|
||||
|
||||
- [ ] **Step 2: Tighten parkour admissibility before planner partial fallback is considered acceptable**
|
||||
|
||||
Review the current path:
|
||||
|
||||
- `Traverse -> Ascend` to a partial fallback at `(143,81,138)`
|
||||
|
||||
The fix should make this route non-admissible in the first place if the intended gap cannot be completed with valid run-up.
|
||||
- Use `ThirdpartyReference/baritone/.../MovementParkour.java` and the prior admissibility spec as references, but keep MCC’s execution contract stricter: direct reject in this harness.
|
||||
|
||||
- [ ] **Step 3: Re-run rejection-only live validation**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh
|
||||
bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected for rejection scenarios:
|
||||
|
||||
- `2x1 side-wall jump`: direct reject, `0 replan`
|
||||
- `3x1 no-run-up gap`: direct reject, `0 replan`
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Add Long-Route Zero-Replan Stability Coverage
|
||||
|
||||
**Files:**
|
||||
- Create: `tools/test-pathing-long-routes.sh`
|
||||
- Modify: `docs/guide/pathfinding-research.md`
|
||||
|
||||
- [ ] **Step 1: Add same-operation long routes**
|
||||
|
||||
Use route lengths that remain comfortably below planner timeout:
|
||||
|
||||
- straight traverse chain: `8-12` blocks
|
||||
- diagonal zig-zag chain: `6-8` segments
|
||||
- ascend staircase: `4-6` ascends
|
||||
- descend staircase: `4-6` descends
|
||||
- aligned parkour chain: `3-4` jumps
|
||||
|
||||
Each must require:
|
||||
|
||||
- `A* result: Success`
|
||||
- `0 replan`
|
||||
- final location inside goal block
|
||||
|
||||
- [ ] **Step 2: Add mixed-operation long routes**
|
||||
|
||||
Recommended mixed routes:
|
||||
|
||||
- `Traverse -> Turn -> Parkour -> Turn -> Traverse -> FinalStop`
|
||||
- `Diagonal -> Ascend -> Traverse -> Descend -> FinalStop`
|
||||
- `Traverse -> Ascend -> Traverse -> Parkour -> Descend -> FinalStop`
|
||||
|
||||
Keep all mixed routes inside a small pre-cleared test region so chunk loading is not the variable under test.
|
||||
Do not reset speed or orientation between internal actions of a route. A route only passes if the naturally carried speed across those actions still yields `0 replan`.
|
||||
|
||||
- [ ] **Step 3: Add turn-density routes**
|
||||
|
||||
Add one route with frequent heading changes and no jumps:
|
||||
|
||||
- `8-10` short traverse/diagonal segments
|
||||
- every segment should change heading
|
||||
- must still finish with `0 replan`
|
||||
|
||||
- [ ] **Step 4: Add speed-carry long routes**
|
||||
|
||||
Add one route each for:
|
||||
|
||||
- repeated `Traverse -> Ascend`
|
||||
- repeated `Traverse -> Descend`
|
||||
- repeated `Traverse -> Parkour`
|
||||
|
||||
These routes exist specifically to prove that residual speed between actions does not force replans in deterministic conditions.
|
||||
|
||||
- [ ] **Step 5: Run the long-route harness**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh
|
||||
bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected: PASS with every accepted route showing `0 replan`.
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Full Verification
|
||||
|
||||
**Files:**
|
||||
- No new files
|
||||
|
||||
- [ ] **Step 1: Run deterministic test coverage**
|
||||
|
||||
```bash
|
||||
dotnet test MinecraftClient.Tests/MinecraftClient.Tests.csproj --filter "FullyQualifiedName~Pathing.Execution" -v minimal
|
||||
```
|
||||
|
||||
Expected: PASS, `0 failed`.
|
||||
|
||||
- [ ] **Step 2: Run release build**
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh
|
||||
mcc-build
|
||||
```
|
||||
|
||||
Expected: `Build succeeded. 0 Warning(s), 0 Error(s)`.
|
||||
|
||||
- [ ] **Step 3: Run short live suites**
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh
|
||||
bash tools/test-transition-braking.sh 1.21.11-Vanilla
|
||||
bash tools/test-pathing-template-regressions.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- accepted scenarios: `0 replan`
|
||||
- rejection scenarios: direct reject, `0 replan`
|
||||
|
||||
- [ ] **Step 4: Run long-route live suite**
|
||||
|
||||
```bash
|
||||
source tools/mcc-env.sh
|
||||
bash tools/test-pathing-long-routes.sh 1.21.11-Vanilla
|
||||
```
|
||||
|
||||
Expected: PASS, no accepted route triggers `replan`.
|
||||
|
||||
## Self-Review
|
||||
|
||||
Spec coverage:
|
||||
|
||||
- `0 replan` short deterministic live scenarios: covered by Tasks 1-6
|
||||
- interference inventory first: covered near top of this plan, revised to remove non-applicable entity and yaw/pitch concerns
|
||||
- Baritone comparison before code changes: covered by Task 2
|
||||
- rejection cleanup: covered by Task 7
|
||||
- long-path stability coverage, including speed-carry routes: covered by Task 8
|
||||
|
||||
Placeholder scan:
|
||||
|
||||
- No `TODO` or `TBD` placeholders remain
|
||||
- Concrete files, scenarios, and commands are included
|
||||
|
||||
Type consistency:
|
||||
|
||||
- File paths and move/template names match current codebase names:
|
||||
- `WalkTemplate`
|
||||
- `AscendTemplate`
|
||||
- `DescendTemplate`
|
||||
- `GroundedSegmentController`
|
||||
- `AStarPathFinder`
|
||||
398
tools/test-pathing-jump-combos.sh
Normal file
398
tools/test-pathing-jump-combos.sh
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||
|
||||
VERSION="${1:-1.21.11-Vanilla}"
|
||||
SESSION="mcc-pathing-jump-combos"
|
||||
USERNAME="CursorBot"
|
||||
|
||||
SESSION_ROOT="$(_mcc_session_root "$SESSION")"
|
||||
LOG="$(_mcc_session_log_file "$SESSION")"
|
||||
|
||||
PASSED_CASES=()
|
||||
FAILED_CASES=()
|
||||
|
||||
cleanup() {
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
send_mcc() {
|
||||
mcc-cmd --session "$SESSION" "$1"
|
||||
}
|
||||
|
||||
log_line_count() {
|
||||
if [[ -f "$LOG" ]]; then
|
||||
wc -l < "$LOG"
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
log_since() {
|
||||
local from_line="$1"
|
||||
if [[ ! -f "$LOG" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
tail -n +"$((from_line + 1))" "$LOG"
|
||||
}
|
||||
|
||||
log_since_clean() {
|
||||
log_since "$1" | sed 's/\x1b\[[0-9;]*m//g'
|
||||
}
|
||||
|
||||
wait_for_log() {
|
||||
local pattern="$1"
|
||||
local from_line="${2:-0}"
|
||||
local timeout="${3:-30}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
if log_since_clean "$from_line" | grep -Fq "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_navigation() {
|
||||
local from_line="$1"
|
||||
local timeout="${2:-45}"
|
||||
local saw_start=0
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local recent
|
||||
recent="$(log_since_clean "$from_line")"
|
||||
|
||||
if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then
|
||||
saw_start=1
|
||||
fi
|
||||
|
||||
if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for navigation completion" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
count_replans_since() {
|
||||
local from_line="$1"
|
||||
log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true
|
||||
}
|
||||
|
||||
assert_no_replans_since() {
|
||||
local from_line="$1"
|
||||
local count
|
||||
count="$(count_replans_since "$from_line")"
|
||||
if [[ "$count" != "0" ]]; then
|
||||
echo "Expected 0 replans, saw $count" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_no_partial_since() {
|
||||
local from_line="$1"
|
||||
if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then
|
||||
echo "Expected full success path, saw partial path" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
debug_state_snapshot() {
|
||||
local label="$1"
|
||||
local from_line
|
||||
from_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$from_line" 10
|
||||
echo ""
|
||||
echo "=== Debug state: $label ==="
|
||||
log_since_clean "$from_line"
|
||||
}
|
||||
|
||||
capture_debug_state_before_route() {
|
||||
debug_state_snapshot "before route - $1"
|
||||
}
|
||||
|
||||
capture_debug_state_after_route() {
|
||||
debug_state_snapshot "after route - $1"
|
||||
}
|
||||
|
||||
capture_debug_location() {
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$start_line" 10
|
||||
extract_last_location "$start_line"
|
||||
}
|
||||
|
||||
wait_for_location_in_block() {
|
||||
local expected_x="$1"
|
||||
local expected_y="$2"
|
||||
local expected_z="$3"
|
||||
local timeout="${4:-10}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local actual_x actual_y actual_z
|
||||
read -r actual_x actual_y actual_z <<< "$(capture_debug_location)"
|
||||
if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05:
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
prepare_independent_route() {
|
||||
local label="$1"
|
||||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
extract_last_location() {
|
||||
local from_line="${1:-0}"
|
||||
|
||||
python3 - "$LOG" "$from_line" <<'PY'
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
log_path = pathlib.Path(sys.argv[1])
|
||||
from_line = int(sys.argv[2])
|
||||
text = log_path.read_text(errors="ignore")
|
||||
text = "\n".join(text.splitlines()[from_line:])
|
||||
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
|
||||
matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text)
|
||||
if not matches:
|
||||
raise SystemExit("No Location line found in MCC log")
|
||||
x, y, z = matches[-1]
|
||||
print(f"{x} {y} {z}")
|
||||
PY
|
||||
}
|
||||
|
||||
assert_inside_goal_block() {
|
||||
local actual_x="$1"
|
||||
local actual_y="$2"
|
||||
local actual_z="$3"
|
||||
local expected_x="$4"
|
||||
local expected_y="$5"
|
||||
local expected_z="$6"
|
||||
|
||||
python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) != int(ex) or math.floor(az) != int(ez) or abs(ay - ey) > 0.05:
|
||||
raise SystemExit(
|
||||
f"Expected location inside goal block ({int(ex)}, {ey:.2f}, {int(ez)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})"
|
||||
)
|
||||
PY
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
local header="$1"
|
||||
|
||||
echo ""
|
||||
echo "----- $header -----"
|
||||
if [[ -f "$LOG" ]]; then
|
||||
tail -n 40 "$LOG" | sed 's/\x1b\[[0-9;]*m//g'
|
||||
else
|
||||
echo "(no log available yet)"
|
||||
fi
|
||||
}
|
||||
|
||||
fill_box() {
|
||||
mc-rcon "fill $1 $2 $3 $4 $5 $6 $7" >/dev/null
|
||||
}
|
||||
|
||||
set_stone() {
|
||||
mc-rcon "setblock $1 $2 $3 stone" >/dev/null
|
||||
}
|
||||
|
||||
run_accepted_route() {
|
||||
local label="$1"
|
||||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
local goal_x="$5"
|
||||
local goal_y="$6"
|
||||
local goal_z="$7"
|
||||
local timeout="${8:-45}"
|
||||
|
||||
prepare_independent_route "$label" "$start_x" "$start_y" "$start_z"
|
||||
capture_debug_state_before_route "$label"
|
||||
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind $goal_x $goal_y $goal_z"
|
||||
wait_for_navigation "$start_line" "$timeout"
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "$label"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "$goal_x" "$goal_y" "$goal_z"
|
||||
print_summary "$label"
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local label="$1"
|
||||
shift
|
||||
|
||||
echo ""
|
||||
echo "== $label =="
|
||||
set +e
|
||||
(
|
||||
set -e
|
||||
"$@"
|
||||
)
|
||||
local status=$?
|
||||
set -e
|
||||
|
||||
if [[ $status -eq 0 ]]; then
|
||||
PASSED_CASES+=("$label")
|
||||
echo "RESULT: PASS - $label"
|
||||
else
|
||||
FAILED_CASES+=("$label")
|
||||
echo "RESULT: FAIL - $label"
|
||||
fi
|
||||
}
|
||||
|
||||
start_mcc() {
|
||||
mkdir -p "$SESSION_ROOT"
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
mcc-build >/dev/null
|
||||
mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --debug-on --no-build >/dev/null
|
||||
wait_for_log "Server was successfully joined." 0 40
|
||||
send_mcc "debug on"
|
||||
}
|
||||
|
||||
scenario_repeated_cardinal_parkour() {
|
||||
fill_box 578 79 578 590 79 582 air
|
||||
fill_box 578 80 578 590 90 582 air
|
||||
set_stone 580 79 580
|
||||
set_stone 582 79 580
|
||||
set_stone 584 79 580
|
||||
set_stone 586 79 580
|
||||
set_stone 588 79 580
|
||||
run_accepted_route "Repeated jump - cardinal parkour chain" "580.5" "80" "580.5" "588" "80.00" "580"
|
||||
}
|
||||
|
||||
scenario_repeated_diagonal_parkour() {
|
||||
fill_box 598 79 598 608 79 608 air
|
||||
fill_box 598 80 598 608 90 608 air
|
||||
set_stone 600 79 600
|
||||
set_stone 602 79 602
|
||||
set_stone 604 79 604
|
||||
set_stone 606 79 606
|
||||
run_accepted_route "Repeated jump - diagonal parkour chain" "600.5" "80" "600.5" "606" "80.00" "606"
|
||||
}
|
||||
|
||||
scenario_obstructed_parkour_turn_mix() {
|
||||
fill_box 618 79 618 628 79 624 air
|
||||
fill_box 618 80 618 628 90 624 air
|
||||
set_stone 620 79 620
|
||||
set_stone 622 79 620
|
||||
set_stone 622 79 621
|
||||
set_stone 624 79 621
|
||||
set_stone 624 79 622
|
||||
set_stone 626 79 622
|
||||
set_stone 620 80 621
|
||||
set_stone 620 81 621
|
||||
set_stone 622 80 622
|
||||
set_stone 622 81 622
|
||||
run_accepted_route "Obstructed jump mix - repeated parkour L-turns" "620.5" "80" "620.5" "626" "80.00" "622"
|
||||
}
|
||||
|
||||
scenario_parkour_ascend_descend_chain() {
|
||||
fill_box 638 79 618 650 80 622 air
|
||||
fill_box 638 81 618 650 92 622 air
|
||||
set_stone 640 79 620
|
||||
set_stone 642 80 620
|
||||
set_stone 644 79 620
|
||||
set_stone 646 80 620
|
||||
set_stone 648 79 620
|
||||
run_accepted_route "Vertical jump mix - parkour ascend descend chain" "640.5" "80" "620.5" "648" "80.00" "620"
|
||||
}
|
||||
|
||||
scenario_diagonal_ascend_descend_chain() {
|
||||
fill_box 678 79 618 686 80 626 air
|
||||
fill_box 678 81 618 686 92 626 air
|
||||
set_stone 680 79 620
|
||||
set_stone 681 80 621
|
||||
set_stone 682 79 622
|
||||
set_stone 683 80 623
|
||||
set_stone 684 79 624
|
||||
run_accepted_route "Diagonal vertical mix - ascend descend chain" "680.5" "80" "620.5" "684" "80.00" "624"
|
||||
}
|
||||
|
||||
start_mcc
|
||||
|
||||
mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true
|
||||
mc-rcon "gamerule doMobSpawning false" >/dev/null 2>&1 || true
|
||||
mc-rcon "time set day" >/dev/null 2>&1 || true
|
||||
|
||||
run_case "Repeated jump - cardinal parkour chain" scenario_repeated_cardinal_parkour
|
||||
run_case "Repeated jump - diagonal parkour chain" scenario_repeated_diagonal_parkour
|
||||
run_case "Obstructed jump mix - repeated parkour L-turns" scenario_obstructed_parkour_turn_mix
|
||||
run_case "Vertical jump mix - parkour ascend descend chain" scenario_parkour_ascend_descend_chain
|
||||
run_case "Diagonal vertical mix - ascend descend chain" scenario_diagonal_ascend_descend_chain
|
||||
|
||||
echo ""
|
||||
echo "Jump combo summary:"
|
||||
for label in "${PASSED_CASES[@]}"; do
|
||||
echo " PASS $label"
|
||||
done
|
||||
for label in "${FAILED_CASES[@]}"; do
|
||||
echo " FAIL $label"
|
||||
done
|
||||
|
||||
if [[ ${#FAILED_CASES[@]} -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Pathing jump-combo suite complete."
|
||||
454
tools/test-pathing-long-routes.sh
Normal file
454
tools/test-pathing-long-routes.sh
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||
|
||||
VERSION="${1:-1.21.11-Vanilla}"
|
||||
SESSION="mcc-pathing-long-routes"
|
||||
USERNAME="CursorBot"
|
||||
|
||||
SESSION_ROOT="$(_mcc_session_root "$SESSION")"
|
||||
LOG="$(_mcc_session_log_file "$SESSION")"
|
||||
|
||||
cleanup() {
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
send_mcc() {
|
||||
mcc-cmd --session "$SESSION" "$1"
|
||||
}
|
||||
|
||||
log_line_count() {
|
||||
if [[ -f "$LOG" ]]; then
|
||||
wc -l < "$LOG"
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
log_since() {
|
||||
local from_line="$1"
|
||||
if [[ ! -f "$LOG" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
tail -n +"$((from_line + 1))" "$LOG"
|
||||
}
|
||||
|
||||
wait_for_log() {
|
||||
local pattern="$1"
|
||||
local from_line="${2:-0}"
|
||||
local timeout="${3:-30}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
if log_since_clean "$from_line" | grep -Fq "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_navigation() {
|
||||
local from_line="$1"
|
||||
local timeout="${2:-45}"
|
||||
local saw_start=0
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local recent
|
||||
recent="$(log_since_clean "$from_line")"
|
||||
|
||||
if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then
|
||||
saw_start=1
|
||||
fi
|
||||
|
||||
if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for navigation completion" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
log_since_clean() {
|
||||
log_since "$1" | sed 's/\x1b\[[0-9;]*m//g'
|
||||
}
|
||||
|
||||
count_replans_since() {
|
||||
local from_line="$1"
|
||||
log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true
|
||||
}
|
||||
|
||||
assert_no_replans_since() {
|
||||
local from_line="$1"
|
||||
local count
|
||||
count="$(count_replans_since "$from_line")"
|
||||
if [[ "$count" != "0" ]]; then
|
||||
echo "Expected 0 replans, saw $count" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_no_partial_since() {
|
||||
local from_line="$1"
|
||||
if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then
|
||||
echo "Expected full success path, saw partial path" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
debug_state_snapshot() {
|
||||
local label="$1"
|
||||
local from_line
|
||||
from_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$from_line" 10
|
||||
echo ""
|
||||
echo "=== Debug state: $label ==="
|
||||
log_since_clean "$from_line"
|
||||
}
|
||||
|
||||
capture_debug_state_before_route() {
|
||||
debug_state_snapshot "before route - $1"
|
||||
}
|
||||
|
||||
capture_debug_state_after_route() {
|
||||
debug_state_snapshot "after route - $1"
|
||||
}
|
||||
|
||||
prepare_independent_route() {
|
||||
local label="$1"
|
||||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
capture_debug_location() {
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$start_line" 10
|
||||
extract_last_location "$start_line"
|
||||
}
|
||||
|
||||
wait_for_location_in_block() {
|
||||
local expected_x="$1"
|
||||
local expected_y="$2"
|
||||
local expected_z="$3"
|
||||
local timeout="${4:-10}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local actual_x actual_y actual_z
|
||||
read -r actual_x actual_y actual_z <<< "$(capture_debug_location)"
|
||||
if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05:
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
extract_last_location() {
|
||||
local from_line="${1:-0}"
|
||||
|
||||
python3 - "$LOG" "$from_line" <<'PY'
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
log_path = pathlib.Path(sys.argv[1])
|
||||
from_line = int(sys.argv[2])
|
||||
text = log_path.read_text(errors="ignore")
|
||||
text = "\n".join(text.splitlines()[from_line:])
|
||||
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
|
||||
matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text)
|
||||
if not matches:
|
||||
raise SystemExit("No Location line found in MCC log")
|
||||
x, y, z = matches[-1]
|
||||
print(f"{x} {y} {z}")
|
||||
PY
|
||||
}
|
||||
|
||||
assert_inside_goal_block() {
|
||||
local actual_x="$1"
|
||||
local actual_y="$2"
|
||||
local actual_z="$3"
|
||||
local expected_x="$4"
|
||||
local expected_y="$5"
|
||||
local expected_z="$6"
|
||||
|
||||
python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) != int(ex) or math.floor(az) != int(ez) or abs(ay - ey) > 0.05:
|
||||
raise SystemExit(
|
||||
f"Expected location inside goal block ({int(ex)}, {ey:.2f}, {int(ez)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})"
|
||||
)
|
||||
PY
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
local header="$1"
|
||||
|
||||
echo ""
|
||||
echo "----- $header -----"
|
||||
if [[ -f "$LOG" ]]; then
|
||||
tail -n 40 "$LOG" | sed 's/\x1b\[[0-9;]*m//g'
|
||||
else
|
||||
echo "(no log available yet)"
|
||||
fi
|
||||
}
|
||||
|
||||
fill_box() {
|
||||
mc-rcon "fill $1 $2 $3 $4 $5 $6 $7" >/dev/null
|
||||
}
|
||||
|
||||
set_stone() {
|
||||
mc-rcon "setblock $1 $2 $3 stone" >/dev/null
|
||||
}
|
||||
|
||||
run_accepted_route() {
|
||||
local label="$1"
|
||||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
local goal_x="$5"
|
||||
local goal_y="$6"
|
||||
local goal_z="$7"
|
||||
local timeout="${8:-45}"
|
||||
|
||||
prepare_independent_route "$label" "$start_x" "$start_y" "$start_z"
|
||||
capture_debug_state_before_route "$label"
|
||||
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind $goal_x $goal_y $goal_z"
|
||||
wait_for_navigation "$start_line" "$timeout"
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "$label"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "$goal_x" "$goal_y" "$goal_z"
|
||||
print_summary "$label"
|
||||
}
|
||||
|
||||
start_mcc() {
|
||||
mkdir -p "$SESSION_ROOT"
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
mcc-build >/dev/null
|
||||
mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --debug-on --no-build >/dev/null
|
||||
wait_for_log "Server was successfully joined." 0 40
|
||||
send_mcc "debug on"
|
||||
}
|
||||
|
||||
run_same_move_routes() {
|
||||
echo "== Same move routes =="
|
||||
|
||||
fill_box 298 79 298 314 79 302 air
|
||||
fill_box 298 80 298 314 90 302 air
|
||||
fill_box 300 79 300 312 79 300 stone
|
||||
run_accepted_route "Same move - straight traverse chain" "300.5" "80" "300.5" "312" "80.00" "300"
|
||||
|
||||
fill_box 318 79 318 330 79 330 air
|
||||
fill_box 318 80 318 330 90 330 air
|
||||
set_stone 320 79 320
|
||||
set_stone 321 79 321
|
||||
set_stone 322 79 322
|
||||
set_stone 323 79 323
|
||||
set_stone 324 79 324
|
||||
set_stone 325 79 325
|
||||
set_stone 326 79 326
|
||||
set_stone 327 79 327
|
||||
run_accepted_route "Same move - diagonal chain" "320.5" "80" "320.5" "327" "80.00" "327"
|
||||
|
||||
fill_box 338 79 338 347 85 342 air
|
||||
fill_box 338 80 338 347 90 342 air
|
||||
fill_box 340 79 339 340 79 341 stone
|
||||
fill_box 341 80 339 341 80 341 stone
|
||||
fill_box 342 81 339 342 81 341 stone
|
||||
fill_box 343 82 339 343 82 341 stone
|
||||
fill_box 344 83 339 344 83 341 stone
|
||||
fill_box 345 84 339 345 84 341 stone
|
||||
run_accepted_route "Same move - ascend staircase" "340.5" "80" "340.5" "345" "85.00" "340"
|
||||
|
||||
fill_box 360 79 358 369 85 362 air
|
||||
fill_box 360 80 358 369 90 362 air
|
||||
fill_box 362 84 359 362 84 361 stone
|
||||
fill_box 363 83 359 363 83 361 stone
|
||||
fill_box 364 82 359 364 82 361 stone
|
||||
fill_box 365 81 359 365 81 361 stone
|
||||
fill_box 366 80 359 366 80 361 stone
|
||||
fill_box 367 79 359 367 79 361 stone
|
||||
run_accepted_route "Same move - descend staircase" "362.5" "85" "360.5" "367" "80.00" "360"
|
||||
|
||||
fill_box 378 79 378 390 79 382 air
|
||||
fill_box 378 80 378 390 90 382 air
|
||||
set_stone 380 79 380
|
||||
set_stone 382 79 380
|
||||
set_stone 384 79 380
|
||||
set_stone 386 79 380
|
||||
set_stone 388 79 380
|
||||
run_accepted_route "Same move - aligned parkour chain" "380.5" "80" "380.5" "388" "80.00" "380"
|
||||
}
|
||||
|
||||
run_mixed_move_routes() {
|
||||
echo "== Mixed move routes =="
|
||||
|
||||
fill_box 398 79 398 410 79 406 air
|
||||
fill_box 398 80 398 410 90 406 air
|
||||
set_stone 400 79 400
|
||||
set_stone 401 79 400
|
||||
set_stone 402 79 400
|
||||
set_stone 402 79 401
|
||||
set_stone 402 79 402
|
||||
set_stone 404 79 402
|
||||
set_stone 405 79 402
|
||||
set_stone 406 79 402
|
||||
set_stone 406 79 403
|
||||
set_stone 406 79 404
|
||||
set_stone 407 79 404
|
||||
set_stone 408 79 404
|
||||
run_accepted_route "Mixed - traverse turn parkour turn traverse" "400.5" "80" "400.5" "408" "80.00" "404"
|
||||
|
||||
fill_box 418 79 418 430 82 424 air
|
||||
fill_box 418 80 418 430 92 424 air
|
||||
set_stone 420 79 420
|
||||
set_stone 421 79 421
|
||||
set_stone 422 79 422
|
||||
set_stone 423 80 422
|
||||
set_stone 424 81 422
|
||||
set_stone 425 81 422
|
||||
set_stone 426 81 422
|
||||
set_stone 427 80 422
|
||||
set_stone 428 79 422
|
||||
run_accepted_route "Mixed - diagonal ascend traverse descend" "420.5" "80" "420.5" "428" "80.00" "422"
|
||||
|
||||
fill_box 438 79 438 450 82 442 air
|
||||
fill_box 438 80 438 450 92 442 air
|
||||
set_stone 440 79 440
|
||||
set_stone 441 79 440
|
||||
set_stone 442 80 440
|
||||
set_stone 443 81 440
|
||||
set_stone 444 81 440
|
||||
set_stone 446 81 440
|
||||
set_stone 447 80 440
|
||||
set_stone 448 79 440
|
||||
run_accepted_route "Mixed - traverse ascend parkour descend" "440.5" "80" "440.5" "448" "80.00" "440"
|
||||
}
|
||||
|
||||
run_turn_density_routes() {
|
||||
echo "== Turn density routes =="
|
||||
|
||||
fill_box 458 79 458 468 79 468 air
|
||||
fill_box 458 80 458 468 90 468 air
|
||||
set_stone 460 79 460
|
||||
set_stone 461 79 460
|
||||
set_stone 461 79 461
|
||||
set_stone 462 79 462
|
||||
set_stone 463 79 462
|
||||
set_stone 463 79 463
|
||||
set_stone 464 79 464
|
||||
set_stone 465 79 464
|
||||
set_stone 465 79 465
|
||||
set_stone 466 79 466
|
||||
run_accepted_route "Turn density - alternating traverse diagonal chain" "460.5" "80" "460.5" "466" "80.00" "466"
|
||||
}
|
||||
|
||||
run_speed_carry_routes() {
|
||||
echo "== Speed carry routes =="
|
||||
|
||||
fill_box 478 79 478 490 83 482 air
|
||||
fill_box 478 80 478 490 94 482 air
|
||||
set_stone 480 79 480
|
||||
set_stone 481 79 480
|
||||
set_stone 482 80 480
|
||||
set_stone 483 80 480
|
||||
set_stone 484 81 480
|
||||
set_stone 485 81 480
|
||||
set_stone 486 82 480
|
||||
set_stone 487 82 480
|
||||
set_stone 488 83 480
|
||||
run_accepted_route "Speed carry - repeated traverse ascend" "480.5" "80" "480.5" "488" "84.00" "480"
|
||||
|
||||
fill_box 498 79 498 510 82 502 air
|
||||
fill_box 498 80 498 510 94 502 air
|
||||
set_stone 500 82 500
|
||||
set_stone 501 82 500
|
||||
set_stone 502 81 500
|
||||
set_stone 503 81 500
|
||||
set_stone 504 80 500
|
||||
set_stone 505 80 500
|
||||
set_stone 506 79 500
|
||||
set_stone 507 79 500
|
||||
run_accepted_route "Speed carry - repeated traverse descend" "500.5" "83" "500.5" "507" "80.00" "500"
|
||||
|
||||
fill_box 518 79 518 532 79 522 air
|
||||
fill_box 518 80 518 532 90 522 air
|
||||
set_stone 520 79 520
|
||||
set_stone 521 79 520
|
||||
set_stone 523 79 520
|
||||
set_stone 524 79 520
|
||||
set_stone 526 79 520
|
||||
set_stone 527 79 520
|
||||
set_stone 529 79 520
|
||||
run_accepted_route "Speed carry - repeated traverse parkour" "520.5" "80" "520.5" "529" "80.00" "520"
|
||||
}
|
||||
|
||||
start_mcc
|
||||
|
||||
mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true
|
||||
mc-rcon "gamerule doMobSpawning false" >/dev/null 2>&1 || true
|
||||
mc-rcon "time set day" >/dev/null 2>&1 || true
|
||||
|
||||
run_same_move_routes
|
||||
run_mixed_move_routes
|
||||
run_turn_density_routes
|
||||
run_speed_carry_routes
|
||||
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
|
||||
echo ""
|
||||
echo "Pathing long-route suite complete."
|
||||
|
|
@ -7,17 +7,19 @@ source "$REPO_ROOT/tools/mcc-env.sh"
|
|||
|
||||
VERSION="${1:-1.21.11-Vanilla}"
|
||||
SESSION="mcc-pathing-template"
|
||||
TEST_ROOT="${TMPDIR:-/tmp}/mcc-pathing-template"
|
||||
CFG="$TEST_ROOT/MinecraftClient.pathing-template.ini"
|
||||
LOG="$TEST_ROOT/mcc-pathing-template.log"
|
||||
INPUT_FILE="$REPO_ROOT/mcc_input.txt"
|
||||
PREPARE_CFG_SCRIPT="$REPO_ROOT/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh"
|
||||
ENSURE_SERVER_SCRIPT="$REPO_ROOT/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh"
|
||||
USERNAME="CursorBot"
|
||||
|
||||
mkdir -p "$TEST_ROOT"
|
||||
SESSION_ROOT="$(_mcc_session_root "$SESSION")"
|
||||
LOG="$(_mcc_session_log_file "$SESSION")"
|
||||
|
||||
cleanup() {
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
send_mcc() {
|
||||
echo "$1" >> "$INPUT_FILE"
|
||||
mcc-cmd --session "$SESSION" "$1"
|
||||
}
|
||||
|
||||
log_line_count() {
|
||||
|
|
@ -40,10 +42,10 @@ log_since() {
|
|||
wait_for_log() {
|
||||
local pattern="$1"
|
||||
local from_line="${2:-0}"
|
||||
local timeout="${3:-20}"
|
||||
local timeout="${3:-30}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
if log_since "$from_line" | grep -Fq "$pattern"; then
|
||||
if log_since_clean "$from_line" | grep -Fq "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
|
|
@ -55,25 +57,35 @@ wait_for_log() {
|
|||
wait_for_navigation() {
|
||||
local from_line="$1"
|
||||
local timeout="${2:-25}"
|
||||
local saw_start=0
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local recent
|
||||
recent="$(log_since "$from_line")"
|
||||
recent="$(log_since_clean "$from_line")"
|
||||
|
||||
if grep -Eq "\\[PathMgr\\] (Replan failed|Giving up)|\\[PathMgr\\] Segment failed, replanning|\\[PathExec\\] Segment .* FAILED" <<<"$recent"; then
|
||||
if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then
|
||||
saw_start=1
|
||||
fi
|
||||
|
||||
if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then
|
||||
return 0
|
||||
if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for navigation completion" >&2
|
||||
log_since "$from_line" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -83,9 +95,9 @@ wait_for_failure_signal() {
|
|||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local recent
|
||||
recent="$(log_since "$from_line")"
|
||||
recent="$(log_since_clean "$from_line")"
|
||||
|
||||
if grep -Eq "\\[PathMgr\\] (Replan failed|Giving up)|No path found|\\[Navigate\\] A\\* result: Failed" <<<"$recent"; then
|
||||
if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
|
@ -95,6 +107,82 @@ wait_for_failure_signal() {
|
|||
return 1
|
||||
}
|
||||
|
||||
log_since_clean() {
|
||||
log_since "$1" | sed 's/\x1b\[[0-9;]*m//g'
|
||||
}
|
||||
|
||||
count_replans_since() {
|
||||
local from_line="$1"
|
||||
log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true
|
||||
}
|
||||
|
||||
assert_no_replans_since() {
|
||||
local from_line="$1"
|
||||
local count
|
||||
count="$(count_replans_since "$from_line")"
|
||||
if [[ "$count" != "0" ]]; then
|
||||
echo "Expected 0 replans, saw $count" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_no_partial_since() {
|
||||
local from_line="$1"
|
||||
if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then
|
||||
echo "Expected full success path, saw partial path" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
debug_state_snapshot() {
|
||||
local label="$1"
|
||||
local from_line
|
||||
from_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$from_line" 10
|
||||
echo ""
|
||||
echo "=== Debug state: $label ==="
|
||||
log_since_clean "$from_line"
|
||||
}
|
||||
|
||||
capture_debug_state_before_route() {
|
||||
debug_state_snapshot "before route - $1"
|
||||
}
|
||||
|
||||
capture_debug_state_after_route() {
|
||||
debug_state_snapshot "after route - $1"
|
||||
}
|
||||
|
||||
prepare_independent_route() {
|
||||
local label="$1"
|
||||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
assert_direct_rejection_since() {
|
||||
local from_line="$1"
|
||||
if ! log_since_clean "$from_line" | grep -Eq "No path found|\[Navigate\] A\* result: Failed"; then
|
||||
echo "Expected direct rejection before navigation execution" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
if log_since_clean "$from_line" | grep -Fq "[PathMgr] Navigation started"; then
|
||||
echo "Expected rejection before navigation started" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
assert_no_replans_since "$from_line"
|
||||
}
|
||||
|
||||
extract_last_location() {
|
||||
local from_line="${1:-0}"
|
||||
|
||||
|
|
@ -108,11 +196,11 @@ from_line = int(sys.argv[2])
|
|||
text = log_path.read_text(errors="ignore")
|
||||
text = "\n".join(text.splitlines()[from_line:])
|
||||
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
|
||||
matches = re.findall(r"Location\s+([-\d.]+),\s+([-\d.]+),\s+([-\d.]+)", text)
|
||||
matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text)
|
||||
if not matches:
|
||||
matches = re.findall(r"Segment \d+ complete .* at \(([-\d.]+),([-\d.]+),([-\d.]+)\)", text)
|
||||
matches = re.findall(r"Segment \d+ complete .* at \(([-,0-9.]+),([-,0-9.]+),([-,0-9.]+)\)", text)
|
||||
if not matches:
|
||||
matches = re.findall(r"pos=\(([-\d.]+),\s*([-\d.]+),\s*([-\d.]+)\)", text)
|
||||
matches = re.findall(r"pos=\(([-,0-9.]+),\s*([-,0-9.]+),\s*([-,0-9.]+)\)", text)
|
||||
if not matches:
|
||||
raise SystemExit("No location line found in MCC log")
|
||||
x, y, z = matches[-1]
|
||||
|
|
@ -120,23 +208,22 @@ print(f"{x} {y} {z}")
|
|||
PY
|
||||
}
|
||||
|
||||
assert_close() {
|
||||
assert_inside_goal_block() {
|
||||
local actual_x="$1"
|
||||
local actual_y="$2"
|
||||
local actual_z="$3"
|
||||
local target_x="$4"
|
||||
local target_y="$5"
|
||||
local target_z="$6"
|
||||
local tolerance="${7:-0.2}"
|
||||
|
||||
python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$target_x" "$target_y" "$target_z" "$tolerance"
|
||||
python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$target_x" "$target_y" "$target_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, tx, ty, tz, tol = map(float, sys.argv[1:])
|
||||
if abs(ax - tx) > tol or abs(ay - ty) > tol or abs(az - tz) > tol:
|
||||
ax, ay, az, tx, ty, tz = map(float, sys.argv[1:])
|
||||
if math.floor(ax) != int(tx) or math.floor(az) != int(tz) or abs(ay - ty) > 0.05:
|
||||
raise SystemExit(
|
||||
f"Expected ({tx:.2f}, {ty:.2f}, {tz:.2f}) within {tol:.2f}, got ({ax:.2f}, {ay:.2f}, {az:.2f})"
|
||||
f"Expected location inside goal block ({int(tx)}, {ty:.2f}, {int(tz)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})"
|
||||
)
|
||||
PY
|
||||
}
|
||||
|
|
@ -153,37 +240,69 @@ print_summary() {
|
|||
fi
|
||||
}
|
||||
|
||||
capture_debug_location() {
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$start_line" 10
|
||||
extract_last_location "$start_line"
|
||||
}
|
||||
|
||||
wait_for_location_in_block() {
|
||||
local expected_x="$1"
|
||||
local expected_y="$2"
|
||||
local expected_z="$3"
|
||||
local timeout="${4:-10}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local actual_x actual_y actual_z
|
||||
read -r actual_x actual_y actual_z <<< "$(capture_debug_location)"
|
||||
if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05:
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
start_mcc() {
|
||||
bash "$PREPARE_CFG_SCRIPT" "$CFG" "$VERSION" CursorBot >/dev/null
|
||||
|
||||
: > "$INPUT_FILE"
|
||||
: > "$LOG"
|
||||
|
||||
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||
tmux new-session -d -s "$SESSION" -x 160 -y 50 \
|
||||
"cd '$REPO_ROOT' && MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release --no-build -- '$CFG' CursorBot - localhost:25565 > '$LOG' 2>&1; echo '=== MCC EXITED ==='; sleep 600"
|
||||
|
||||
wait_for_log "Server was successfully joined." 0 20
|
||||
mkdir -p "$SESSION_ROOT"
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
mcc-build >/dev/null
|
||||
mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --debug-on --no-build >/dev/null
|
||||
wait_for_log "Server was successfully joined." 0 40
|
||||
send_mcc "debug on"
|
||||
sleep 1
|
||||
}
|
||||
|
||||
run_flat_final_stop() {
|
||||
echo "== Flat final stop =="
|
||||
mc-rcon "fill 95 79 95 115 79 105 stone" >/dev/null
|
||||
mc-rcon "fill 95 80 95 115 85 105 air" >/dev/null
|
||||
mc-rcon "tp CursorBot 100.5 80 100.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Flat final stop" "100.5" "80" "100.5"
|
||||
capture_debug_state_before_route "Flat final stop"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 103 80 100"
|
||||
wait_for_navigation "$start_line" 30
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Flat final stop"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(extract_last_location "$start_line")"
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_close "$x" "$y" "$z" "103.50" "80.00" "100.50"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "103" "80.00" "100"
|
||||
print_summary "Flat final stop"
|
||||
}
|
||||
|
||||
|
|
@ -196,18 +315,20 @@ run_parkour_into_turn() {
|
|||
mc-rcon "setblock 122 79 111 stone" >/dev/null
|
||||
mc-rcon "setblock 120 80 111 stone" >/dev/null
|
||||
mc-rcon "setblock 120 81 111 stone" >/dev/null
|
||||
mc-rcon "tp CursorBot 120.5 80 110.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Parkour into L-turn" "120.5" "80" "110.5"
|
||||
capture_debug_state_before_route "Parkour into L-turn"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 122 80 111"
|
||||
wait_for_navigation "$start_line" 30
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Parkour into L-turn"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(extract_last_location "$start_line")"
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_close "$x" "$y" "$z" "122.50" "80.00" "111.50"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "122" "80.00" "111"
|
||||
print_summary "Parkour into L-turn"
|
||||
}
|
||||
|
||||
|
|
@ -221,14 +342,14 @@ run_side_wall_jump() {
|
|||
mc-rcon "setblock 132 81 126 stone" >/dev/null
|
||||
mc-rcon "setblock 133 80 126 stone" >/dev/null
|
||||
mc-rcon "setblock 133 81 126 stone" >/dev/null
|
||||
mc-rcon "tp CursorBot 131.5 80 127.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Rejected 2x1 side-wall jump" "131.5" "80" "127.5"
|
||||
capture_debug_state_before_route "Rejected 2x1 side-wall jump"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 133 80 127"
|
||||
|
||||
if wait_for_failure_signal "$start_line" 20; then
|
||||
assert_direct_rejection_since "$start_line"
|
||||
echo " Pathfinding rejected as expected."
|
||||
else
|
||||
echo " Expected rejection but navigation continued." >&2
|
||||
|
|
@ -244,33 +365,18 @@ run_reject_3x1_gap() {
|
|||
mc-rcon "fill 140 79 135 148 79 140 stone" >/dev/null
|
||||
mc-rcon "fill 140 80 135 148 85 140 air" >/dev/null
|
||||
mc-rcon "setblock 143 80 138 stone" >/dev/null
|
||||
mc-rcon "tp CursorBot 141.5 80 138.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Rejected 3x1 no-run-up gap" "141.5" "80" "138.5"
|
||||
capture_debug_state_before_route "Rejected 3x1 gap"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 144 81 138"
|
||||
|
||||
if wait_for_log "Replan failed" "$start_line" 20; then
|
||||
if wait_for_failure_signal "$start_line" 20; then
|
||||
assert_direct_rejection_since "$start_line"
|
||||
echo " Pathfinding rejected as expected."
|
||||
elif wait_for_navigation "$start_line" 30; then
|
||||
local x y z
|
||||
read -r x y z <<< "$(extract_last_location "$start_line")"
|
||||
if python3 - <<'PY' "$x" "$y" "$z"
|
||||
import sys
|
||||
x, y, z = map(float, sys.argv[1:])
|
||||
tx, ty, tz = 144.5, 81.0, 138.5
|
||||
tol = 0.2
|
||||
sys.exit(0 if abs(x - tx) > tol or abs(y - ty) > tol or abs(z - tz) > tol else 1)
|
||||
PY
|
||||
then
|
||||
echo " Pathfinder only reached a partial fallback, rejection accepted."
|
||||
else
|
||||
echo " Expected rejection but goal was reached." >&2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo " Expected rejection but navigation continued." >&2
|
||||
log_since "$start_line" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
@ -284,18 +390,20 @@ run_corner_ascend_around_wall() {
|
|||
mc-rcon "setblock 191 80 171 stone" >/dev/null
|
||||
mc-rcon "setblock 191 80 170 stone" >/dev/null
|
||||
mc-rcon "setblock 191 81 170 stone" >/dev/null
|
||||
mc-rcon "tp CursorBot 190.5 80 170.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Corner ascend around wall" "190.5" "80" "170.5"
|
||||
capture_debug_state_before_route "Corner ascend around wall"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 191 81 171"
|
||||
wait_for_navigation "$start_line" 25
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Corner ascend around wall"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(extract_last_location "$start_line")"
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_close "$x" "$y" "$z" "191.50" "81.00" "171.50" "0.25"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "191" "81.00" "171"
|
||||
print_summary "Corner ascend around wall"
|
||||
}
|
||||
|
||||
|
|
@ -309,18 +417,20 @@ run_wall_adjacent_descend_smoke() {
|
|||
mc-rcon "setblock 202 80 199 stone" >/dev/null
|
||||
mc-rcon "setblock 201 81 199 stone" >/dev/null
|
||||
mc-rcon "setblock 202 81 199 stone" >/dev/null
|
||||
mc-rcon "tp CursorBot 200.5 81 200.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Wall-adjacent descend" "200.5" "81" "200.5"
|
||||
capture_debug_state_before_route "Wall-adjacent descend"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 201 80 200"
|
||||
wait_for_navigation "$start_line" 25
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Wall-adjacent descend"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(extract_last_location "$start_line")"
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_close "$x" "$y" "$z" "201.50" "80.00" "200.50" "0.25"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "201" "80.00" "200"
|
||||
print_summary "Wall-adjacent descend"
|
||||
}
|
||||
|
||||
|
|
@ -331,30 +441,24 @@ run_ascend_chain_smoke() {
|
|||
mc-rcon "setblock 175 80 162 stone" >/dev/null
|
||||
mc-rcon "setblock 176 81 162 stone" >/dev/null
|
||||
mc-rcon "setblock 177 82 162 stone" >/dev/null
|
||||
mc-rcon "fill 178 78 160 182 78 164 stone" >/dev/null
|
||||
mc-rcon "fill 178 83 160 182 83 164 air" >/dev/null
|
||||
mc-rcon "setblock 181 80 162 minecraft:ladder[facing=east]" >/dev/null
|
||||
mc-rcon "setblock 181 81 162 minecraft:ladder[facing=east]" >/dev/null
|
||||
mc-rcon "setblock 181 82 162 minecraft:ladder[facing=east]" >/dev/null
|
||||
mc-rcon "setblock 181 83 162 minecraft:ladder[facing=east]" >/dev/null
|
||||
mc-rcon "tp CursorBot 171.5 80 160.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Ascend chain smoke" "171.5" "80" "160.5"
|
||||
capture_debug_state_before_route "Ascend chain smoke"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "pathfind 182 83 162"
|
||||
send_mcc "pathfind 177 83 162"
|
||||
wait_for_navigation "$start_line" 35
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Ascend chain smoke"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo " Final location: $x $y $z"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "177" "83.00" "162"
|
||||
|
||||
echo " Ascend chain completed."
|
||||
print_summary "Ascend chain smoke"
|
||||
}
|
||||
|
||||
mcc-preflight "$VERSION" >/dev/null
|
||||
mc-reset-test-env "$VERSION" >/dev/null
|
||||
bash "$ENSURE_SERVER_SCRIPT" "$VERSION" >/dev/null
|
||||
mc-start "$VERSION" >/dev/null
|
||||
mc-wait-ready "$VERSION" 60 >/dev/null
|
||||
mcc-kill >/dev/null 2>&1 || true
|
||||
start_mcc
|
||||
|
||||
mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true
|
||||
|
|
|
|||
|
|
@ -7,17 +7,19 @@ source "$REPO_ROOT/tools/mcc-env.sh"
|
|||
|
||||
VERSION="${1:-1.21.11-Vanilla}"
|
||||
SESSION="mcc-brake-test"
|
||||
TEST_ROOT="${TMPDIR:-/tmp}/mcc-debug"
|
||||
CFG="$TEST_ROOT/MinecraftClient.transition-braking.ini"
|
||||
LOG="$TEST_ROOT/mcc-transition-braking.log"
|
||||
INPUT_FILE="$REPO_ROOT/mcc_input.txt"
|
||||
PREPARE_CFG_SCRIPT="$REPO_ROOT/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh"
|
||||
ENSURE_SERVER_SCRIPT="$REPO_ROOT/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh"
|
||||
USERNAME="CursorBot"
|
||||
|
||||
mkdir -p "$TEST_ROOT"
|
||||
SESSION_ROOT="$(_mcc_session_root "$SESSION")"
|
||||
LOG="$(_mcc_session_log_file "$SESSION")"
|
||||
|
||||
cleanup() {
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
send_mcc() {
|
||||
echo "$1" >> "$INPUT_FILE"
|
||||
mcc-cmd --session "$SESSION" "$1"
|
||||
}
|
||||
|
||||
log_line_count() {
|
||||
|
|
@ -40,10 +42,10 @@ log_since() {
|
|||
wait_for_log() {
|
||||
local pattern="$1"
|
||||
local from_line="${2:-0}"
|
||||
local timeout="${3:-20}"
|
||||
local timeout="${3:-30}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
if log_since "$from_line" | grep -Fq "$pattern"; then
|
||||
if log_since_clean "$from_line" | grep -Fq "$pattern"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
|
|
@ -55,16 +57,26 @@ wait_for_log() {
|
|||
wait_for_navigation() {
|
||||
local from_line="$1"
|
||||
local timeout="${2:-20}"
|
||||
local saw_start=0
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local recent
|
||||
recent="$(log_since "$from_line")"
|
||||
recent="$(log_since_clean "$from_line")"
|
||||
|
||||
if grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then
|
||||
if grep -Fq "[PathMgr] Navigation started" <<<"$recent"; then
|
||||
saw_start=1
|
||||
fi
|
||||
|
||||
if (( saw_start )) && grep -Fq "[PathMgr] Navigation complete!" <<<"$recent"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -Eq "\\[PathMgr\\] (Replan failed|Giving up)|\\[PathExec\\] Segment .* FAILED" <<<"$recent"; then
|
||||
if grep -Eq "\[PathMgr\] (Replan failed|Giving up)" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if grep -Eq "No path found|\[Navigate\] A\* result: Failed" <<<"$recent"; then
|
||||
echo "$recent" >&2
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -73,10 +85,71 @@ wait_for_navigation() {
|
|||
done
|
||||
|
||||
echo "Timed out waiting for navigation completion" >&2
|
||||
log_since "$from_line" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
log_since_clean() {
|
||||
log_since "$1" | sed 's/\x1b\[[0-9;]*m//g'
|
||||
}
|
||||
|
||||
count_replans_since() {
|
||||
local from_line="$1"
|
||||
log_since_clean "$from_line" | grep -Ec '\[PathMgr\] Replan #|\[PathExec\] Segment .* FAILED' || true
|
||||
}
|
||||
|
||||
assert_no_replans_since() {
|
||||
local from_line="$1"
|
||||
local count
|
||||
count="$(count_replans_since "$from_line")"
|
||||
if [[ "$count" != "0" ]]; then
|
||||
echo "Expected 0 replans, saw $count" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_no_partial_since() {
|
||||
local from_line="$1"
|
||||
if log_since_clean "$from_line" | grep -Fq "[Navigate] A* result: Partial"; then
|
||||
echo "Expected full success path, saw partial path" >&2
|
||||
log_since_clean "$from_line" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
debug_state_snapshot() {
|
||||
local label="$1"
|
||||
local from_line
|
||||
from_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$from_line" 10
|
||||
echo ""
|
||||
echo "=== Debug state: $label ==="
|
||||
log_since_clean "$from_line"
|
||||
}
|
||||
|
||||
capture_debug_state_before_route() {
|
||||
debug_state_snapshot "before route - $1"
|
||||
}
|
||||
|
||||
capture_debug_state_after_route() {
|
||||
debug_state_snapshot "after route - $1"
|
||||
}
|
||||
|
||||
prepare_independent_route() {
|
||||
local label="$1"
|
||||
local start_x="$2"
|
||||
local start_y="$3"
|
||||
local start_z="$4"
|
||||
|
||||
echo ""
|
||||
echo "Preparing independent route: $label"
|
||||
mc-rcon "effect clear $USERNAME" >/dev/null 2>&1 || true
|
||||
mc-rcon "tp $USERNAME $start_x $start_y $start_z" >/dev/null
|
||||
wait_for_location_in_block "$start_x" "$start_y" "$start_z" 10
|
||||
}
|
||||
|
||||
extract_last_location() {
|
||||
local from_line="${1:-0}"
|
||||
|
||||
|
|
@ -90,7 +163,7 @@ from_line = int(sys.argv[2])
|
|||
text = log_path.read_text(errors="ignore")
|
||||
text = "\n".join(text.splitlines()[from_line:])
|
||||
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
|
||||
matches = re.findall(r"Location\s+([-\d.]+),\s+([-\d.]+),\s+([-\d.]+)", text)
|
||||
matches = re.findall(r"Location\s+([-,0-9.]+),\s+([-,0-9.]+),\s+([-,0-9.]+)", text)
|
||||
if not matches:
|
||||
raise SystemExit("No Location line found in MCC log")
|
||||
x, y, z = matches[-1]
|
||||
|
|
@ -98,23 +171,22 @@ print(f"{x} {y} {z}")
|
|||
PY
|
||||
}
|
||||
|
||||
assert_close() {
|
||||
assert_inside_goal_block() {
|
||||
local actual_x="$1"
|
||||
local actual_y="$2"
|
||||
local actual_z="$3"
|
||||
local expected_x="$4"
|
||||
local expected_y="$5"
|
||||
local expected_z="$6"
|
||||
local tolerance="${7:-0.05}"
|
||||
|
||||
python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z" "$tolerance"
|
||||
python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez, tol = map(float, sys.argv[1:])
|
||||
if abs(ax - ex) > tol or abs(ay - ey) > tol or abs(az - ez) > tol:
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) != int(ex) or math.floor(az) != int(ez) or abs(ay - ey) > 0.05:
|
||||
raise SystemExit(
|
||||
f"Expected ({ex:.2f}, {ey:.2f}, {ez:.2f}) within {tol:.2f}, got ({ax:.2f}, {ay:.2f}, {az:.2f})"
|
||||
f"Expected location inside goal block ({int(ex)}, {ey:.2f}, {int(ez)}), got ({ax:.2f}, {ay:.2f}, {az:.2f})"
|
||||
)
|
||||
PY
|
||||
}
|
||||
|
|
@ -123,71 +195,90 @@ capture_debug_location() {
|
|||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "debug state"
|
||||
wait_for_log "Location" "$start_line" 5
|
||||
wait_for_log "Location" "$start_line" 10
|
||||
extract_last_location "$start_line"
|
||||
}
|
||||
|
||||
wait_for_location_in_block() {
|
||||
local expected_x="$1"
|
||||
local expected_y="$2"
|
||||
local expected_z="$3"
|
||||
local timeout="${4:-10}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local actual_x actual_y actual_z
|
||||
read -r actual_x actual_y actual_z <<< "$(capture_debug_location)"
|
||||
if python3 - <<'PY' "$actual_x" "$actual_y" "$actual_z" "$expected_x" "$expected_y" "$expected_z"
|
||||
import math
|
||||
import sys
|
||||
|
||||
ax, ay, az, ex, ey, ez = map(float, sys.argv[1:])
|
||||
if math.floor(ax) == int(ex) and math.floor(az) == int(ez) and abs(ay - ey) <= 0.05:
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Timed out waiting for player to reach start block ($expected_x, $expected_y, $expected_z)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
start_mcc() {
|
||||
bash "$PREPARE_CFG_SCRIPT" "$CFG" "$VERSION" CursorBot >/dev/null
|
||||
|
||||
: > "$INPUT_FILE"
|
||||
: > "$LOG"
|
||||
|
||||
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||
tmux new-session -d -s "$SESSION" -x 160 -y 50 \
|
||||
"cd '$REPO_ROOT' && MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release --no-build -- '$CFG' CursorBot - localhost:25565 > '$LOG' 2>&1; echo '=== MCC EXITED ==='; sleep 600"
|
||||
|
||||
wait_for_log "Server was successfully joined." 0 20
|
||||
mcc-kill --session "$SESSION" >/dev/null 2>&1 || true
|
||||
mkdir -p "$SESSION_ROOT"
|
||||
mcc-build >/dev/null
|
||||
mcc-debug -v "$VERSION" --session "$SESSION" --username "$USERNAME" --file-input --no-build --debug-on >/dev/null
|
||||
wait_for_log "Server was successfully joined." 0 30
|
||||
send_mcc "debug on"
|
||||
sleep 1
|
||||
}
|
||||
|
||||
run_flat_final_stop() {
|
||||
echo "== Flat final stop =="
|
||||
mc-rcon "fill 95 79 95 115 79 105 stone" >/dev/null
|
||||
mc-rcon "fill 95 80 95 115 85 105 air" >/dev/null
|
||||
mc-rcon "tp CursorBot 100.5 80 100.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Flat final stop" "100.5" "80" "100.5"
|
||||
capture_debug_state_before_route "Flat final stop"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "goto 103 80 100"
|
||||
wait_for_navigation "$start_line" 20
|
||||
sleep 1
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Flat final stop"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo "Final location: $x $y $z"
|
||||
assert_close "$x" "$y" "$z" "103.50" "80.00" "100.50"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "103" "80.00" "100"
|
||||
}
|
||||
|
||||
run_parkour_into_turn() {
|
||||
echo "== Parkour into turn =="
|
||||
mc-rcon "fill 118 79 108 126 79 112 air" >/dev/null
|
||||
mc-rcon "fill 118 80 108 126 85 112 air" >/dev/null
|
||||
mc-rcon "setblock 120 79 110 stone" >/dev/null
|
||||
mc-rcon "setblock 123 79 110 stone" >/dev/null
|
||||
mc-rcon "setblock 123 79 111 stone" >/dev/null
|
||||
mc-rcon "tp CursorBot 120.5 80 110.5" >/dev/null
|
||||
sleep 2
|
||||
|
||||
prepare_independent_route "Parkour into turn" "120.5" "80" "110.5"
|
||||
capture_debug_state_before_route "Parkour into turn"
|
||||
local start_line
|
||||
start_line="$(log_line_count)"
|
||||
send_mcc "goto 123 80 111"
|
||||
wait_for_navigation "$start_line" 20
|
||||
sleep 1
|
||||
assert_no_partial_since "$start_line"
|
||||
assert_no_replans_since "$start_line"
|
||||
capture_debug_state_after_route "Parkour into turn"
|
||||
|
||||
local x y z
|
||||
read -r x y z <<< "$(capture_debug_location)"
|
||||
echo "Final location: $x $y $z"
|
||||
assert_close "$x" "$y" "$z" "123.50" "80.00" "111.50"
|
||||
assert_inside_goal_block "$x" "$y" "$z" "123" "80.00" "111"
|
||||
}
|
||||
|
||||
mcc-preflight "$VERSION" >/dev/null
|
||||
mc-reset-test-env "$VERSION" >/dev/null
|
||||
bash "$ENSURE_SERVER_SCRIPT" "$VERSION" >/dev/null
|
||||
mc-start "$VERSION" >/dev/null
|
||||
mc-wait-ready "$VERSION" 60 >/dev/null
|
||||
mcc-kill >/dev/null 2>&1 || true
|
||||
start_mcc
|
||||
|
||||
mc-rcon "difficulty peaceful" >/dev/null 2>&1 || true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue