Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0420176
Web map: long curves cost less to draw (an older PC no longer freezes)
claude Nov 12, 2026
7454586
Headland distance box: no held value with the headland off
claude Nov 12, 2026
e294ca9
Closed curve passes: no step back at the seam
claude Nov 12, 2026
be920f7
Closed curve passes: the arc round the seam goes through the collisio…
claude Nov 12, 2026
26a0a76
Merge pull request #818 from renatorgr/fix/long-curve-draw-cost-upstream
chriskinal Nov 13, 2026
bf3ba81
Merge pull request #819 from renatorgr/fix/headland-distance-off-upst…
chriskinal Nov 13, 2026
5396285
compare-reports: read the findings tables of the web client and worki…
chriskinal Nov 13, 2026
51bbb7f
Merge pull request #822 from AgOpenGPS-Official/fix/compare-table-rep…
chriskinal Nov 13, 2026
6cc1449
field.geojson: a format version, and what this build doesn't read is …
chriskinal Nov 13, 2026
7e16881
job.json and coverage: versioned like field.geojson
chriskinal Nov 13, 2026
9b19ea4
Log each lateral move, and the AB line's heading only when it changes
claude Nov 13, 2026
0608668
U-turn tried again off a hard fence stays at the end of the pass
claude Nov 13, 2026
cde7d54
Merge pull request #825 from AgOpenGPS-Official/fix/field-format-version
chriskinal Nov 13, 2026
bc38e9d
Runner compare: every fixture's report, and an incomplete comparison …
chriskinal Nov 13, 2026
dad77f3
Job harness: a job that never got past its first pass is a finding (J…
chriskinal Nov 13, 2026
6442be8
Merge pull request #821 from renatorgr/fix/closed-pass-seam-upstream
chriskinal Nov 13, 2026
3becda5
Merge pull request #828 from renatorgr/fix/uturn-setback-end-of-pass-…
chriskinal Nov 13, 2026
42555d1
Merge pull request #827 from renatorgr/fix/log-lateral-moves-upstream
chriskinal Nov 13, 2026
5babf3a
Merge pull request #829 from AgOpenGPS-Official/fix/runner-report-per…
chriskinal Nov 13, 2026
b5d2d3e
RigGeometry: where the tool hangs on the tractor, worked out in one p…
chriskinal Nov 13, 2026
d331e92
Merge pull request #830 from AgOpenGPS-Official/refactor/rig-geometry
chriskinal Nov 13, 2026
8ae2279
Hitch model: the tractor's hitch points on the vehicle, a fixed tool'…
chriskinal Nov 13, 2026
a6cc596
Update harness: read back a fixed tool's working point from the rear …
chriskinal Nov 13, 2026
9b95738
Merge pull request #831 from AgOpenGPS-Official/feat/hitch-model
chriskinal Nov 14, 2026
422d5e5
Hitch diagrams: the vehicle's distances to its lower-link pins, a fix…
chriskinal Nov 14, 2026
af2346b
Merge pull request #832 from AgOpenGPS-Official/feat/hitch-diagrams
chriskinal Nov 14, 2026
5168bcf
Update translation files
weblate Nov 14, 2026
fa6155b
Update translation files
weblate Nov 14, 2026
9c5f7a3
Update translation files
weblate Nov 14, 2026
9c6f407
Update translation files
weblate Nov 14, 2026
9ec839e
Update translation files
weblate Nov 14, 2026
ab81538
Update translation files
weblate Nov 14, 2026
150d934
Update translation files
weblate Nov 14, 2026
9cfdf4b
Update translation files
weblate Nov 14, 2026
832881f
Jobs keep the machine they were worked with: the vehicle's hitch poin…
chriskinal Nov 14, 2026
a89bf8e
Merge pull request #833 from weblate/weblate-agopengps-agopenweb
chriskinal Nov 14, 2026
dac3bfd
Merge pull request #834 from AgOpenGPS-Official/feat/job-rig
chriskinal Nov 14, 2026
bc98de1
Merge official develop dac3bfd7 (#818-#834) into sync-pulverizador
claude Nov 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ sections), the `Scene` (tracks, boundary, imagery extent), `Config`, `Status`, c
init + incremental cell deltas, and smaller frames for dialogs, prompts, sounds and toasts.
Position between ticks is extrapolated client-side (`RENDER_DELAY`); the pose estimator on the
host does the same for the 100 Hz control loop.
Long lines cost the most to draw: Skia strokes a bent, anti-aliased path through a CPU mask the
size of the path on the screen, so a curve that goes round the field costs a screen-sized mask.
Lines from the `Scene` are drawn with `strokeFixedPtsSk` (`js/map-draw.js`: a copy without the
points that add nothing, made once per array; the host replaces its arrays, the page never edits
them); a line the page draws or edits uses `strokePtsSk`. Lines in one style share one path
(`strokeCmdsSk`), as the extra guidelines do.

### Control authority
Several browsers may be connected; only one holds control (`ControlAuthority`, deadman model).
Expand Down Expand Up @@ -561,7 +567,7 @@ dotnet run --project TestRunner/TestRunner.csproj
- `AgOpenWeb.WorkingDayScenarios.Tests` - an eight-hour working day in one process (fields, refills, lunch, a tablet and a phone that drop, stall and reconnect, the next morning): memory baselines, heap census, cycle and tick times, the deadman with a second browser; about 45 minutes (`working-day-scenarios.yml`)
- `AgOpenWeb.DumpReplayScenarios.Tests` - the bug report dumps attached to public issues, replayed on this build: the reporter's settings, field, job and track, their GPS log fed back through the pipeline and compared with what their build did, then a machine of their shape taken over with AutoSteer and U-turns; the bug report service's dumps only in a local run (`AGOPENWEB_REPLAY_PRIVATE=1`), never in CI (`dump-replay-scenarios.yml`)

**Running the harnesses:** on the harness host, never on a development machine (`Tools/harness-runner`: a Docker image run on the `runner` context; `runner-compare.sh` runs a fix and its base side by side and lists the findings new, gone and in both, `Tools/compare-reports.py`). Every harness stops at the start when another process holds UDP 9999 or 8888 (`Tests/ScenarioShared/PortGuard.cs`, linked into each): an app left running shares the packets and the findings are noise. Before rerunning, read the saved report, pictures and traces; one confirming run per fix.
**Running the harnesses:** on the harness host, never on a development machine (`Tools/harness-runner`: a Docker image run on the `runner` context; `runner-compare.sh` runs a fix and its base side by side and lists the findings new, gone and in both for each report, `Tools/compare-reports.py`; it exits 1 when a side did not pass or a report is missing). Every harness stops at the start when another process holds UDP 9999 or 8888 (`Tests/ScenarioShared/PortGuard.cs`, linked into each): an app left running shares the packets and the findings are noise. Before rerunning, read the saved report, pictures and traces; one confirming run per fix.

CI also runs `Tools/i18n-extract.py --check` so `en.json` stays current, `Tools/license-headers.py --check` for the licence headers, and `Tools/i18n-limits.py --check` (job `translations-fit`, only when a change touches `wwwroot/`, the check, its baseline or the workflow; otherwise skipped, which counts as passed) so every translated label fits the English layout.

Expand All @@ -585,7 +591,7 @@ turns from the on-screen buttons) with paths built by `YouTurnCreationService`:
- Obstacles chosen as *Go around* (`AtObstacle == GoRound`; a new circle's default) bend the pass round them (`ObstacleDetour`: the obstacle grown by half the implement plus the Safety margin, the offset limited to 45° and rounded to the Curve radius, both settings on the U-turn tab and in the tool profile). One `GoAroundPasses` singleton serves the pipeline (steering, display, light bar) and the U-turn planner (the current pass walked to the turn, the next pass the exit lands on), so a turn is never planned from a line the tractor does not drive; it pins the side of each obstacle while the tractor is beside it and over on that side, so a nudge or snap never rebuilds the bend across the tree (the pin is let go when the tractor gets to the other side, as a U-turn next to the tree does). An obstacle with no room on the allowed side is promoted to a U-turn obstacle for that pass (toast "No room to go around Inner N on this pass: U-turn before it"); a turn keeps the tractor out of every Go around obstacle and the implement off a hard one; and with AutoSteer on, a Go around obstacle within 10 m plus 2 s straight ahead while the tractor is more than 1.5 m off the steered line takes AutoSteer off with the alarm. The margin is never under 0.3 m on a 6 m bend and more on a tighter one (`GoAroundPasses.MinMarginFor`), and the radius never under the tractor's minimum turning radius (`GoAroundPasses.Radius`). A bend never moves the line more than two implement widths (never under 20 m) off the pass (`ObstacleDetour.MaxOffset`): a tree, or a row of trees, needing more is a U-turn obstacle for that pass. Design: `Plans/OBSTACLE_GO_AROUND_PLAN.md`; scenarios: `Tests/AgOpenWeb.Services.Tests/YouTurn/ObstacleScenarioTests.cs` (`GoAroundLegs`).
- A pass that leaves the turn area and comes back within `UTurnCrossGap` metres (U-turn settings, "Drive across a headland gap up to"; 0 = off), across the headland only (inside the boundary, outside the headland line and every obstacle not driven through, and never through an obstacle's turn area, which is turned at as without the setting), is not turned at the gap: `GapCrossingEnd` in `YouTurnCreationService` walks on past it in both crossing searches, the turn is planned at the far end and the pass is driven through the gap with the sections off by headland section control (a narrow tip of the field, a notch in its edge). The next pass is taken from abreast of the gap's end (`gapResumeIndex`), since abreast of the tractor it can be outside the field.
- The turn is built from the headland crossing **ahead** of the tractor (`FindTrackHeadlandIntersectionAhead`; one behind only past the last crossing). When no real turn fits, the simple fallback (a semicircle) is seated back along the pass until it keeps `UTurnDistanceFromBoundary` from the fence: to the headland crossing and up to 30 m into the field past it, starting ahead of the tractor (`SimpleFallback`, fork only: renatorgr/AgOpenWeb#130, #135). Only on a straight pass: the fallback runs straight from the tractor, so on a curve it is seated nowhere (as official) and built again as the tractor comes closer; seated to fit 190 m away on a boundary curve, it was never joined and the missed turn was made "from here". It is then used only if every point of it stays in the field, outside obstacles a turn may not drive through (`LeavesField`, #698); otherwise no turn, the creation-failed sound and "No U-turn fits inside the boundary at this end - take over manually". A hard boundary refuses a turn that would start under or behind the tractor.
- A turn tried again with the turn lines moved in (the implement over a hard fence, a landing on a bend, an obstacle in its legs: `CreateTurnPath`'s setback loop, which moves the whole fence in) is seated back from the end of the pass the first try found: walking back from that end, at the moved line's crossing nearest to it (`YouTurnCreationInput.TurnLineEndIndex`, `TurnLineEndOnRetry`; not after a gap crossed). Moved in, the fence's turn line also crossed the pass where the fence bulges in beside it, and the first crossing ahead was taken: on Tapada da Casa an 8 m front boom near the north fence moved the line in 5 m and the turn was made 260 m early, beside the east bulge, landing on the next pass inside its crop and leaving a strip of it unsprayed. Test: `TurnSetbackStaysAtEndTests`. Fork only.
- A turn tried again with the turn lines moved in (the implement over a hard fence, a landing on a bend, an obstacle in its legs: `CreateTurnPath`'s setback loop, which moves the whole fence in) is seated back from the end of the pass the first try found: walking back from that end, at the moved line's crossing nearest to it (`YouTurnCreationInput.TurnLineEndIndex`, `TurnLineEndOnRetry`; not after a gap crossed). Moved in, the fence's turn line also crossed the pass where the fence bulges in beside it, and the first crossing ahead was taken: on Tapada da Casa an 8 m front boom near the north fence moved the line in 5 m and the turn was made 260 m early, beside the east bulge, landing on the next pass inside its crop and leaving a strip of it unsprayed. Test: `TurnSetbackStaysAtEndTests`. Official since #828; not after a gap crossed is fork only.
- The next pass must have room at this end: at least 20 m of it inside the cultivated area back from the end ahead (`IsPathLandable` / `LandingLength`), not just any piece of it inside the field; otherwise "End of field reached - no room on the next pass at this end" (#712). In this fork, when no pass has room but the current pass goes on inside the field past the headland ahead (a corner the headland clips off it, a gap), there is no turn and AutoSteer drives on across it: "No room to turn at this headland - driving on across it" (`GapAhead` / `DrivesOnAcrossGap`, renatorgr/AgOpenWeb#129; upstream left it out until after the v1.0 freeze). The pass must run on at least 10 m inside past the gap (`MinRunAfterGap`): a sliver or spike of the headland line is not more field.
- Skip worked passes (the snake order): when the order's next pass has no room at this end, or its turn does not build, the turn goes onto the nearest pass still to do that has room, first on the side the order is going, then on the other side (one turn built per side; a pass at least the skip away before a nearer one; none further than twice the skip's step, else "End of field reached" and the operator decides), never against the side the operator picked with the U-turn arrow (`TurnOntoNearestOpenPass`; AgOpenGPS's `GetNextNotWorkedTrack` only steps outward on the turn's side past worked tracks, so the other side, passes nearer than the skip and the room and build checks are ours); the order is planned again from where it lands. The same nearest pass is tried first when the order's next pass still to do is further than twice the skip's step (`MaxFallbackStep`; the passes in between worked before): the turn went from 17R to 7R along the headland, 80 m, with 16R and 14R open beside the tractor (a tester's field, simulator); with none of them taking a turn, the order's pass is turned onto as before. Fork only. An end where no turn was made is remembered (`_failedEnd`) and not tried again on every cycle until the pass, the direction of travel, the nudge, the order, the field or headland, the skip, the U-turn arrow or a turn setting changes. The order is also planned again when the headland line or the outer boundary changes (`SnakeFieldStamp`), and forgotten with what was found about each pass when the line moves (`ForgetPassStatus`: Reference Line Nudge, an end trimmed; the moved line's pass N is another strip, and the old answers turned the snake past clean passes on Tapada da Casa). In the headland the snake looks for its next turn only heading out of the field (no cultivated ground ahead on the pass): a turn that ends in the headland leaves the tractor facing the field on its new pass, which is not the end of that pass. Fork only: a pass worked on the whole (half covered, `IsPassWorked`) is still to do at the end ahead when less than half of its 20 m inside the cultivated area next to that end is covered (`YouTurnPathingService.IsPassOpenAtThisEnd`, asked in `WorkedPasses` when a turn is planned, once per end): on a tester's field a pass 71 % covered, with its last 100 m at that end left, was turned past.
- The end of a pass at a hard outer boundary with no turn planned or driven there ("End of field reached", no turn that fits, U-turns off): as the pivot gets 1 m into the headland from the cultivated area (`HeadlandEntryCheckAfter`), on the line and heading out (the pass leaves the outer boundary within 300 m with no 10 m of field on it first, `YouTurnPathingService.PassLeavesFieldAhead`), AutoSteer goes off with the alarm, "AutoSteer off: end of the pass and no U-turn here. Turn by hand." (`YouTurnStateMachine.EndOfPassWithoutTurn`, step 5b'''' in `GpsPipelineService`; once per entry; fork only). Left on, AutoSteer steered on along the line through the fence (as AgOpenGPS, which only sends geoStop): on Tapada da Casa the simulator drove on 2.8 km. Heading into the field, a headland strip with field past it, or a soft boundary do not count. This stop, the line facing out of the field below and a pass still open at this end measure from the headland line, where the crop ends, not from the turn line the U-turn uses (`TickContext.PassEndLine`, the line the pipeline arms them on: from the turn line a headland more than 10 m wider than the U-turn distance never stopped AutoSteer).
Expand All @@ -601,8 +607,8 @@ Key parameters:
- The headland's width plays no part in the turn: with a headland narrower than the turn is deep, the turn leaves its pass inside the headland line, still working ("Headland too narrow for this U-turn").

## A front implement round a bend
AutoSteer steers the pivot on the line; an implement fixed `h` ahead of it (`Tool.IsToolFrontFixed`,
`Tool.HitchLength`) runs outside a bend of radius R by √(R² + h²) − R. `FrontToolLine.Steered` moves each point of
AutoSteer steers the pivot on the line; an implement fixed `h` ahead of it (`ToolMount.FrontFixed`,
`RigGeometry.AttachX`: the vehicle's front hitch plus the tool's working point from it) runs outside a bend of radius R by √(R² + h²) − R. `FrontToolLine.Steered` moves each point of
the line steered (after the Go around bend, the same points one for one, so `PassUnfit` still lines up) towards the
bend's centre by R − √(R² − h²), the pivot's bend never tighter than `Vehicle.MinTurningRadius`; R is measured over
`max(h, 3 m)` either side of the point half way to the implement, headings over 2 m chords (a loop's ends overlap a
Expand Down
Loading
Loading