Units correctness: exact imperial entry, garden grid at dimension scale
Three compounding defects in the measurement layer (#47), measured against the live Home garden rather than estimated. Entry was quantized to whole centimeters even though every measurement column is REAL and the server only range-checks. A 12in grid stored 30cm — an 11.811in grid, short by 0.189in per cell, and because the error is systematic it accumulates: 4.61in of drift across a 24ft garden. cmFromFtIn / cmFromMeters / cmFromSpacing now convert exactly, rounding only at 1e-6 cm to clear IEEE-754 dust (12 x 2.54 is 30.479999999999997 in binary). Display-side rounding is untouched, so cm quantization still can't show through in a field. The garden grid was entered at spacing scale: "Grid size (in)" sat directly under "Width (ft)", two adjacent numeric fields at two different scales distinguished only by a suffix. Home ended up on a 3cm grid, almost certainly "1" typed meaning one foot. The garden grid is a layout concern, so it moves to dimension scale and reads "Garden grid (ft)"; the bed grid in the object inspector is a plant-spacing concern and deliberately stays at cm/in. A sub-10cm garden grid now draws a soft warning rather than being silently accepted. Snap could stay active while the grid stopped drawing: the canvas faded the grid out below 6px per cell, so Home ran with snapToGrid on, no grid drawn, and no snapping you could feel. Replaced the fade with coarsening — visibleGridStepCm picks the smallest multiple of the true grid (2x, 5x, 10x...) whose cells are legible, so every line drawn is still a real grid line and something aligned to the grid is always visible. When the drawn step isn't the true grid the canvas says so; a degenerate grid it can't draw at all says that instead of nothing. Closes #47 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
snapLocalToBedGrid,
|
||||
snapPoint,
|
||||
snapValue,
|
||||
visibleGridStepCm,
|
||||
worldToLocal,
|
||||
worldToScreen,
|
||||
zoomToFitRect,
|
||||
@@ -128,3 +129,34 @@ describe('grid snapping', () => {
|
||||
expect(p.x).toBeGreaterThanOrEqual(-60)
|
||||
})
|
||||
})
|
||||
|
||||
describe('visibleGridStepCm', () => {
|
||||
it('draws the true grid when its cells are already legible', () => {
|
||||
expect(visibleGridStepCm(30.48, 1, 6)).toBe(30.48)
|
||||
expect(visibleGridStepCm(10, 0.6, 6)).toBe(10) // exactly at the minimum
|
||||
})
|
||||
|
||||
it('coarsens to a multiple rather than fading out', () => {
|
||||
// The #47 failure: a 3cm grid at 0.84 px/cm is 2.5px per cell. Instead of
|
||||
// drawing nothing while snapping stays on, draw every 5th line (12.6px).
|
||||
expect(visibleGridStepCm(3, 0.84, 6)).toBe(15)
|
||||
expect(visibleGridStepCm(1, 0.84, 6)).toBe(10)
|
||||
})
|
||||
|
||||
it('only ever returns a whole multiple of the true grid', () => {
|
||||
for (const scale of [0.05, 0.3, 0.84, 1, 4]) {
|
||||
const step = visibleGridStepCm(7, scale, 6)
|
||||
expect(step).not.toBeNull()
|
||||
expect(Math.round(step! / 7) * 7).toBeCloseTo(step!, 9)
|
||||
expect(step! * scale).toBeGreaterThanOrEqual(6)
|
||||
}
|
||||
})
|
||||
|
||||
it('reports null for a degenerate grid rather than drawing nothing silently', () => {
|
||||
expect(visibleGridStepCm(0, 1, 6)).toBeNull()
|
||||
expect(visibleGridStepCm(-5, 1, 6)).toBeNull()
|
||||
expect(visibleGridStepCm(NaN, 1, 6)).toBeNull()
|
||||
expect(visibleGridStepCm(30, 0, 6)).toBeNull()
|
||||
expect(visibleGridStepCm(1e-9, 1, 6)).toBeNull() // beyond the coarsening cap
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user