Fill packs plops against the bed edge wrong: full-pitch indent on staggered rows, overhang on the far edge #75

Closed
opened 2026-07-21 14:10:24 +00:00 by steve · 0 comments
Owner

Reported from real use: filling a bed with garlic leaves the first plop too far from the corner, and the corner plop "doesn't have to be a full diameter from the edge when you plant".

That is correct, and there are two separate defects behind it.

What's wrong

hexCenters anchors the lattice one radius inside the region's min corner and then keeps any candidate whose centre is in the region. On a 122 × 244 cm (4 × 8 ft) bed of garlic (15 cm spacing → plop radius 22.5, pitch 45):

BEFORE — 15 plops
   row 0: 3 plops | W gap   +0.0 cm | E gap  -13.0 cm
   row 1: 2 plops | W gap  +22.5 cm | E gap   +9.5 cm
   row 2: 3 plops | W gap   +0.0 cm | E gap  -13.0 cm
   ** worst overhang outside the bed: 13.0 cm
  1. Staggered rows start a full pitch in. Odd rows are offset by radius, so their first centre lands at MinX + 2·radius — a bare strip a whole plop wide down the west side of every other row.
  2. All the slack piles up on the far edge, where plops then hang 13 cm outside the bed. Nothing clips them, so they draw over the bed outline.

The rule

Spacing is a constraint between neighbouring plants competing for the same soil, light and water. A bed edge is not a competitor, so the outer row only owes it half the spacing — the half it would otherwise share with a neighbour.

This is the arithmetic inside every square-foot-gardening chart: 4 per square is 6" apart and 3" from the square's edge; 9 per square is 4" apart and 2" from the edge. Garlic at 9 per square goes in 2" from the frame, not 6".

The wrinkle specific to pansy: a plop is a clump, not a plant. defaultPlopRadius is 1.5 × spacing, so a plop is three spacings across and its plants sit out to its rim. Keeping the whole circle inside the bed would inset the outer row by a full 1.5 spacings — three times what the rule allows. So the clump is allowed to cross the edge by up to half a spacing, which puts its outermost plants exactly the half-spacing from the edge the rule asks for. Capped there, and nowhere near the full radius: a clump mostly outside the bed is a drawing of plants in the path.

The fix

Centre the lattice in the region, with the minimum centre-inset at radius − spacing/2 rather than a full radius. Same bed:

AFTER — 15 plops
   row 0: 3 plops | W gap   -6.5 cm | E gap   -6.5 cm
   row 1: 2 plops | W gap  +16.0 cm | E gap  +16.0 cm
   row 2: 3 plops | W gap   -6.5 cm | E gap   -6.5 cm
   ** worst overhang outside the bed: 6.5 cm

Same density, symmetric, and the overhang is now deliberate and inside the 7.5 cm half-spacing budget instead of an accidental 13 cm on one side only.

The stagger falls out of the centring for free: an offset row holds one fewer plop, and centring that run puts it exactly half a pitch off its neighbours.

Notes

  • TestFillRegionDeterministicPacking expected 4 plops in a 60 × 60 bed; the fourth was centred on the east edge with half of it outside, well past the half-spacing budget. It is 3 now — that is the fix working, not a regression in it.
  • Region.contains becomes dead: the lattice is bounded by construction, so the loop is count-bounded rather than condition-bounded.
Reported from real use: filling a bed with garlic leaves the first plop too far from the corner, and the corner plop "doesn't have to be a full diameter from the edge when you plant". That is correct, and there are two separate defects behind it. ## What's wrong `hexCenters` anchors the lattice one radius inside the region's **min corner** and then keeps any candidate whose *centre* is in the region. On a 122 × 244 cm (4 × 8 ft) bed of garlic (15 cm spacing → plop radius 22.5, pitch 45): ``` BEFORE — 15 plops row 0: 3 plops | W gap +0.0 cm | E gap -13.0 cm row 1: 2 plops | W gap +22.5 cm | E gap +9.5 cm row 2: 3 plops | W gap +0.0 cm | E gap -13.0 cm ** worst overhang outside the bed: 13.0 cm ``` 1. **Staggered rows start a full pitch in.** Odd rows are offset by `radius`, so their first centre lands at `MinX + 2·radius` — a bare strip a whole plop wide down the west side of every other row. 2. **All the slack piles up on the far edge**, where plops then hang 13 cm outside the bed. Nothing clips them, so they draw over the bed outline. ## The rule Spacing is a constraint *between neighbouring plants* competing for the same soil, light and water. A bed edge is not a competitor, so the outer row only owes it **half the spacing** — the half it would otherwise share with a neighbour. This is the arithmetic inside every square-foot-gardening chart: 4 per square is 6" apart and **3" from the square's edge**; 9 per square is 4" apart and 2" from the edge. [Garlic at 9 per square](https://squarefootgardening.org/2020/11/square-foot-garlic/) goes in 2" from the frame, not 6". The wrinkle specific to pansy: **a plop is a clump, not a plant.** `defaultPlopRadius` is `1.5 × spacing`, so a plop is three spacings across and its plants sit out to its rim. Keeping the whole circle inside the bed would inset the outer row by a full 1.5 spacings — three times what the rule allows. So the clump is allowed to cross the edge by **up to half a spacing**, which puts its outermost plants exactly the half-spacing from the edge the rule asks for. Capped there, and nowhere near the full radius: a clump mostly outside the bed is a drawing of plants in the path. ## The fix Centre the lattice in the region, with the minimum centre-inset at `radius − spacing/2` rather than a full `radius`. Same bed: ``` AFTER — 15 plops row 0: 3 plops | W gap -6.5 cm | E gap -6.5 cm row 1: 2 plops | W gap +16.0 cm | E gap +16.0 cm row 2: 3 plops | W gap -6.5 cm | E gap -6.5 cm ** worst overhang outside the bed: 6.5 cm ``` Same density, symmetric, and the overhang is now deliberate and inside the 7.5 cm half-spacing budget instead of an accidental 13 cm on one side only. The stagger falls out of the centring for free: an offset row holds one fewer plop, and centring *that* run puts it exactly half a pitch off its neighbours. ## Notes - `TestFillRegionDeterministicPacking` expected 4 plops in a 60 × 60 bed; the fourth was centred **on** the east edge with half of it outside, well past the half-spacing budget. It is 3 now — that is the fix working, not a regression in it. - `Region.contains` becomes dead: the lattice is bounded by construction, so the loop is count-bounded rather than condition-bounded.
steve closed this issue 2026-07-21 18:28:18 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#75