Add exclusion zones to movement settings (like upstream mineflayer-pathfinder)#5
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16d5f0c4e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| exclusionAreasStep: [], | ||
| exclusionAreasBreak: [], | ||
| exclusionAreasPlace: [] |
There was a problem hiding this comment.
Avoid sharing default exclusion arrays
Because the default option object now contains mutable arrays and the rest of the code copies it with shallow spreads/Object.assign, any in-place update to a bot's public defaultMoveSettings.exclusionAreasStep (or to DEFAULT_MOVEMENT_OPTS) mutates this shared default array. In that scenario, later pathfinders or later setMoveOptions calls that fall back to the default will inherit zones from another bot/session; use fresh arrays when constructing defaults instead of storing these shared [] instances.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 508ff8b. Added buildMovementOptions(), which merges over the defaults and always returns fresh copies of the three exclusion arrays, and used it at all four build sites (Movement ctor, MovementHandler.create, ThePathfinder ctor and setMoveOptions). The default arrays are also frozen now. Added regression tests asserting independent (non-shared) arrays and frozen defaults.
…ings DEFAULT_MOVEMENT_OPTS held one shared [] per exclusion list, and every settings object was built with a shallow Object.assign/spread. So when a user didn't override the arrays, all settings objects pointed at the SAME array instance. Mutating one in place (e.g. bot.pathfinder.defaultMoveSettings.exclusionAreasStep.push(zone)) leaked zones into other bots in the same process and into later setMoveOptions calls, and could even pollute the module-level default. Fix: add buildMovementOptions(), which merges over the defaults and always returns fresh copies of the three exclusion arrays. Use it at all four build sites (Movement ctor, MovementHandler.create, ThePathfinder ctor and setMoveOptions). The default arrays are also frozen as a safety net. Adds regression tests: fresh/independent arrays per settings object, user-supplied arrays are copied (not referenced), and the defaults are frozen. Full suite: 28/28. Reported by the codex review bot on PR Minecraft-Pathfinding#5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
508ff8b to
4c9c8fe
Compare
GenerelSchwerz
left a comment
There was a problem hiding this comment.
There are a lot of changes to be made. Also, the exclusion zones are not properly addressed (exclusionStep, notably), during post-processing at all, resulting in optimized paths still interacting with excluded terrain.
| } | ||
| ``` | ||
|
|
||
| <h1 align="center">Exclusion Zones (Keep-Out Areas)</h1> |
There was a problem hiding this comment.
These docs are too conversational. Make this more concise.
|
|
||
| <h3>How it works</h3> | ||
|
|
||
| An **exclusion area** is just a function. You give it one block, and it returns |
There was a problem hiding this comment.
Again, a bit too conversational. Cut it down.
|
|
||
| <h3>Ready-made zone shapes</h3> | ||
|
|
||
| You usually don't need to write the function yourself. These helpers build the |
There was a problem hiding this comment.
These comments are to be removed, adding utility functions is not in scope for this library. Users are expected to come up with these themselves, imo. this one can be argued.
| * @param cost extra cost for blocks inside the box. Defaults to "never" | ||
| * ({@link EXCLUSION_NEVER}). Pass a smaller positive number for a "soft" zone. | ||
| */ | ||
| export function createBoxExclusion (corner1: Vec3, corner2: Vec3, cost: number = EXCLUSION_NEVER): ExclusionArea { |
There was a problem hiding this comment.
This is a fine implementation, but these should be shown in examples rather than be directly included in the library in the interest of encouraging users to create their own functions for this.
| // Exclusion zones: forbid or make-expensive any move that ends with the bot | ||
| // standing inside a "step" keep-out area. When no step areas are configured | ||
| // (the default), this is skipped entirely, so normal pathfinding pays nothing. | ||
| if (this.settings.exclusionAreasStep.length > 0) { |
There was a problem hiding this comment.
I have no idea what this is. This should be calculated during actual A* calculations. This seems entirely incorrect and should be removed. Convince me otherwise.
There was a problem hiding this comment.
Upon reading further, I believe I am correct. ExclusionPlace and ExclusionBreak are handled more or less correctly--exclusionStep is handled in the wrong place and must be fixed. This should be handled during the calculation step, not after the path has been made.
| * jumping, dropping, parkour, towers — respects the same zones, with no extra | ||
| * work when no zones are configured. | ||
| */ | ||
| private applyStepExclusions (moves: Move[]): void { |
There was a problem hiding this comment.
https://github.com/Minecraft-Pathfinding/minecraft-pathfinding/pull/5/changes#r3422198575
Same issue. This appears to be entirely redundant and should not be handled here.
| toBreak: BreakHandler[], | ||
| public readonly remainingBlocks: number, | ||
| public readonly cost: number, | ||
| // NOTE: not readonly on purpose. MovementHandler.getNeighbors may add a small |
There was a problem hiding this comment.
This is also incorrect. This should remain readonly and the exclusionStep cost should be calculated before the move is ever made. Convince me otherwise in terms of performance only. The rest of the system is constructed in this way.
| export type { MovementOptions } from './mineflayer-specific/movements' | ||
|
|
||
| // Exclusion zones ("keep out" areas), like upstream mineflayer-pathfinder. | ||
| export { |
There was a problem hiding this comment.
IMO, get rid of these entirely and move them to examples. Utilities are not needed.
|
My bad about the selections for the review btw, I have never done that before. I'll make sure to highlight the correct sections next time. The line that was selected for each comment is accurate though. |
…eam) Add "keep out" exclusion zones to the movement settings, matching the exclusion-area concept from upstream PrismarineJS/mineflayer-pathfinder. - exclusionAreasStep / exclusionAreasBreak / exclusionAreasPlace in MovementOptions (default []). Each is a (block: BlockInfo) => number; a sum >= COST_INF forbids the block, a positive value is a soft penalty. - Step exclusion is applied inside each movement provider, on the block the bot lands in (already fetched during move generation), folded into the move cost BEFORE the move is created. So Move.cost stays readonly, there is no extra/unrouted block lookup, and forbidden landings are simply never generated (no array splicing). Covers walk/jump/drop/parkour/tower. - Break/place exclusion is folded into Movement.breakCost / placeCost, which every break/place caller already routes through. - buildMovementOptions() merges over the defaults and always hands out fresh copies of the three arrays (defaults are frozen), so bots/sessions never share or mutate the same array. - Helpers createBoxExclusion / createRadiusExclusion / createColumnRadiusExclusion, EXCLUSION_NEVER, and the ExclusionArea type. - Tests (28/28), docs (API + AdvancedUsage), and an example.
4c9c8fe to
63a63dd
Compare
- Post-processing: the Optimizer now refuses to straight-line a merge whose swept line crosses a HARD step-exclusion zone, so optimized/straight-lined paths no longer cut through "keep out" terrain the A* route went around. Soft zones still allow straightening (a preference, not a wall). The check is central in Optimizer.compute, so every optimizer respects it. - Remove the box/radius/column zone-builder utilities from the library and keep only the ExclusionArea type; the shape helpers now live in examples/exclusionZones.js for users to copy. Utilities are out of scope. - Trim the Exclusion Zones docs (API.md, AdvancedUsage.md) to be concise. - Tests: add two deterministic optimizer-guard tests (merge skipped across a hard zone; merged freely without one); drop the now-removed helper unit tests. Build clean, 25/25 tests pass.
- Optimizer: replace the approximate 0.5-block sampling in the hard-zone check with an exact voxel traversal (Amanatides & Woo). It visits exactly the cells the merged segment crosses, so it can neither skip a thin hard cell nor do redundant block lookups -- more correct and cheaper. - Reuse: extract sumExclusionAreas() and call it from exclusionStep/Break/Place and the optimizer's hard-zone check, instead of repeating the same sum loop four times. - Add an optimizer test for diagonal merges (a single hard cell on the diagonal), which exercises the voxel traversal's tie handling. Build clean, 26/26 tests pass.
|
Superseded by a clean single-commit PR: #6. |
What & why
Adds exclusion zones ("keep out" areas) to the movement settings, bringing back the exclusion-area concept from upstream
PrismarineJS/mineflayer-pathfinder. The2026-rewritebranch kept the doc comments referencingexclusionStep/exclusionBreak/stepExclusionAreasbut the implementation had been removed — this wires it back up cleanly.You can now tell the bot to avoid (softly) or never enter (hard) a region, and likewise restrict where it may mine or build.
New settings (
MovementOptions)All default to
[], so there is zero overhead when unused:exclusionAreasStepexclusionAreasBreakexclusionAreasPlaceEach entry is a function
(block: BlockInfo) => numberreturning the extra cost of using that block;>= COST_INF(orInfinity) forbids it. Costs from every area in a list are summed. This is API-compatible with upstream's exclusion areas.Helpers (exported from the package root)
createBoxExclusion(corner1, corner2, cost?)— axis-aligned box (corners inclusive, any order)createRadiusExclusion(center, radius, cost?)— sphere (height counts)createColumnRadiusExclusion(center, radius, cost?)— vertical pillar (ignores height)EXCLUSION_NEVERconstant and theExclusionAreatypeHow it's implemented
Move.coststaysreadonly, there is no extra/unrouted block lookup, and forbidden landings are simply never generated (no array splicing). Covers walking, jumping, dropping, parkour and towers.Movement.breakCost/Movement.placeCost, which every break/place caller already routes through.buildMovementOptions()merges over the defaults and always hands out fresh copies of the three arrays (the defaults are frozen), so two bots — or twosetMoveOptionscalls — never share and then mutate the same array.>= COST_INF) skip the move; soft zones just add their cost.Tests
tests/exclusionZones.test.ts(runs in the existingnode --testrig):Full suite: 25/25 passing (17 pre-existing + 8 new).
tscbuild is clean.Docs & example
docs/API.md: Settings table + a dedicated Exclusion Zones section.docs/AdvancedUsage.md: a beginner-friendly "Keep-Out Areas" section.examples/exclusionZones.js: a runnable chat-driven demo.Scope note
This PR is intentionally focused: it touches only the files needed for the feature, plus its docs and example. It does not include the repo's pre-existing
ts-standardlint failures, which exist independently of this change across unrelated files.