diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts index 935745ce..9ddf07da 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts @@ -302,6 +302,22 @@ export function buildCorridorStructures( const count = Math.max(2, Math.ceil((to - from) / SWEEP_STEP_M) + 1); return Array.from({ length: count }, (_unused, i) => from + ((to - from) * i) / (count - 1)); }; + /** 링 누가거리 목록으로 프레임을 만든다(보어처럼 촘촘한 간격이 필요할 때). */ + const ringsAt = (chainages: number[]): StructureFrame[] => + chainages.map((at) => { + const frame = frameAt(at); + if (!frame) return stationFrame; + return { + cx: frame.cx, + cy: frame.cy, + leftX: frame.leftX, + leftY: frame.leftY, + dz: baseZ != null && frame.designZ != null ? frame.designZ - baseZ : 0, + }; + }); + + // ⚠ `ringsAt` 는 `pushSwept` 보다 **먼저** 선언한다 — 아래 기슭막이 단 반복이 `pushSwept` 를 곧바로 + // 부르므로 뒤에 두면 ReferenceError(TDZ)로 그 구간 3D 코리도가 통째로 안 만들어진다. const pushSwept = ( kind: "revet" | "basin", points: Array<{ offset: number; elevation: number }>, @@ -348,20 +364,6 @@ export function buildCorridorStructures( } } - /** 링 누가거리 목록으로 프레임을 만든다(보어처럼 촘촘한 간격이 필요할 때). */ - const ringsAt = (chainages: number[]): StructureFrame[] => - chainages.map((at) => { - const frame = frameAt(at); - if (!frame) return stationFrame; - return { - cx: frame.cx, - cy: frame.cy, - leftX: frame.leftX, - leftY: frame.leftY, - dz: baseZ != null && frame.designZ != null ? frame.designZ - baseZ : 0, - }; - }); - /** * 관이 뚫고 지나는 구조물 — 단면을 위·아래 조각으로 갈라 링마다 관 지름만큼 * 벌린다(2026-08-25 사용자: 배관 외측 서피스 기준 관통 컷). 관이 닿지 않는 diff --git a/resources/tester/test_b05_corridor_structures_order.py b/resources/tester/test_b05_corridor_structures_order.py new file mode 100644 index 00000000..d0f3a131 --- /dev/null +++ b/resources/tester/test_b05_corridor_structures_order.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +"""3D 코리도 구조물 — 선언 차례(PLAN 24-6a). + +`ringsAt` 를 `const` 로 `pushSwept` 뒤에 선언했더니, 그 사이 기슭막이 단 반복이 `pushSwept` 를 곧바로 불러 +ReferenceError(TDZ)가 나 기슭막이 있는 구간의 3D 코리도가 통째로 안 만들어졌다(2026-08-28 e371c275 ~). +쓰는 `const` 는 그것을 부르는 첫 호출보다 앞에 선언돼야 한다. +""" + +from pathlib import Path + +SOURCE = ( + Path(__file__).resolve().parents[2] / "B05_Profile" / "B05_Profile_UI_Corridor_Structures.ts" +) + + +def test_ringsAt_는_pushSwept_와_첫_호출보다_앞에_선언(): + source = SOURCE.read_text(encoding="utf-8") + rings = source.index("const ringsAt = ") + swept = source.index("const pushSwept = (") + first_call = ( + source.index("pushSwept(\n") if "pushSwept(\n" in source else source.index("pushSwept(") + ) + assert rings < swept, "pushSwept 가 ringsAt 를 쓴다 — 뒤에 선언하면 TDZ" + # 선언(`const pushSwept = (`) 뒤에 나오는 첫 호출 자리 + first_call = source.index("pushSwept(", swept + len("const pushSwept = (")) + assert rings < first_call + + +def test_스윕_쓰는_const_는_호출보다_앞(): + """pushSwept · pushPierced 가 쓰는 도우미 const 가 첫 호출(기슭막이 단 반복)보다 앞에 있다.""" + source = SOURCE.read_text(encoding="utf-8") + first_revet_loop = source.index("for (const tier of revetLayout.tiers)") + for name in ("const ringChainagesOf = ", "const ringsAt = ", "const pushSwept = ("): + assert source.index(name) < first_revet_loop, name