fix(B05): 잘린 자리 윤곽의 시작 모서리 유실 수정 — dropCollinear 닫힘점 처리
- `_Corridor_Cut.ts` `dropCollinear`: 추적 고리의 닫힘 중복점(머리=꼬리)을 떼고 순환 다듬기 뒤 다시 붙임. 그대로 두면 머리 점의 이웃이 자기 자신이 되어 `away=0` 으로 지워지고 양옆 일직선 점까지 빠져 8고리 전부 시작 모서리가 대각선으로 잘림(63셀 구멍). - 공선 판정의 앞점을 마지막으로 남긴 점 기준으로 변경 — 완만한 곡선에서 누적 편차로 현이 곡선 안쪽으로 파고들던 2셀 제거(`_Region.ts` `simplifyLoop` 과 같은 규칙). - `BUILD_VERSION` 89→90 으로 저장본 만료. - 실측: 되살릴 영역 미포함 셀 63→0(우 7886/7886·좌 7023/7023), 8고리 닫힘, `tmp/tests/test_b05_cut_merged_loop.py` 3건 포함 359 passed. - PLAN.md 해당 항목 완료 표시 + 자체검증 기록(검증 대기). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -218,7 +218,11 @@ function fnv1a(text: string): string {
|
||||
// 걸려 있어 BOX 패치는 한 번도 안 잘리고 영역 밖으로 넘쳤다 — 실측에서 BOX 패치
|
||||
// 리본 두 벌(198.4~203.5m)에 셀 마스크가 없었다. BOX도 `wing-box` 커브로 절취
|
||||
// 영역을 내므로 같은 반대 규칙이 성립한다.
|
||||
const BUILD_VERSION = 89;
|
||||
// 90 = 잘린 자리 윤곽(`cut-merged`)의 **시작 모서리 유실**을 고친다 — `dropCollinear`가
|
||||
// 고리의 닫힘 중복점(머리=꼬리)을 순환 이웃으로 셈해 머리 모서리를 지우고, 그 양옆
|
||||
// 일직선 점까지 빠져 첫 행이 대각선으로 잘렸다(실측: 8고리 전부, 63셀 구멍 —
|
||||
// 우 197.6~198.2m BOX 앞 쐐기 33셀이 가장 큼). 되살릴 영역이 이 커브라 저장본을 만료한다.
|
||||
const BUILD_VERSION = 90;
|
||||
|
||||
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
|
||||
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
|
||||
|
||||
@@ -323,12 +323,24 @@ const COLLINEAR_TOLERANCE_M = 0.001;
|
||||
function dropCollinear(
|
||||
loop: ReadonlyArray<[number, number, number]>,
|
||||
): Array<[number, number, number]> {
|
||||
if (loop.length < 3) return [...loop];
|
||||
// 추적 고리는 머리 점이 꼬리에 한 번 더 실려 온다(닫힘 표시). **순환으로 다듬기 전에
|
||||
// 뗀다** — 그대로 두면 머리 점의 이웃이 자기 자신이 되어 `away = 0`으로 지워지고, 그
|
||||
// 모서리 양옆의 일직선 점들까지 함께 빠져 **시작 모서리가 대각선으로 잘려 나갔다**
|
||||
// (2026-09-02 실측: 고리마다 첫 행 쐐기 63셀이 되살릴 영역에서 빠져 구멍이 됐다).
|
||||
const closed =
|
||||
loop.length > 1 &&
|
||||
loop[0][0] === loop[loop.length - 1][0] &&
|
||||
loop[0][1] === loop[loop.length - 1][1];
|
||||
const ring = closed ? loop.slice(0, -1) : loop;
|
||||
if (ring.length < 3) return [...loop];
|
||||
const kept: Array<[number, number, number]> = [];
|
||||
for (let i = 0; i < loop.length; i += 1) {
|
||||
const prev = loop[(i - 1 + loop.length) % loop.length];
|
||||
const at = loop[i];
|
||||
const next = loop[(i + 1) % loop.length];
|
||||
for (let i = 0; i < ring.length; i += 1) {
|
||||
// 앞점은 **마지막으로 남긴 점**이다 — 바로 앞 원점으로 재면 완만한 곡선에서 점마다
|
||||
// 1mm 미만이라 전부 빠지고, 남은 긴 현이 곡선 안쪽으로 2cm 파고든다(2026-09-02 실측:
|
||||
// 직선부 노견 열의 셀 중심이 현 위에 놓였다). `_Region.ts` `simplifyLoop`과 같은 규칙.
|
||||
const prev = kept[kept.length - 1] ?? ring[ring.length - 1];
|
||||
const at = ring[i];
|
||||
const next = ring[(i + 1) % ring.length];
|
||||
const dx = next[0] - prev[0];
|
||||
const dy = next[1] - prev[1];
|
||||
const span = Math.hypot(dx, dy);
|
||||
@@ -339,6 +351,8 @@ function dropCollinear(
|
||||
}
|
||||
kept.push(at);
|
||||
}
|
||||
// 닫힘 표시는 그대로 돌려준다 — 스케치가 `Line`으로 그려져 꼬리 점이 있어야 닫힌다.
|
||||
if (closed && kept.length) kept.push(kept[0]);
|
||||
return kept;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user