323f29b649
- One crop_fill (default 0.9) replaces duplicated FILL in inventory/overlay - Thin localStorage settings store; crop % only moves long-axis end insets - Pure tapeGeometry: fixed stage line, stage↔image map, tick span → px - measure() prefers tape tick span for scale; maskSpanAlong demoted - rotateCalibration bakes cal tilt into inventory + overlay only - Fix stale 80%-fill comment on slabBounds
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import {
|
||
STAGE_H, STAGE_W, clampTickPair, containFit, defaultTape, defaultTicks,
|
||
lineX, stageToImage, tapeEndpoints,
|
||
} from './tapeGeometry.ts';
|
||
import { cropMarginFraction, DEFAULT_CROP_FILL } from '../png/crop.ts';
|
||
|
||
test('defaultTicks sit at crop insets for 90% fill', () => {
|
||
const t = defaultTicks(0.9);
|
||
const inset = STAGE_H * cropMarginFraction(0.9);
|
||
assert.ok(Math.abs(t.tickA - inset) < 1e-9);
|
||
assert.ok(Math.abs(t.tickB - (STAGE_H - inset)) < 1e-9);
|
||
});
|
||
|
||
test('lineX is the middle of the 3:4 stage', () => {
|
||
assert.equal(lineX(), STAGE_W / 2);
|
||
});
|
||
|
||
test('identity tape: stage centre maps near image centre', () => {
|
||
const imgW = 480, imgH = 640;
|
||
const tape = defaultTape(DEFAULT_CROP_FILL, 0);
|
||
const [ix, iy] = stageToImage(STAGE_W / 2, STAGE_H / 2, tape, imgW, imgH);
|
||
assert.ok(Math.abs(ix - imgW / 2) < 1e-6, `ix=${ix}`);
|
||
assert.ok(Math.abs(iy - imgH / 2) < 1e-6, `iy=${iy}`);
|
||
});
|
||
|
||
test('tapeEndpoints span equals tick distance in image px at identity (matching aspect)', () => {
|
||
const imgW = 480, imgH = 640;
|
||
const tape = defaultTape(0.9, 0);
|
||
const ep = tapeEndpoints(tape, imgW, imgH);
|
||
const { scale0 } = containFit(imgW, imgH);
|
||
const expected = Math.abs(tape.tickB - tape.tickA) / scale0;
|
||
assert.ok(Math.abs(ep.spanPx - expected) < 1e-6, `span ${ep.spanPx} vs ${expected}`);
|
||
// Direction should be +y (down the stage → down the image)
|
||
assert.ok(Math.abs(ep.ux) < 1e-6, `ux=${ep.ux}`);
|
||
assert.ok(ep.uy > 0.99, `uy=${ep.uy}`);
|
||
});
|
||
|
||
test('zoom scales inverse: 2× zoom halves image-space tick span', () => {
|
||
const imgW = 480, imgH = 640;
|
||
const base = defaultTape(0.9, 0);
|
||
const zoomed = { ...base, zoom: 2 };
|
||
const a = tapeEndpoints(base, imgW, imgH).spanPx;
|
||
const b = tapeEndpoints(zoomed, imgW, imgH).spanPx;
|
||
assert.ok(Math.abs(b * 2 - a) < 1e-6, `base=${a} zoomed=${b}`);
|
||
});
|
||
|
||
test('clampTickPair enforces gap and order', () => {
|
||
const p = clampTickPair(400, 100, 40);
|
||
assert.ok(p.tickB - p.tickA >= 40);
|
||
assert.ok(p.tickA <= p.tickB);
|
||
});
|