') + ')', '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); } })(); })(); Add "Bale windrows" AI job by BrewingCoder · Pull Request #1294 · Courseplay/Courseplay_FS25 · GitHub
Skip to content

Add "Bale windrows" AI job - #1294

Draft
BrewingCoder wants to merge 2 commits into
Courseplay:mainfrom
BrewingCoder:feature/bale-windrow-job
Draft

Add "Bale windrows" AI job#1294
BrewingCoder wants to merge 2 commits into
Courseplay:mainfrom
BrewingCoder:feature/bale-windrow-job

Conversation

@BrewingCoder

Copy link
Copy Markdown

Adds a new Courseplay AI job, Bale windrows, that generates a course following the actual straw/hay/grass windrows lying on the field, so an attached baler drives only where the product is instead of a generic up/down pattern that over-covers empty ground.

Closes#1293

What it does

  • Baler-gated: the job is only offered when a baler is attached (getCanStartCpBaleWindrowsAIUtil.hasChildVehicleWithSpecialization(Baler)).
  • On Generate, it detects the field and reads the windrows off the ground via DensityMapHeightUtil, then builds a fieldwork course whose work rows trace them.
  • Windrows are read dynamically as density ridges — each row is found at its own angle/curve, nothing globally imposed (no fixed row angle or spacing).
  • Headland windrows (straw that wraps the boundary) are driven first, then the interior rows are swept edge-to-edge.
  • Rows are WORK waypoints, so CP lowers/raises the baler and generates the turns itself; a short in-field overrun past each row end keeps the baler down across the whole windrow.

Notes for review

  • The windrow sweep runs once, on the Generate action only — never in the boundary-detection/validate loop (which fires every frame).
  • Baled-over residue is filtered by a straw-level cutoff so a baled field reads as empty.
  • Phantom headland rings from rows that merely run parallel to an edge are rejected by a boundary-wrap coverage test (offset the boundary inward by the ring distance; a real ring lands on straw all the way around).
  • CpObject change: propagate the Giants base constructor down the whole inheritance chain so a grandchild job (this one) initializes its AIJob base.

Still iterating on in-field tuning (row-end coverage overrun, residue cutoff), but the job is functional end-to-end. Feedback on approach welcome.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KhF4kNA6TWw3EJ8hy99SHM

BrewingCoderand others added 2 commits July 10, 2026 12:31
…e that follows them
New AI job type (BALE_WINDROWS_CP), selectable when a baler is attached to a
tractor. It detects the field boundary, detects the straw/hay/grass windrows
lying on the ground, and generates a fieldwork course whose rows follow those
windrows so the baler is driven exactly over the product. Extends the fieldwork
job to reuse all driving, turning and implement control; the course is generated
automatically from the detected windrows once the field boundary is known.
- scripts/field/WindrowDetector.lua: sweeps DensityMapHeightUtil probes over the
field polygon, picks the cross-row axis by histogram peakiness, clusters the
product cells into per-windrow lanes and fits each to a line.
- CourseGeneratorInterface:generateWindrowCourse: feeds the windrow lines as rows
into FieldworkCourseVine/CenterPredefinedRows (rows on the lines), reusing the
existing row-sequencing + 180-degree turn generation.
- CpAIJobBaleWindrows (extends CpAIJobFieldWork) + CpAIBaleWindrows vehicle spec
gating availability on an attached Baler (complement of the bale finder, which
collects finished bales and excludes balers).
- Registration in CpAIJob.registerJob, Courseplay.register, modDesc.xml, and the
CP_job_baleWindrows / CP_error_no_windrows translations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up fixes to the Bale windrows job:
- WindrowDetector: read windrows as density ridges (per-row angle/curve,
nothing globally imposed); merge collinear crest pieces so a gap-split
windrow is one row; straw-level cutoff + histogram to drop baled residue;
reject phantom headland rings via boundary-wrap coverage so rows parallel
to an edge are not mistaken for rings.
- CourseGeneratorInterface: mitre headland-ring corners (stay inside field);
sweep interior rows edge-to-edge instead of greedy-from-middle; extend each
row a short in-field overrun so the baler covers the whole windrow (row
start/end markers land on bare ground, not on product).
- CpObject: propagate the Giants base constructor down the whole inheritance
chain so a grandchild job initializes its AIJob base.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhF4kNA6TWw3EJ8hy99SHM
@pvaiko

Copy link
Copy Markdown
Contributor

This is a very cool feature.

However, it shows all the problems of AI generated code, for instance it does not reuse the existing code (such as the headland generation), it pollutes the CourseGeneratorInterface, which supposed to a thin adapter layer only, with business logic, etc.

Sure, this could be improved by adding an AGENTS.md, several skills, but that needs time as well as a dedicated, skilled, motivated person.

And even then, it is questionable when the code quality (which isn't perfect now of course, but is ok) will deteriorate to the point where no one, including Claude will be able to fix small bugs.

Elsewhere I follow the policy that vibe coding is only allowed for non-production code, such as development or monitoring tools or utilities. In those cases only, code review is not needed, no human needs to understand the code, we ignore code quality and improvements and fixes are done by AI (again, how long this works depends on many factors, but every change will likely deteriorate code quality).

Every production code though must go through a strict code review and a human must understand the code. This however can be very time consuming, for example, WindrowDetector.lua is way too big and complicated compared to the time and motivation I have.

We could also just give up and declare that some parts (must be very well defined parts) of CP are AI coded, but then comes the question of maintenance. Who is going to fix a bug in WindrowDetector? The context likely would be lost long ago (unless some agent or skill file checked in with the code), are you @BrewingCoder ready to support this for years?

We'll think about how CP wants the handle AI code.

@Tensuko
Tensuko marked this pull request as draft July 31, 2026 11:17
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.

CP Work mode for bailing windrows

2 participants

@BrewingCoder@pvaiko