The clump model can't draw a real planting: a filled bed is 3 blobs per row, not 8 rows of garlic #77

Closed
opened 2026-07-21 21:59:03 +00:00 by steve · 1 comment
Owner

Fell out of #75. That issue fixed where the lattice sits; this one is about what the lattice is made of.

The problem

A plop is a clump, not a plant: defaultPlopRadius is 1.5 × spacing, so a plop is three spacings across, and its plant count is derived from area (π·r² / spacing²). Fill packs those clumps at a 2 × radius pitch.

For a 122 × 244 cm (4 × 8 ft) bed of garlic at 15 cm spacing, that gives 15 clumps of 7 — verified live after #75:

row 0: 3 plops   row 1: 2 plops   row 2: 3 plops   ...

A real 4 × 8 ft bed of garlic at 15 cm on-centre is 8 rows of ~16, about 150 cloves, laid out on a triangular lattice. pansy draws 15 circles.

The counts are in the right ballpark (105 vs ~150 — the shortfall is circle packing leaving the interstices empty, ~91% density at best, worse once the clumps are inset). But the layout isn't a planting. You could not take this drawing out to the garden and plant from it.

Why it matters

The clump model earns its keep for freeform placement — "a few plops of garlic in one corner, herbs in another" is exactly what DESIGN.md describes, and dragging one blob is much better than dragging 7 plants. It's the right primitive for sketching.

It's the wrong primitive for filling a bed, which is the operation where you actually want to know where things go. And filling a bed is the flagship agent interaction ("change the garlic bed to cucumbers this year").

Options

Not proposing a specific one — this is a product decision, and it changes what the app looks like.

  1. Leave it. The blob is a deliberate abstraction; the count is honest even if the drawing isn't literal. Cheapest, and defensible if pansy is a planner rather than a planting guide.
  2. Fill at true plant spacing. Pitch = spacing, radius = spacing/2, one plant per plop. The 4 × 8 ft bed becomes ~135–150 plops instead of 15. Horticulturally correct and directly plantable, but ~9× the rows per fill, ~9× the SVG nodes, and it changes the visual character of the app substantially. Would want to check the rendering budget first — DESIGN.md's SVG choice was justified against "low-hundreds of plops", and this pushes a single bed to that number.
  3. Make it a property of the fill. Keep clumps as the default primitive; add a "rows" or "grid" fill mode that lays out individual plants, so sketching and planning are different operations with different outputs. More surface area, but it stops one model having to be both things.
  4. Keep clumps, render their contents. One row per clump in the DB, but draw the individual plants inside the circle at the real spacing when zoomed in. Cheap in storage, honest on screen, but the plants aren't addressable — you can't move or remove one.

Option 3 or 4 look most promising to me; 4 is notably cheaper and composes with the existing semantic-zoom tiers (SEMANTIC_FAR / SEMANTIC_NEAR in PlopMarker.tsx already switch representation by zoom).

Notes

  • Whatever is chosen, derivedCount and the seed-lot remaining calculation depend on the count staying meaningful — option 2 makes every plop count 1, which changes how lot usage reads.
  • #75's half-spacing edge rule stays correct under all four options; it's about the lattice, not the primitive.
Fell out of #75. That issue fixed *where* the lattice sits; this one is about what the lattice is made of. ## The problem A plop is a **clump**, not a plant: `defaultPlopRadius` is `1.5 × spacing`, so a plop is three spacings across, and its plant count is derived from area (`π·r² / spacing²`). Fill packs those clumps at a `2 × radius` pitch. For a 122 × 244 cm (4 × 8 ft) bed of garlic at 15 cm spacing, that gives **15 clumps of 7** — verified live after #75: ``` row 0: 3 plops row 1: 2 plops row 2: 3 plops ... ``` A real 4 × 8 ft bed of garlic at 15 cm on-centre is **8 rows of ~16**, about 150 cloves, laid out on a triangular lattice. pansy draws 15 circles. The counts are in the right ballpark (105 vs ~150 — the shortfall is circle packing leaving the interstices empty, ~91% density at best, worse once the clumps are inset). But the *layout* isn't a planting. You could not take this drawing out to the garden and plant from it. ## Why it matters The clump model earns its keep for freeform placement — "a few plops of garlic in one corner, herbs in another" is exactly what DESIGN.md describes, and dragging one blob is much better than dragging 7 plants. It's the right primitive for sketching. It's the wrong primitive for *filling a bed*, which is the operation where you actually want to know where things go. And filling a bed is the flagship agent interaction ("change the garlic bed to cucumbers this year"). ## Options Not proposing a specific one — this is a product decision, and it changes what the app looks like. 1. **Leave it.** The blob is a deliberate abstraction; the count is honest even if the drawing isn't literal. Cheapest, and defensible if pansy is a planner rather than a planting guide. 2. **Fill at true plant spacing.** Pitch = spacing, radius = spacing/2, one plant per plop. The 4 × 8 ft bed becomes ~135–150 plops instead of 15. Horticulturally correct and directly plantable, but ~9× the rows per fill, ~9× the SVG nodes, and it changes the visual character of the app substantially. Would want to check the rendering budget first — DESIGN.md's SVG choice was justified against "low-hundreds of plops", and this pushes a single bed to that number. 3. **Make it a property of the fill.** Keep clumps as the default primitive; add a "rows" or "grid" fill mode that lays out individual plants, so sketching and planning are different operations with different outputs. More surface area, but it stops one model having to be both things. 4. **Keep clumps, render their contents.** One row per clump in the DB, but draw the individual plants inside the circle at the real spacing when zoomed in. Cheap in storage, honest on screen, but the plants aren't addressable — you can't move or remove one. Option 3 or 4 look most promising to me; 4 is notably cheaper and composes with the existing semantic-zoom tiers (`SEMANTIC_FAR` / `SEMANTIC_NEAR` in `PlopMarker.tsx` already switch representation by zoom). ## Notes - Whatever is chosen, `derivedCount` and the seed-lot `remaining` calculation depend on the count staying meaningful — option 2 makes every plop count 1, which changes how lot usage reads. - #75's half-spacing edge rule stays correct under all four options; it's about the lattice, not the primitive.
Author
Owner

Decision (Steve, 2026-07-22): option 3 — fill mode.

Keep clumps as the default primitive; add a grid/rows fill mode that lays out individual plants at true spacing. Sketching (a few blobs in a corner) and planning (a plantable layout) become different operations with different outputs, rather than one model forced to be both.

Plan

  • hexCenters already does the geometry — grid mode is just a different radius→spacing relationship. Clump mode: plop radius 1.5×spacing, pitch 2×radius (≈3 spacings), each plop a clump of ~7. Grid mode: plop radius spacing/2, pitch = spacing, each plop one plant (derivedCount at r=spacing/2 is π/4 ≈ 0.79 → 1). The #75 half-spacing edge rule holds for both — it's about the lattice, not the primitive.
  • Service: FillRegion/FillNamedRegion take a layout ("clump" | "grid"), threading a plop-radius choice into the existing packer. Default "clump" so nothing changes for current callers.
  • REST (POST /objects/:id/fill, from #82): a layout field. Agent tool fill_region: a mode arg, so "plant the bed in rows" works. Frontend: a mode toggle on the fill affordance.
  • Note: a grid-filled 4×8 ft bed is ~130–150 one-plant plops vs. 15 clumps. DESIGN.md's SVG budget was justified against "low-hundreds of plops" — one bed now approaches that, so worth a glance at render perf on a full garden. PlopMarker is already memo'd and semantic-zoom-tiered, which helps.

Building this as its own PR now that the epic's other work is landing.

**Decision (Steve, 2026-07-22): option 3 — fill mode.** Keep clumps as the default primitive; add a **grid/rows fill mode** that lays out individual plants at true spacing. Sketching (a few blobs in a corner) and planning (a plantable layout) become different operations with different outputs, rather than one model forced to be both. ## Plan - **`hexCenters` already does the geometry** — grid mode is just a different radius→spacing relationship. Clump mode: plop radius `1.5×spacing`, pitch `2×radius` (≈3 spacings), each plop a clump of ~7. Grid mode: plop radius `spacing/2`, pitch `= spacing`, each plop **one** plant (`derivedCount` at `r=spacing/2` is `π/4 ≈ 0.79 → 1`). The #75 half-spacing edge rule holds for both — it's about the lattice, not the primitive. - **Service:** `FillRegion`/`FillNamedRegion` take a `layout` ("clump" | "grid"), threading a plop-radius choice into the existing packer. Default "clump" so nothing changes for current callers. - **REST** (`POST /objects/:id/fill`, from #82): a `layout` field. **Agent tool** `fill_region`: a `mode` arg, so "plant the bed in rows" works. **Frontend:** a mode toggle on the fill affordance. - Note: a grid-filled 4×8 ft bed is ~130–150 one-plant plops vs. 15 clumps. DESIGN.md's SVG budget was justified against "low-hundreds of plops" — one bed now approaches that, so worth a glance at render perf on a full garden. `PlopMarker` is already memo'd and semantic-zoom-tiered, which helps. Building this as its own PR now that the epic's other work is landing.
steve closed this issue 2026-07-22 04:22:46 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/pansy#77