fix(B05): 기슭막이 상단 성토면을 되메움 리본 배열에 담는다

배수관 세트(기슭막이) 구간에서 노견 → 벽 이음선 상단 성토 사면이 3D에서 통째로
비어 있었다. 포켓(FILL_CUT_APPLY)은 노견까지 파는데, 되메움 배열(fillRuns,
4a5a98fe)에는 벽 아래 다단 성토선·집수정 성토선만 담겨 그 위가 안 메워졌다.
culvertSilhouettes가 designTrim 사면선을 push(실루엣)만 하고 addRun(리본)은 하지
않은 게 원인 — 세월교·BOX암거는 각각 fillSegments·fillLine으로 담고 있어 무관하다.

새 면을 만들지 않는다. 실루엣이 이미 쓰던 designTrim.min/maxSlope 점 배열을 리본
배열에도 등록할 뿐이다.

- Station_Structure: pushTopSlope() 추가 — push + addRun을 함께 하되 측당 한 번만.
  벽 루프와 집수정 분기가 같은 designTrim 사면선을 각각 담으므로 표시가 없으면
  리본이 두 장 겹친다. 호출 3곳(벽 루프, 집수정 slope, 집수정 2점 폴백).
- Corridor: BUILD_VERSION 79 -> 80.

실측(공용 브라우저 A/B, git stash로 코드만 교체):
- 패치 리본 6 -> 10장. 늘어난 4장 전부 기슭막이 세트(측점 84.3·275.71).
- 275.71 좌측: 기존 z 531.11~535.93 바로 위에 536.85~539.92가 새로 앉았다.
  사이 0.92m는 벽 전면 — 구조물 솔리드 몫이라 원래 비는 자리.
- 84.3은 수정 전 양측 0장 — 포켓만 파고 되메움이 통째로 없었다.
- 날개형 구조물(149.73·200.92) 리본 수·z 범위 소수점까지 불변 = 무회귀.
- tsc --noEmit 0.

화면 검증은 사용자가 직접 수행(2026-08-27 사용자 지시).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 21:02:28 +09:00
co-authored by Claude Opus 5
parent a756181067
commit 521fb89c30
2 changed files with 29 additions and 4 deletions
+6 -1
View File
@@ -157,7 +157,12 @@ function fnv1a(text: string): string {
// 지우고 걸친 셀은 안쪽 조각만 남긴다(`patchClip`). 행 단위 바깥선 트림은 폐기 —
// 비스듬한 사다리꼴 경계를 행 끝 하나로는 못 맞춰 74셀이 삐져나왔다(실측).
// 지우는 면과 채우는 면이 같은 경계를 공유한다(2026-08-27 사용자).
const BUILD_VERSION = 79;
// 80 = 배수관 세트(기슭막이) **노견 → 벽 이음선 상단 성토 사면**을 되메움 리본 배열
// (`fillRuns`)에도 담는다 — 78/79의 포켓은 노견까지 파는데 79까지의 배열에는
// 벽 아래 다단·집수정 성토선만 있어 **기슭막이 상단부가 통째로 비었다**
// (2026-08-27 사용자). 새 면이 아니라 실루엣이 이미 쓰던 `designTrim` 사면선
// 그대로다. 벽 루프·집수정 분기가 같은 선을 담으므로 측당 한 번만 등록한다.
const BUILD_VERSION = 80;
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
@@ -258,11 +258,31 @@ export function culvertSilhouettes(section: CrossSection): StructureSilhouette[]
};
const trimSlopeOf = (side: "left" | "right") =>
side === "left" ? layout.designTrim?.maxSlope : layout.designTrim?.minSlope;
/**
* 노견 → 구조물 상단 성토 사면선을 이미 담은 측. 벽 루프와 집수정 분기가 **같은**
* `designTrim` 사면선을 각각 담으므로 표시가 없으면 리본이 두 장 겹친다.
*/
const topSlopeSides = new Set<"left" | "right">();
/**
* 노견 → 구조물 상단 성토 사면 — 실루엣과 **되메움 리본에 함께** 담는다
* (2026-08-27 사용자: "기슭막이 상단부 성토면이 비어 있다"). `fillRuns` 도입
* (`4a5a98fe`) 때 이 줄만 `addRun`에서 빠져 포켓이 그 자리만 안 메워졌다.
* 벽 전면과 달리 구조물 솔리드가 덮지 않는 자리라 리본이 있어야 한다.
*/
const pushTopSlope = (
side: "left" | "right",
points: ReadonlyArray<{ offset: number; elevation: number }>,
): void => {
push(side, points);
if (topSlopeSides.has(side)) return;
topSlopeSides.add(side);
addRun(side, points);
};
for (const wall of layout.walls) {
const side: "left" | "right" = wall.outward > 0 ? "left" : "right";
const slope = trimSlopeOf(side);
if (slope) push(side, slope.points);
if (slope) pushTopSlope(side, slope.points);
// 구조물 전면 실루엣 — 이음선 상단에서 전면 발끝까지 내려온다.
push(side, [wall.topJoint, wall.bottomFront]);
spans[side] = wallSpanOf(section, wall.role === "inlet" ? "inlet" : "outlet");
@@ -292,10 +312,10 @@ export function culvertSilhouettes(section: CrossSection): StructureSilhouette[]
const trimElevation =
inletSide === "left" ? layout.designTrim?.maxElevation : layout.designTrim?.minElevation;
if (slope) {
push(inletSide, slope.points);
pushTopSlope(inletSide, slope.points);
} else if (trimOffset != null && trimElevation != null && Number.isFinite(trimOffset)) {
const edge = inletSide === "left" ? edges.left : edges.right;
push(inletSide, [
pushTopSlope(inletSide, [
{ offset: edge.offset_m, elevation: edge.elevation_m },
{ offset: trimOffset, elevation: trimElevation },
]);