⚠ **뿌리** — `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>
64 lines
3.3 KiB
JavaScript
64 lines
3.3 KiB
JavaScript
// 교차점 탐색·외삽(B06_Section_UI_Cross_Fit) 자체검증 — 로직 복제.
|
|
// 사면이 5m를 넘어도 실제 지반 교차점을 짚어야 한다(2026-08-23 사용자 보고).
|
|
// 실행: node tmp/tests/test_toe_extrapolate.mjs
|
|
import assert from "node:assert/strict";
|
|
|
|
const TREND_SPAN_M = 2;
|
|
const MAX = 60;
|
|
function extrapolateMeet(designAt, groundAt, limit, startOffset) {
|
|
const outward = Math.sign(limit - startOffset) || 1;
|
|
const back = limit - outward * TREND_SPAN_M;
|
|
const diffAtLimit = designAt(limit) - groundAt(limit);
|
|
const diffAtBack = designAt(back) - groundAt(back);
|
|
const closingRate = (Math.abs(diffAtBack) - Math.abs(diffAtLimit)) / TREND_SPAN_M;
|
|
if (!(closingRate > 1e-6)) return limit;
|
|
const extra = Math.abs(diffAtLimit) / closingRate;
|
|
if (!Number.isFinite(extra)) return limit;
|
|
return limit + outward * Math.min(extra, MAX);
|
|
}
|
|
|
|
// 성토 사면 1:1.5(바깥으로 갈수록 0.667m/m 하강), 지반 평탄 → 실제 교차점 = 노견+15m.
|
|
const edge = 3;
|
|
const designAt = (o) => 10 - ((o - edge) * 2) / 3;
|
|
const groundAt = () => 0;
|
|
// 샘플 끝 15m에서 아직 안 만남(차이 2m) → 외삽이 실제 교차점 18m를 짚어야 한다.
|
|
const meet = extrapolateMeet(designAt, groundAt, 15, edge);
|
|
assert.ok(Math.abs(meet - 18) < 1e-6, `외삽 어긋남: ${meet}`);
|
|
// 반대쪽(음의 offset)도 부호가 맞아야 한다.
|
|
const designLeft = (o) => 10 - ((-edge - o) * 2) / 3;
|
|
const meetLeft = extrapolateMeet(designLeft, groundAt, -15, -edge);
|
|
assert.ok(Math.abs(meetLeft + 18) < 1e-6, `좌측 외삽 어긋남: ${meetLeft}`);
|
|
// 벌어지기만 하는 지형(지반이 사면보다 더 빨리 내려감)은 넓히지 않는다.
|
|
const divergingGround = (o) => -Math.abs(o) * 2;
|
|
assert.equal(extrapolateMeet(designAt, divergingGround, 15, edge), 15);
|
|
// 표시 반폭 = 올림(교차점) + 1m — 18m 교차점이면 19m.
|
|
assert.equal(Math.min(Math.ceil(Math.abs(meet) + 1), MAX), 19);
|
|
console.log("ok");
|
|
|
|
// ── 실제 교차점 탐색(meetOffset) — 성토 사면 5m 규칙에 잘리면 안 된다.
|
|
const SCAN_STEP_M = 0.1;
|
|
const MEET_TOLERANCE_M = 0.02;
|
|
function meetOffset(designAt, groundAt, startOffset, limitOffset, outward) {
|
|
const span = Math.abs(limitOffset - startOffset);
|
|
if (!(span > 0)) return startOffset;
|
|
const steps = Math.ceil(span / SCAN_STEP_M);
|
|
let previousDiff = designAt(startOffset) - groundAt(startOffset);
|
|
for (let i = 1; i <= steps; i += 1) {
|
|
const o = startOffset + outward * Math.min(SCAN_STEP_M * i, span);
|
|
const diff = designAt(o) - groundAt(o);
|
|
if (Math.abs(diff) <= MEET_TOLERANCE_M) return o;
|
|
if (previousDiff !== 0 && Math.sign(diff) !== Math.sign(previousDiff)) return o;
|
|
previousDiff = diff;
|
|
}
|
|
return extrapolateMeet(designAt, groundAt, limitOffset, outward);
|
|
}
|
|
|
|
// 사면 길이 약 18m(1:1.5, 높이 10m) — 5m 컷오프면 노견+약 4.2m에서 멈춘다.
|
|
const meetFar = meetOffset(designAt, groundAt, edge, 25, 1);
|
|
assert.ok(Math.abs(meetFar - 18) < 0.15, `교차점 탐색 어긋남: ${meetFar}`);
|
|
assert.ok(meetFar > edge + 5, "5m 사면 규칙에 잘렸다");
|
|
// 설계선이 교차 전에 끝나면(15m까지만) 외삽으로 같은 자리를 짚는다.
|
|
const meetClipped = meetOffset(designAt, groundAt, edge, 15, 1);
|
|
assert.ok(Math.abs(meetClipped - 18) < 1e-6, `외삽 어긋남: ${meetClipped}`);
|
|
console.log("ok(fit)");
|