fix(B05): 종단 편집 최소고 가드 개편 - 구간 쉬프트 차단 + 시설별 산식

2026-08-23 사용자 보고: 횡단배수 최소고를 어기고 구간 쉬프트가 되는 경우
있음(가드가 측점 ▼에만, 관경 고정 산식).

- 가드 개편(blocksMinCover): 편집 후보로 정렬을 계산해 시설별 최소고
  (minCoverPoints 원천 - 배수관 관경+토피, BOX암거 구체높이+토피, 세월교
  +물넘이, 물넘이포장 제외) 위반이 새로 생기거나 커지면 차단. 측점 ▲▼와
  구간 ⇧⇩ 모두 적용, 이웃 틸트가 종단곡선을 거쳐 배관 계획고를 내리는
  경로까지 커버. 이미 위반이면 악화만 차단(복구 편집 허용). 기존
  blocksPipeLowering(측점 ▼ 전용·관경 1000 고정)은 제거.
- shiftSegment 시딩 수정: 초기 오프셋을 곡선 샘플 계획고(PVI z - 중앙종거)
  대신 변화점 실제 z로 계산 - 첫 이동에서 변화점이 내려앉아 상승 후 하강
  복귀가 비대칭이던 화면 실측 버그 해소.
- MinCover 낡은 주석 정정(구현은 세월교 포함, 물넘이포장만 제외).

검증: tsc 통과 · pytest 182 passed(가드·시딩 신규 8건) · 공용 브라우저
실조작 - 배수관 앵커 구간 ⬇ 즉시 차단(+1.5), BOX암거 구간 차단(+2.5),
⬆/⬇ 대칭 복귀, 검증 상태 전부 원복.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 16:27:41 +09:00
co-authored by Claude Fable 5
parent 8393d22c23
commit eb716ffbb1
4 changed files with 51 additions and 26 deletions
@@ -502,10 +502,18 @@ export function shiftSegment(
const baseZ = base.basePvi.map((node) => node.elevation_m);
const current = buildAlignment(base, edits);
const offsets = { ...edits.station_offsets };
// 구간 끝은 항상 변화점이다 — 초기 오프셋은 **변화점 실제 z** 기준으로 잡는다.
// 곡선 샘플 계획고(= PVI z − 중앙종거)를 쓰면 첫 이동에서 변화점이 중앙종거만큼
// 내려앉아 ⬆ 뒤 ⬇ 복귀가 원위치보다 낮아진다(2026-08-23 화면 실측: 복귀가
// 최소고 가드에 걸림). 변화점이 못 찾아지는 예외에서만 샘플 보간으로 폴백한다.
const nodeElevationAt = (chainage: number): number => {
const node = current.pvi.find((entry) => Math.abs(entry.chainage_m - chainage) < 1e-6);
return node ? node.elevation_m : planElevationAt(current, chainage);
};
[segment.from_m, segment.to_m].forEach((chainage) => {
const key = chainageKey(chainage);
const existing =
offsets[key] ?? planElevationAt(current, chainage) - interpolate(baseS, baseZ, chainage);
offsets[key] ?? nodeElevationAt(chainage) - interpolate(baseS, baseZ, chainage);
offsets[key] = Number((existing + delta).toFixed(6));
});
return { ...edits, station_offsets: offsets };