') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Account for articulated harvester components when moving out of the way by helgehelge123 · Pull Request #1288 · Courseplay/Courseplay_FS25 · GitHub
Skip to content

Account for articulated harvester components when moving out of the way - #1288

Open
helgehelge123 wants to merge 2 commits into
Courseplay:ai-pr-testingfrom
helgehelge123:fix/articulated-move-away-offset
Open

Account for articulated harvester components when moving out of the way#1288
helgehelge123 wants to merge 2 commits into
Courseplay:ai-pr-testingfrom
helgehelge123:fix/articulated-move-away-offset

Conversation

@helgehelge123

@helgehelge123helgehelge123 commented Jul 4, 2026

Copy link
Copy Markdown

Problem

When the unloader moves away from a blocking CP combine (AIDriveStrategyUnloadCombine:onBlockingVehicle), the parallel course offset is computed as own width/2 + work width/2 + 2 around the combine's AI direction node. Articulated harvesters (e.g. the spinach harvester) can stand bent, with the rear component sticking out sideways well beyond that offset - the unloader then keeps touching the rear part and never gets past it.

Solution

Scan the actual, current extents of the blocking vehicle with the existing VehicleSizeScanner (relative to its AI direction node) and use the larger of the scanned half width and the half work width for the offset. For rigid vehicles the scanned half width stays at (or below) the work-width-based value, so behavior there is unchanged. The scan runs once per blocking event, the same pattern PathfinderUtil.VehicleData uses at pathfinder start.

Testing

Reproduced with a tractor + trailer blocked by a bent articulated spinach harvester; with the wider offset the unloader clears the rear component. (Originally verified with a component-position-based approximation; updated to VehicleSizeScanner per review - in-game re-test of the scanner version pending, will report here.)

🤖 Generated with Claude Code

When the unloader moves away from a blocking CP combine, the parallel
course offset was computed from the work width around the AI direction
node only. Articulated harvesters (e.g. the spinach harvester) can
stand bent, with the rear component sticking out sideways well beyond
that, so the unloader kept touching the rear part and never got past.
Include the lateral extent of all components of the blocking vehicle
in the offset calculation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Tensuko
Tensuko requested a review from pvaikoJuly 4, 2026 22:29
Comment threadscripts/ai/strategies/AIDriveStrategyUnloadCombine.lua Outdated
Instead of approximating the widest point from component node positions
(which assumes full vehicle width for every component and ignores how far
a bent rear section really reaches), scan the actual current extents of
the blocking vehicle with the existing VehicleSizeScanner, as suggested
in review. One-time scan on the (rare) blocking event, same pattern as
PathfinderUtil.VehicleData.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Tensuko
Tensuko changed the base branch from main to ai-pr-testingJuly 31, 2026 11:10
Sign up for freeto 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

@helgehelge123@pvaiko