⚠ **뿌리** — `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>
112 lines
3.4 KiB
JavaScript
112 lines
3.4 KiB
JavaScript
/* B05 잘린 자리 윤곽(`cut-merged`) 단위검증 헬퍼 — `_Corridor_Cut.ts`를 그 자리에서
|
|
* 트랜스파일해 Node로 돌린다(타입 import뿐이라 의존 없음). 2026-09-02 */
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
|
|
const source = fs.readFileSync(
|
|
path.join(__dirname, "..", "..", "B05_Profile", "B05_Profile_UI_Corridor_Cut.ts"),
|
|
"utf8",
|
|
);
|
|
const js = ts.transpileModule(source, {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const moduleBox = { exports: {} };
|
|
new Function("exports", "module", "require", js)(moduleBox.exports, moduleBox, require);
|
|
const { maskFillByPlanCurves } = moduleBox.exports;
|
|
|
|
/** 직선 노선 성토 리본 — x = 종방향(1m/행), y = 횡방향(1m/열), z = 0. */
|
|
function ribbonOf(rows, cols) {
|
|
const positions = new Float64Array(rows * cols * 3);
|
|
for (let row = 0; row < rows; row += 1) {
|
|
for (let col = 0; col < cols; col += 1) {
|
|
const i = (row * cols + col) * 3;
|
|
positions[i] = row;
|
|
positions[i + 1] = col;
|
|
positions[i + 2] = 0;
|
|
}
|
|
}
|
|
return {
|
|
kind: "fill",
|
|
side: "left",
|
|
colCount: cols,
|
|
chainages: Array.from({ length: rows }, (_v, row) => row),
|
|
positions,
|
|
groundZ: new Float64Array(rows * cols),
|
|
};
|
|
}
|
|
|
|
/** 절취 영역 — 직사각형 x∈[x0,x1], y∈[y0,y1] (닫힘점 포함). */
|
|
function rectCurve(x0, x1, y0, y1) {
|
|
return {
|
|
setChainageM: (x0 + x1) / 2,
|
|
source: "slope-projected",
|
|
role: "inlet",
|
|
side: "left",
|
|
planZ: 10,
|
|
loops: [
|
|
[
|
|
[x0, y0, 10],
|
|
[x1, y0, 10],
|
|
[x1, y1, 10],
|
|
[x0, y1, 10],
|
|
[x0, y0, 10],
|
|
],
|
|
],
|
|
};
|
|
}
|
|
|
|
function insideLoop(loop, x, y) {
|
|
let hit = false;
|
|
for (let i = 0, j = loop.length - 1; i < loop.length; j = i, i += 1) {
|
|
const [xi, yi] = loop[i];
|
|
const [xj, yj] = loop[j];
|
|
if (yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) hit = !hit;
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
function run(rows, cols, curve) {
|
|
const ribbon = ribbonOf(rows, cols);
|
|
const { ribbons, curves } = maskFillByPlanCurves([ribbon], [curve]);
|
|
const cut = ribbons[0];
|
|
const merged = curves.filter((c) => c.source === "cut-merged");
|
|
const loops = merged.flatMap((c) => c.loops);
|
|
let masked = 0;
|
|
let covered = 0;
|
|
const uncovered = [];
|
|
if (cut.cellMask) {
|
|
for (let row = 0; row < rows - 1; row += 1) {
|
|
for (let col = 0; col < cols - 1; col += 1) {
|
|
if (!cut.cellMask[row * (cols - 1) + col]) continue;
|
|
masked += 1;
|
|
const cx = row + 0.5;
|
|
const cy = col + 0.5;
|
|
if (loops.some((loop) => insideLoop(loop, cx, cy))) covered += 1;
|
|
else uncovered.push([row, col]);
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
masked,
|
|
covered,
|
|
uncovered,
|
|
loops: loops.map((loop) => ({
|
|
n: loop.length,
|
|
closed:
|
|
loop.length > 1 &&
|
|
loop[0][0] === loop[loop.length - 1][0] &&
|
|
loop[0][1] === loop[loop.length - 1][1],
|
|
points: loop.map(([x, y]) => [x, y]),
|
|
})),
|
|
};
|
|
}
|
|
|
|
const out = {
|
|
// 직사각형 — 시작 모서리(첫 행 첫 열)가 살아야 4모서리 + 닫힘점 = 5.
|
|
rect: run(12, 6, rectCurve(1.9, 7.1, -0.5, 3.4)),
|
|
// 한 셀짜리 — 가장 작은 고리도 4모서리를 다 지킨다.
|
|
single: run(6, 4, rectCurve(1.9, 3.1, 0.9, 2.1)),
|
|
};
|
|
process.stdout.write(JSON.stringify(out));
|