⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt` 가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는 시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음. - `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침** - `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시) - 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q` 옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0 ⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
79 lines
3.7 KiB
JavaScript
79 lines
3.7 KiB
JavaScript
// 횡단 Y축 눈금이 플롯 안(상단~X축선)에만 놓이는지 확인 — crossPlotMetrics + Cross_View 로직 복제.
|
|
// 실행: node tmp/tests/test_cross_y_ticks.mjs
|
|
import assert from "node:assert/strict";
|
|
|
|
const CROSS_PAD = { left: 58, right: 20, top: 10, bottom: 52 };
|
|
const CROSS_HEIGHT = 250;
|
|
const AREA_OVERLAY_HEADROOM_PX = 58;
|
|
|
|
function niceTickStep(span, targetCount, maxCount) {
|
|
if (!(span > 0)) return 1;
|
|
const exponent = Math.floor(Math.log10(span / targetCount));
|
|
let best = Math.pow(10, exponent + 2);
|
|
let bestError = Infinity;
|
|
for (const power of [exponent - 1, exponent, exponent + 1, exponent + 2]) {
|
|
for (const mantissa of [1, 2, 5]) {
|
|
const step = mantissa * Math.pow(10, power);
|
|
const count = Math.floor(span / step) + 1;
|
|
if (count > Math.max(2, maxCount)) continue;
|
|
const error = Math.abs(count - targetCount);
|
|
if (error < bestError) {
|
|
bestError = error;
|
|
best = step;
|
|
}
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
function ticksOf({ rawMin, rawMax, halfWidth, widthPx = 630, exaggeration = 1 }) {
|
|
const minOffset = -halfWidth;
|
|
const maxOffset = halfWidth;
|
|
const elevationMid = (rawMin + rawMax) / 2;
|
|
const padding = rawMax > rawMin ? (rawMax - rawMin) * 0.08 : 0.5;
|
|
const pixelsPerMeter = (widthPx - CROSS_PAD.left - CROSS_PAD.right) / (maxOffset - minOffset);
|
|
const rawSpan = Math.max((rawMax - rawMin + padding * 2) * Math.max(exaggeration, 0.1), 1e-6);
|
|
const naturalHeight =
|
|
rawSpan * pixelsPerMeter + CROSS_PAD.top + CROSS_PAD.bottom + AREA_OVERLAY_HEADROOM_PX;
|
|
const heightPx = Math.max(naturalHeight, CROSS_HEIGHT);
|
|
const displaySpan = Math.max(heightPx - CROSS_PAD.top - CROSS_PAD.bottom, 1e-6) / pixelsPerMeter;
|
|
const displayMax = elevationMid + displaySpan / 2 + AREA_OVERLAY_HEADROOM_PX / pixelsPerMeter / 2;
|
|
const y = (elevation) => CROSS_PAD.top + (displayMax - elevation) * pixelsPerMeter;
|
|
|
|
const rawDisplaySpan = displaySpan / exaggeration;
|
|
const rawTop = elevationMid + (displayMax - elevationMid) / exaggeration;
|
|
const rawBottom = rawTop - rawDisplaySpan;
|
|
const plotHeightPx = heightPx - CROSS_PAD.top - CROSS_PAD.bottom;
|
|
// 표고 눈금 최소 1m(2026-08-23) — 화면 로직과 같게 맞춘다.
|
|
const step = Math.max(niceTickStep(rawDisplaySpan, 10, Math.floor(plotHeightPx / 14)), 1);
|
|
const out = [];
|
|
for (let tick = Math.ceil(rawBottom / step) * step; tick <= rawTop + 1e-9; tick += step) {
|
|
out.push({ tick, y: y(elevationMid + (tick - elevationMid) * exaggeration) });
|
|
}
|
|
return { out, step, top: CROSS_PAD.top, bottom: heightPx - CROSS_PAD.bottom, heightPx };
|
|
}
|
|
|
|
const CASES = [
|
|
{ rawMin: 536.0, rawMax: 541.0, halfWidth: 12 }, // 스크린샷과 비슷한 성토 단면
|
|
{ rawMin: 530.0, rawMax: 530.4, halfWidth: 6 }, // 거의 평탄(1m 눈금 두세 줄만 나온다)
|
|
{ rawMin: 500.0, rawMax: 528.0, halfWidth: 20 }, // 급경사 절토
|
|
{ rawMin: 536.0, rawMax: 541.0, halfWidth: 12, exaggeration: 2 }, // 과장 2배
|
|
];
|
|
for (const c of CASES) {
|
|
const { out, step, top, bottom } = ticksOf(c);
|
|
assert.ok(out.length >= 2, `눈금 부족: ${JSON.stringify(c)} → ${out.length}`);
|
|
assert.ok(step >= 1 - 1e-9, `1m 미만 눈금: ${step}`);
|
|
for (const t of out) {
|
|
assert.ok(Math.abs(t.tick - Math.round(t.tick)) < 1e-6, `정수 아닌 표고 눈금 ${t.tick}`);
|
|
}
|
|
for (const t of out) {
|
|
assert.ok(
|
|
t.y >= top - 1e-6 && t.y <= bottom + 1e-6,
|
|
`플롯 밖 눈금 ${t.tick.toFixed(2)} y=${t.y.toFixed(1)} (허용 ${top}~${bottom.toFixed(1)})`,
|
|
);
|
|
}
|
|
const spacing = out.length > 1 ? Math.abs(out[1].y - out[0].y) : Infinity;
|
|
assert.ok(spacing >= 14 - 1e-6, `눈금 간격 과밀 ${spacing.toFixed(1)}px (step=${step})`);
|
|
}
|
|
console.log("ok");
|