/* 139-2 연동 풀림 굳힘 헬퍼 — 화면 TS(`_Page_Link_Session.freezeLinkedWalls` · `_Cross_Culvert_Wire`)를 * 그 자리에서 트랜스파일해 Node 로 돌림. 입력 = 73-4 픽스처(박스 352.14 흙막이 · 옆 측점 340 · 360). * 짝 = `test_139_2_link_freeze.py`. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const noop = new Proxy({}, { get: () => () => "" }); // 조정창 모듈이 불러올 때 문서 이벤트를 검 — 기하엔 안 쓰임 globalThis.document = { addEventListener: () => undefined }; globalThis.window = globalThis; const { loadTs } = makeLoader({ root: ROOT, requireFor: (full) => (request) => { // 화면 전용(토스트 · 글 · 설정) — 기하엔 안 쓰임 if (request.startsWith("@ui/") || request.startsWith("@config/")) return noop; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); }, }); const resolve = (base) => [base, `${base}.ts`].find((p) => fs.existsSync(p) && fs.statSync(p).isFile()) ?? `${base}.ts`; const B06 = (f) => path.join(ROOT, "B06_Section", f); const { freezeLinkedWalls } = loadTs(B06("B06_Section_UI_Page_Link_Session.ts")); const { computeCardCulvert, culvertLinkFor } = loadTs( B06("logic/B06_Section_Cross_Culvert_Wire.ts"), ); const sections = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"), ).cross_sections; const at = (c) => sections.find((s) => Math.abs(s.chainage_m - c) < 0.6); // 제어기 흉내 — 측점:역할 → 4축(세션 맵과 같은 꼴) const stored = new Map(); const keyOf = (c, role) => `${c.toFixed(2)}:${role}`; const ZERO = { x: 0, d: null, h: null, m: null }; const revetOffset = { adjustFor: (s, role) => ({ ...ZERO, ...(stored.get(keyOf(s.chainage_m, role)) ?? {}) }), storedAdjustFor: (s, role) => stored.get(keyOf(s.chainage_m, role)) ?? null, update: (c, role, patch) => stored.set(keyOf(c, role), { ...ZERO, ...(stored.get(keyOf(c, role)) ?? {}), ...patch }), reset: (c, role) => stored.delete(keyOf(c, role)), syncApplied: () => undefined, selectedFor: () => null, highlightFor: () => null, select: () => undefined, }; const extraWalls = { countFor: () => 0, consumeEqualize: () => false }; let linked = true; const revetLink = { linkedFor: () => linked, followGradeFor: () => true }; const controls = { revetOffset, extraWalls, revetLink }; const round = (v) => Math.round(v * 1000) / 1000; const shape = (s) => { const link = culvertLinkFor(s, sections, () => null); const layout = computeCardCulvert( s, s.samples, null, revetOffset, undefined, extraWalls, link, revetLink, ); return (layout?.walls ?? []).map((w) => ({ role: w.role, points: w.points.map((p) => [round(p.offset), round(p.elevation)]), })); }; const owner = at(352.14); const card = at(360); // 소유 측점 벽을 조금 옮겨 둠 — 옆 카드가 물려받는 자리가 자동 자리가 아니게 revetOffset.update(owner.chainage_m, "outlet", { x: 0.7, h: 2.3 }); const linkedShape = shape(card); freezeLinkedWalls(card, sections, controls); const frozen = Object.fromEntries( [...stored].filter(([k]) => k.startsWith("360.00:")).map(([k, v]) => [k.slice(7), v]), ); linked = false; const detachedShape = shape(card); // 푼 뒤 소유 측점을 또 옮김 — 푼 카드는 안 따라감 revetOffset.update(owner.chainage_m, "outlet", { x: 2.0 }); const afterOwnerMove = shape(card); linked = true; // 대조 — 연동이면 소유 측점 이동을 따라감(시험이 실제로 재는지) const linkedAfterMove = shape(card); process.stdout.write( JSON.stringify({ linkedShape, frozen, detachedShape, afterOwnerMove, linkedAfterMove }), );