Files
Aislo/resources/tester/helper_408_radius_downstream.cjs
T
eomsangdonandClaude Opus 5.5 0176ae4e10 test(430-2 장 시험): 408 도우미 알림 가짜 · 383-2 방향키 범위 단언 새 글 (430-2)
- helper_408_radius_downstream.cjs = @ui/ui_template_elements stub(showToast · 430 곡선 삭제 경고 import)
- test_383_2 = 방향키 범위 .b05-route__main .b05-routeedit(425 · 겹침 평면 밖)
- 앱 코드 그대로 · 408 · 383_2 · 430 · 398_2 · 258 48 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
2026-10-18 16:38:25 +09:00

163 lines
5.4 KiB
JavaScript

/* 408-1 R 바꿈 뒤 뒤쪽 측점 맞춤(요구 252-3) — `_CurveBar` 배선을 Node 로 돌려 JSON 한 줄
* (`test_408_radius_downstream.py` 가 판정). 창(`_Label`)은 손잡이만 잡는 가짜 · 편집 창 `applyEdit` 는
* 같은 길(스냅 켬 = `fitStart` → `fitCurveStations` → 길이 고정 R · 하한 → `buildEditedPolyline`)로 흉내.
* node helper_408_radius_downstream.cjs
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const B05 = path.join(ROOT, "B05_Profile");
const cache = new Map();
let handlers = null;
const STUBS = {
"./B05_Profile_UI_RouteEdit_Label": {
createCurveLabel: (given) => {
handlers = given;
return { show: () => {}, hide: () => {}, destroy: () => {} };
},
centerDirectionOf: () => null,
},
"@ui/ui_template_elements": { showToast: () => {} }, // 곡선 삭제 경고(430)
};
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) =>
STUBS[request] ??
(request.startsWith(".")
? loadTs(path.join(path.dirname(full), `${request}.ts`))
: require(request));
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const curve = loadTs(path.join(B05, "logic", "B05_Profile_UI_RouteEdit_Curve.ts"));
const edits = loadTs(path.join(B05, "logic", "B05_Profile_UI_RouteEdit_Edits.ts"));
const frame = loadTs(path.join(B05, "logic", "B05_Profile_UI_RouteEdit_Frame.ts"));
const fit = loadTs(path.join(B05, "logic", "B05_Profile_RouteEdit_StationFit.ts"));
const bar = loadTs(path.join(B05, "B05_Profile_UI_RouteEdit_CurveBar.ts"));
// 곡선 넷(IP1~4) · 직선이 길어 맞춤이 접선 절반에 안 막힘
const ROUTE = [
[0, 0],
[110, 0],
[210, 60],
[320, 60],
[420, -10],
[540, -10],
];
const LIMITS = { minRadiusM: 12, limitRadiusM: 12, limitArcM: 5, intervalM: 20 };
const KEYS = ["planned", "curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"];
const copy = (state) =>
Object.fromEntries(
KEYS.map((key) => [key, state[key].map((v) => (Array.isArray(v) ? [...v] : v))]),
);
function editor(snap, apexLock = []) {
const n = ROUTE.length;
const state = {
picked: -1,
planned: ROUTE.map((v) => [v[0], v[1]]),
nodeInfo: [],
curveInfo: [],
curveOn: Array(n).fill(true),
curveRadius: Array(n).fill(30),
curveLock: Array(n).fill(null),
curveArc: Array(n).fill(null),
apexLock: Array.from({ length: n }, (_, i) => apexLock.includes(i)),
limitRadiusM: LIMITS.limitRadiusM,
limitArcM: LIMITS.limitArcM,
line: [],
snap: snap ? { stepM: LIMITS.intervalM, stationM: 20 } : null,
};
const status = { textContent: "" };
const markEdited = () => {
edits.applyArcLocks(state.planned, state.curveLock, state.curveArc, state.curveRadius);
edits.applyCurveLimits(
state.planned,
state.curveOn,
state.curveRadius,
LIMITS.limitRadiusM,
LIMITS.limitArcM,
);
const built = curve.buildEditedPolyline(
state.planned,
state.curveOn,
state.curveRadius,
LIMITS.minRadiusM,
);
state.line = built.vertices;
state.curveInfo = built.curves;
state.nodeInfo = built.nodes;
};
let committed = null;
const applyEdit = (message) => {
const from = fit.fitStart(committed, state);
if (snap) state.planned = fit.fitCurveStations(state, LIMITS, from);
markEdited();
status.textContent = `예상노선 — ${message} 꼬리`;
committed = copy(state);
};
bar.createCurveBar({
canvas: { getBoundingClientRect: () => ({ left: 0, top: 0, right: 800, bottom: 600 }) },
container: { querySelector: (sel) => (sel === ".b05-routeedit__status" ? status : null) },
state: () => state,
toScreen: (v) => [v[0], v[1]],
applyEdit,
onUnselect: () => {},
});
// 처음 = 스냅 켤 때처럼 전체 맞춤 한 걸음
markEdited();
applyEdit("측점 스냅을 켰습니다.");
const mcs = () =>
frame
.curveSpans(state.line, state.curveInfo)
.map(([a, b]) => Math.round(((a + b) / 2) * 1000) / 1000);
const off = () => fit.offStationCount(state.line, state.curveInfo, LIMITS.intervalM);
const pick = (k) => {
state.picked = k;
};
return { state, status, h: () => handlers, mcs, off, pick };
}
const out = {};
const row = (ed) => ({
mcs: ed.mcs(),
off: ed.off(),
status: ed.status.textContent,
planned: ed.state.planned.map((v) => [...v]),
});
for (const snap of [true, false]) {
const key = snap ? "on" : "off";
const ed = editor(snap);
out[`${key}Start`] = row(ed);
ed.pick(1);
ed.h().onRadius(60); // 앞쪽 곡선 R 키움
out[`${key}R60`] = row(ed);
ed.h().onArcLength(50); // 곡선 길이 입력도
out[`${key}L50`] = row(ed);
ed.h().onRadius(null); // 빈 값 = 자동
out[`${key}Auto`] = row(ed);
}
// IP3 고정 = 그 곡선은 못 맞춤 → 상태줄 「못 맞춘 곡선 No.…」
{
const ed = editor(true, [3]);
out.lockStart = row(ed);
ed.pick(1);
ed.h().onRadius(60);
out.lockR60 = row(ed);
}
process.stdout.write(JSON.stringify(out));