Skip to content

Recover from being blocked by a static obstacle during fieldwork#1287

Open
helgehelge123 wants to merge 1 commit into
Courseplay:mainfrom
helgehelge123:feature/fieldwork-blocked-recovery
Open

Recover from being blocked by a static obstacle during fieldwork#1287
helgehelge123 wants to merge 1 commit into
Courseplay:mainfrom
helgehelge123:feature/fieldwork-blocked-recovery

Conversation

@helgehelge123

Copy link
Copy Markdown

Problem

The ProximityController stops the vehicle in front of any obstacle and already fires onBlockingObject / onBlockingVehicle after ~5/7 seconds of blockade - but during normal fieldwork no listener is registered (only AITurn registers one during turns). A vehicle blocked mid-course by a tree, a pole or a parked vehicle stands there forever, showing BLOCKED_BY_OBJECT.

Solution

AIDriveStrategyFieldWorkCourse now registers both blocking listeners and recovers, reusing existing building blocks (Course.createStraightReverseCourse, startPathfindingToNextWaypoint):

  1. raise implements, back up 10 m (new REVERSING_AFTER_BLOCKED state, added to the implement controllers' default disabled states),
  2. pathfind around the obstacle to the first course waypoint at least 20 m away and resume there via the existing work starter (the strip the obstacle occupies cannot be worked anyway),
  3. after 3 failed attempts in the same area, stop the job with AIMessageErrorBlockedByObject instead of looping.

Notes:

  • Blocking vehicles are only treated like a static obstacle when they are stationary (< 1 km/h) - moving vehicles are still waited for. This also covers AI-driven vehicles that stand still while blocking (e.g. a harvester standing bent across the path). The combine strategy keeps its own onBlockingVehicle handling, as it registers its listener after this one.
  • AITurn unregisters the blocking object listener at the end of every turn (single-slot registration), so the listener is re-registered in the WORKING branch of getDriveData.
  • Tunables are class constants (blockedRecoveryReverseDistance, blockedRecoverySkipDistance, blockedRecoveryMaxAttempts).

Testing

Tested in singleplayer with obstacles placed mid-row and on headlands. Feedback on edge cases (convoys, vine fields) is very welcome.

🤖 Generated with Claude Code

The ProximityController stops the vehicle in front of any obstacle and
already fires onBlockingObject/onBlockingVehicle after a few seconds,
but no listener was registered while working, so a vehicle blocked by
a tree, a pole or a parked vehicle simply stood there forever with the
BLOCKED_BY_OBJECT info text.

AIDriveStrategyFieldWorkCourse now registers both listeners. When
blocked in the WORKING state it raises the implements, backs up 10m
(new REVERSING_AFTER_BLOCKED state) and then uses the existing
startPathfindingToNextWaypoint() to find a way around the obstacle,
resuming the course at the first waypoint at least 20m away (the strip
the obstacle occupies cannot be worked anyway).

Blocking vehicles are only treated like a static obstacle when they
are stationary; moving vehicles are still waited for, and the combine
strategy keeps its own blocking-vehicle handling. Since AITurn
unregisters the blocking object listener at the end of each turn, the
listener is re-registered while working.

After 3 failed attempts in the same area the job is stopped with
AIMessageErrorBlockedByObject instead of looping forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Tensuko Tensuko requested a review from pvaiko July 4, 2026 22:29

--- First waypoint of the fieldwork course that is far enough from the vehicle so that resuming there
--- clears the obstacle (the strip the obstacle occupies can't be worked anyway)
function AIDriveStrategyFieldWorkCourse:getWaypointToContinueBeyondObstacle()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use the existing Course:getNextWaypointIxWithinDistance() instead.

-- instead of standing in front of a static obstacle forever, try to recover
-- (subclasses like the combine strategy may overwrite the vehicle listener with their own)
self.proximityController:registerBlockingObjectListener(self, AIDriveStrategyFieldWorkCourse.onBlockedByObject)
self.proximityController:registerBlockingVehicleListener(self, AIDriveStrategyFieldWorkCourse.onBlockedByVehicle)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be overwritten in AIDriveStrategyCombineCourse for combines.

self.fieldWorkerProximityController = FieldWorkerProximityController(self.vehicle, self.workWidth)
-- instead of standing in front of a static obstacle forever, try to recover
-- (subclasses like the combine strategy may overwrite the vehicle listener with their own)
self.proximityController:registerBlockingObjectListener(self, AIDriveStrategyFieldWorkCourse.onBlockedByObject)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sets the listener in all modes, such as DRIVING_TO_WORK_START_WAYPOINT, and incorrectly result in starting to work (lower implements) after driving around the obstacle. Would be cleaner to only register in the WORKING state.

self:onBlockedByObject(isBack)
end

--- First waypoint of the fieldwork course that is far enough from the vehicle so that resuming there

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could lead to all kinds of colorful scenarios whenever there is a special waypoint within the skip distance, such as a turn, or a connecting path transition.

-- turns and the other states have their own blocked handling
return
end
-- limit the number of attempts in the same area so we do not bounce between obstacles forever

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With 50 meters, what happens when we meet the same object in the following up/down rows?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants