fix(B05): 노견 쪽·성토 끝단 쪽 리본 경계에는 측벽을 세우지 않는다

거기서 잘린 게 아니라 리본이 끝난 자리다(2026-08-27 사용자).

- 통째로 지운 셀의 모서리 벽: 열 방향 이웃이 리본 밖이면 건너뛴다(`col > 0`,
  `col < cols - 2`). 행 방향(노선 앞뒤)은 종전대로.
- 걸친 셀의 교점 직선: 두 교점이 모두 안쪽 끝(노견)이나 바깥 끝(성토 끝선) 위를
  달리면 건너뛴다. 판정은 **열 좌표 기준 ±0.1열**(셀 폭의 10%) — 커브가 끝선과
  나란히 지나면 교점이 가로 모서리에 걸려 열 좌표가 3~5mm 빗나간다(실측).
- BUILD_VERSION 55 → 57.

검증(실빌드 데이터):
- 벽 삼각형 1,450/1,358 → 852/824(우/좌).
- 성토끝단선 위를 달리는 벽 구간 8/6 → **0**, 노견선 위 2/2 → **0**
  (구간 길이 3cm 이상, 선까지 거리 1cm 이내 기준).
- 벽 ↔ 절단커브 거리는 그대로 중앙값 0.000m. 리본 조각 삼각형 814/787 불변.
- npm run typecheck 통과, prettier 적용.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 11:18:32 +09:00
co-authored by Claude Opus 5
parent d70908f973
commit b9938dfa18
2 changed files with 17 additions and 5 deletions
+3 -3
View File
@@ -106,9 +106,9 @@ function fnv1a(text: string): string {
// 투영 커브 안쪽 성토면 절단 — 커브 다각형 기준(2026-08-26).
// ※ 40에 머물러 있는 동안 기하 수정 3회가 통째로 묻혔다 — 저장본이 그대로
// 복원되어 화면·데이터가 하나도 안 바뀌었다. 기하를 고치면 **반드시** 올릴 것.
// 55 = 측벽 **실제로 잘린 선**(걸친 셀은 교점 직선, 통째 셀은 이웃 모서리)에
// 세운다 — 종전 격자 계단은 면 경계와 최대 0.3m 어긋났다(2026-08-27 사용자).
const BUILD_VERSION = 55;
// 56 = 측벽에서 **노견 쪽·성토 끝단 쪽 리본 경계 벽을 뺀다** — 거기서 잘린 게
// 아니라 리본이 끝난 자리다(2026-08-27 사용자).
const BUILD_VERSION = 57;
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
+14 -2
View File
@@ -334,6 +334,14 @@ class SkirtBuilder {
/** 걸친 셀 — 그 셀을 자른 교점 직선에 벽을 세운다. */
addChord(row: number, col: number, chord: readonly [Corner, Corner]): void {
const [h0, h1] = chord;
// 교점 직선이 리본 **안쪽 끝(노견)·바깥 끝(성토 끝선)** 위를 그대로 달리면 건너뛴다 —
// 투영 커브의 바깥 변이 곧 성토 끝선이라 거기서는 자른 게 아니다(2026-08-27 사용자).
// 판정은 **열 단위**로 한다 — 커브가 끝선과 나란히 지나면 교점이 가로 모서리에
// 걸려 열 좌표가 끝값에서 조금 빗나간다(실측 3~5mm). 셀 폭의 10%를 여유로 둔다.
const last = this.ribbon.colCount - 1;
const TOL = 0.1;
if (h0.a < TOL && h1.a < TOL) return;
if (h0.a > last - TOL && h1.a > last - TOL) return;
this.quad(
h0.p[0],
h0.p[1],
@@ -478,8 +486,12 @@ export function maskFillByPlanCurves(
if (!mask[i] || chorded[i]) continue;
if (filled(row - 1, col)) skirt.addEdge(row, col, row, col + 1);
if (filled(row + 1, col)) skirt.addEdge(row + 1, col + 1, row + 1, col);
if (filled(row, col - 1)) skirt.addEdge(row + 1, col, row, col);
if (filled(row, col + 1)) skirt.addEdge(row, col + 1, row + 1, col + 1);
// 리본의 **안쪽 끝(노견)·바깥 끝(성토 끝단)**에는 벽을 세우지 않는다 —
// 거기서 잘린 게 아니라 리본이 끝난 것이다(2026-08-27 사용자).
if (col > 0 && filled(row, col - 1)) skirt.addEdge(row + 1, col, row, col);
if (col < cols - 2 && filled(row, col + 1)) {
skirt.addEdge(row, col + 1, row + 1, col + 1);
}
}
}
}