원인: 이동 판정 기준이 0.005m였다. 폼의 측점 표기는 0.1m로 반올림되므로(관이
275.71m면 폼은 275.7m) 높이만 고쳐도 0.01m 차이가 나 이동으로 잡혔다.
- 판정 기준을 관 매칭과 같은 0.51m로 맞춘다.
- 실제로 옮긴 경우에도 함께 온 제원은 반영한다 — 위치만 못 옮길 뿐 B05·B06이
같은 데이터를 쓰는 한 화면이다.
원인: 좌측 폼의 실시간 반영이 세션 예약(culvertopt)과 목록 캐시까지만 갱신하고,
횡단 캐시(section.culvert 스펙)를 건드리지 않아 카드가 옛 값으로 남았다.
- 폼 옵션 키를 culvert 스펙 자리로 옮기는 applyPipeOptionsToCache 추가
(관종·관경, 유입·유출 기슭막이 형태·높이·길이·전/후, 집수정 구간값, 구조).
- 연장이 덮는 범위의 카드만 다시 그린다 — 조정창 조작과 같은 흐름.
- 화면 캐시만 고친다. 영구저장은 [저장]·[확정] 그대로.
원인: 단 수·옵션 행 DOM을 유입구·유출구로 옮긴 뒤, 다음 선택에서 clearSideSlots가
replaceChildren으로 그 행을 없앴다. 창은 카드당 하나라 같은 카드에서 다른 벽을
고르면 행이 영영 돌아오지 않았다.
- 옮길 때 제자리에 주석 표식을 남기고, 선택이 바뀌면 그 자리로 되돌린다.
- 단 수 행은 **기준(소유) 측점**에서만 다룬다 — 연동으로 넘어온 링크 카드에서는
숨긴다(2026-08-29 사용자).
검증: 유출→유입→유출로 옮겨 다녀도 단 수·옵션이 매번 제자리에 복귀.
링크 카드(4+0.0)에서는 옵션만 뜨고 단 수는 숨김.
원인: 앞선 커밋에서 넣은 '대표 벽 자동 선택'(selectDefaultWall)이 카드를 고를
때마다 벽을 골라 조정창까지 띄웠다(2026-08-29 사용자 보고: 기슭막이(유출) 창이
계속 떠 있음).
- selectDefaultWall과 그에 딸린 자동 선택 표시(withAutoAdopt 등)를 걷어냈다.
- 카드·목록 선택 리스너에서 남아 있는 벽 선택을 비운다. 벽을 찍어 카드가 딸려
선택된 경우는 건드리지 않는다(isWallSelecting 가드).
부작용: 단 수·옵션 행은 도면에서 벽을 고른 뒤에만 유입구·유출구에 붙는다
(사용자 확인: 측점 선택 시 자동 구조물 선택은 필요 없음).
검증: 목록 선택·카드 선택 시 조정창 0개·강조 없음, 벽 클릭 시 기슭막이(유출) 창
+ 유출구 강조 + 단 수·옵션 행 표시.
2026-08-29 사용자 지시 3건:
1. 유입구·유출구로 옮겨 온 단 수·옵션 행을 폼 양식(라벨+한 덩어리 컨트롤)으로
맞추고 2열 격자로 세운다.
2. 이미 고른 항목은 값이 바뀔 때마다 곧바로 반영한다([수정] 버튼 불필요).
버튼은 visibility만 꺼 자리를 남겨 삭제·리셋의 위치·크기가 흔들리지 않게 했다.
정본 주입이 되먹임으로 돌아와도 편집 중인 폼은 닫지 않는다.
3. 도면에서 구조물을 고르면 그쪽 그룹(유입구/유출구)을 붉은 계열로 강조한다.
카드만 고른 경우는 강조하지 않는다 — 대표 벽은 조용히 딸려 오고, 그 벽을
다시 눌러 선택을 풀면 강조만 꺼지고 행은 남는다.
검증: 슬롯 grid 2열, 카드 선택 시 강조 없음·행 2개, 다른 벽 클릭 시 유입구 그룹
붉은 테두리(rgb(192,57,43)), 재클릭 시 강조 해제·행 복귀. 길이 + 즉시 캐시
반영(11) 후 - 원복(10), 폼 유지. typecheck 통과.
2026-08-29 사용자 보고 두 건:
① 값을 고치고 [수정]하면 '추가·이동·삭제는 B05에서' 안내만 떴다 — B06에서 관
옵션 저장 경로가 없었다. 조정창 구간값과 같은 저장기(culvertOptions.queue)로
보내 캐시에 예약하고 [저장]·[확정]이 정본에 쓴다. 기준점 이동만 배수유역
재분할이 걸려 B05 몫으로 남기고, 안내 문구도 추가·삭제/이동으로 나눴다.
② 연동·경사 옵션과 단 수 행이 구조물 배치에서 사라졌다 — 그 행들은 고른 벽에
딸린 것이라 도면에서 벽을 찍기 전에는 이식될 창 자체가 없었다. 시설을 폼에
올릴 때 대표 벽(유출 기준벽, 없으면 유입)을 함께 골라 행이 서게 한다.
검증: 목록에서 배수관 선택 → 유출구 그룹에 [추가 기슭막이(단)]·[옵션] 표시.
길이 + 후 [수정] → '수정한 값은 [저장]·[확정] 때 반영됩니다' + 세션
b06:culvertopt에 inlet_revet_length_m 11 예약 확인. typecheck 통과.
- SectionView.setStationSelectListener 신설 — 카드(측점) 선택 변경 알림
- 좌측 패널 showAtChainage: 계곡 통과 시설(pipe_points) 우선, 없으면 그 측점을
덮는 구조물 정본 항목(점형 근사·구간형 범위), 둘 다 없으면 선택 해제
- 연동 링크 카드는 structureSpan.ownerOf로 소유 측점 시설을 보여 준다
검증(공용 브라우저): 4+4.3 배수관·7+9.7 세월교·10+0.9 BOX암거 카드 클릭 시
종류·기준 측점·목록 강조 일치. 링크 카드 4+0.0→4+4.3, 14+0.0→13+15.7 소유
측점 표시. 구조물 없는 0+0.0 카드는 폼 해제. typecheck 통과.
증상(사용자): 세월교 횡단도 측벽이 기울어 있고 바닥판 모서리도 깎여 있다.
확인 결과 세로(수직)로 그려져야 한다.
원인: 세월교 측벽은 ㄴ형 집수정 부재를 재사용한다(2026-08-25). 그래서 집수정
규칙인 전면 1:0.3 기움과 거기서 파생된 바닥판 taper(0.3 × 두께)가 딸려 왔다.
세월교 근거가 아니라 부재를 빌려 오며 따라온 값이다.
근거: 지식DB·교본 원문에 세월교 단면 기울기 수치는 없다. 교본 그림 3-18
세월교 설치 종단도의 구체가 수직 벽이고, 사용자가 그렇게 확정했다.
고침: buildBasin에 leanRatio 인자를 두고(기본 0.3) 세월교만 0을 넘긴다.
집수정 호출부는 인자를 안 넘기므로 동작이 그대로다. 벽 상단 도로측 꼭짓점
(설계선 트림 경계)은 노견 끝 그대로라 트림·접근선 규칙도 불변이고, 바닥
길이는 날개벽 투영 연장 + 벽 두께로 유지된다. 3D는 같은 폴리곤을 스윕하므로
자동 반영된다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
증상(사용자): 독립 기슭막이 우측 다단이 구간값에 따라 옆 측점에서 없어지는데,
그 위 기준벽 이후 성토면은 갱신되지 않는다.
원인: 링크 카드가 다단을 **다 세워 놓고 그림만 지웠다**(trimLinkedLayout이
extraWalls만 걸러낸다). 성토부선(outletFill·basinFill)은 원래 단 수로 계산된
값이라 마지막 남은 벽 뒤가 비어 있었다.
고침: 이 측점까지 오지 않는 단은 계산에 들어가기 전에 뺀다(tierPrefix — 단은
위에서부터 쌓이므로 덮이는 앞부분만 남긴다). buildOutletExtras가 마지막 단 뒤
성토부선을 지반까지 다시 긋는다.
화면 실측(234.1 소유 → 240.0 링크, bextra0 후 6.0m → 1.5m): 링크 카드 벽 4 → 3,
유입부 성토부선 0.91+3.00 → 5.27 한 줄로 지반까지. 소유 카드는 불변.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 확정(2026-08-30) 세 가지를 반영한다.
① 링크 강조 — 한 기슭막이는 그 연장 안 모든 횡단도에서 같이 강조된다. 선택
(조정창)과 강조를 분리해 `highlightFor`/`applyRevetHighlight`로 카드 재생성 없이
클래스만 갈아 끼운다(줌·팬 보존). 다단은 단 번호까지 같은 키로 짝짓는다.
② 연동 = 분리가 아니다 — 길이·전/후로 이미 하나의 기슭막이다. 연동을 푼 측점은
그 자리에 서므로 3D에서 벽이 노선과 평행이 아니라 자기 경로선을 따라간다. 링마다
폴리곤을 갈아 끼워(`polygons`) 측점 사이를 Catmull-Rom으로 잇는다. 해제 시 단 수가
0이 되던 것(벽 4→2)도 고쳤다 — 소유 측점 4축에서 출발해 손댄 벽만 갈아 끼운다.
③ 경사 = 수평(Z 일원화) — 3D 링만 눕히던 것을 횡단도까지 넓혔다. 링크 카드 벽
상단 표고를 소유 측점과 맞추고(d = 소유 d + 1.2×dz) 차이는 노견 확장이 흡수한다.
그래서 수평 중에는 연동 해제 불가 — 버튼을 잠그고, 해제해 둔 값은 지운다.
곁다리: 3D 재빌드 해시에 extra_spans·revet_link_detached 추가(안 넣으면 저장
코리도가 안 만료된다), 캐시 저장 payload에 extra_spans 누락 보완, 3D 머리 주석을
"정본만 읽는다" → "캐시를 읽는다"로 정정.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CLAUDE.md 5장(조작·데이터 흐름 정책)을 코드에 반영한다. 조작분은 세션에만 쌓고
영구저장은 [저장]·[확정]에서만 하며, [초기화]는 재계산이 아니라 초기값 복원이다.
자동저장 폐지
- B06 조정창 기준벽 구간값: 800ms 디바운스 PUT을 없애고 세션(b06:culvertopt)에
담는다. flushCulvertOptions()를 B06 [저장]·[확정]과 B05 [저장]이 부른다.
- B05 구조물: 조작 즉시 PUT + 서버 재조회로 화면을 덮어쓰던 것을 세션
(b05:structures) 적재로 바꾼다. 저장 전에도 고르고 지울 수 있도록 식별자를
crypto.randomUUID()로 미리 발급한다(서버는 빈 값일 때만 새로 발급).
- B05에서 만지고 B06에서 확정하는 경로를 위해 flushPendingStructures()를 공용화.
초기값 스냅샷
- common_util_initial_snapshot: 자동설계 체인 성공 직후 정본 파일 4트리와
routes+자식 4표를 initial_snapshot/에 뜬다. 이후 읽기 전용.
- reset_route_design: 스냅샷이 있으면 DELETE와 같은 트랜잭션에서 행을 되세우고
파일을 되돌린다. 없으면 종전 재계산 폴백. 응답에 restored를 더한다.
조작 응답
- 등고선 재적용·B06 진입 정합·[모두 적용]에서 전체 화면 오버레이 제거.
- 재계산이 design을 갈아끼울 때 extra_spans를 보존한다(다른 조작값과 동일).
테스트: tmp/tests/test_initial_snapshot.py 5건 추가. 245 passed·8 failed(기존
실패 — 기슭막이 이관 때 placement가 interval→point로 바뀐 것을 테스트 미반영).
고정 10m(전/후 5)를 기본으로 두니 소유 벽 연장(유출 12m·유입 21.5m)보다 짧아,
+5.9m 떨어진 링크 카드에서 기준벽은 서는데 다단만 사라졌다. 손대기 전에는 기준벽
연장을 그대로 따르고, 손으로 잡은 단만 그 값으로 고정한다.
- tierSpanOf: 저장값 → 소유 벽 연장(유출측은 유출 벽, 유입측은 유입 벽/집수정)
→ 마지막으로 10m(5/5). 그리기·링크 판정·조정창 표시가 이 함수 하나를 쓴다
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
단별 구간값을 넣으면서 성토부선까지 단(tier) 연장으로 잠갔다. 단 기본 연장이
10m(전/후 5)라 5.9m 떨어진 링크 카드에서는 기준벽이 서는데도 그 아래 성토부선이
통째로 사라졌다. 성토부선은 기준벽 소유물이므로 inletIn/outletIn으로 판정하고,
다단 벽만 자기 구간값으로 판정한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
다단은 소유 벽 연장에 종속이라 길이·전/후를 따로 못 잡았다. 계곡부·능선부에서는
아래 단일수록 연장이 달라져 자동 규칙으로 못 잡으므로 수동 입력으로 간다.
조정창 UI는 기준벽 것을 그대로 쓴다(길이·기준측점 전·후 세 행).
- Const: EXTRA_SPAN_DEFAULT(10m·5/5) + applySpanPatch()로 길이↔전/후 산식을
기준벽과 공용화
- Station_Controls: StructureSpanControl에 tierValuesFor/updateTier 추가. 값은
세션(b06:extraspan)에 담고 캐시(design.extra_spans)에도 얹는다. 단 수를 줄이면
그 단의 구간값도 지운다
- View_Structure: 다단 키면 단별 제어기로, 기준벽은 종전 경로
- Wire: culvertReach에 단별 연장을 더하고, 링크 카드에서 각 단을 자기 구간값으로
판정한다(tierSpanOf)
- Corridor_Structures: 3D 스윕 범위를 단별 구간값으로
- 저장: CrossSectionPatch.extra_spans(프론트 payload + WallSpanPatch 스키마 +
확정 병합) — [저장]·[확정] 때 정본에 남는다
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
syncCount가 요청 단 수를 세워진 만큼으로 되돌리는데, 안내는 유출측 채널에만
있었다. 집수정 계류측·독립 기슭막이 유입 벽에서는 +를 눌러도 아무 일이 없는
것처럼 보였다. 유출측과 같은 문구·같은 조건(그 벽이 선택된 상태)으로 알린다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
관이 없는 독립 기슭막이인데 벽 역할 키를 따라 "유입/유출 기슭막이"로 적혀
좌·우 제목이 갈렸다. 관 숨김 세트의 기준벽은 좌우 구분 없이 기존 문구
B06_Cross_Revet_Own을 쓴다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
조정창은 한 컴포넌트였지만 행 표시가 역할 키에 묶여 있어, 같은 성토 벽이라도
유입/유출에 따라 창 구성이 갈렸다. 재질 선택지도 3종뿐이라 B05 폼 형태 6종을
조정창에서 고를 수 없었다.
- 단 수 행을 하나로 합치고 자리를 높이 행 아래로 고정한다. 저장 채널(extra /
bextra)은 선택된 벽이 정하고, 등간격(≡)은 유출 채널 전용으로 제한한다
- 형태 선택지를 B05 폼과 같은 6종(REVET_FORMS)으로 바꾼다. 조작값 m의 의미를
재질 코드에서 형태 문자열로 바꾸고, 옛 저장분은 revetFormOf가 옮겨 읽는다
- 높이 한계는 revetHeightLimit(form), 돌 표현은 materialFromForm(form)으로
형태에서 파생한다. 벽 제원 계산은 revetWallSpec()으로 모아 배관·독립·추가가
같은 함수를 쓴다
- 조정창 행 라벨 "재질" → "형태"
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
이관 뒤 독립 기슭막이 벽 역할이 uphill_side로 정해져, 고른 쪽이 상단이면
유입 역할(집수정 후보·placeInletWall·다단 없음), 하단이면 유출 역할
(성토부선+buildExtrasAt)을 타서 좌우 동작이 갈렸다. 새 기하 코드를 만들지
않고 이미 있는 유출 경로 인자를 유입 쪽에도 넘겨 재사용한다.
- Const: revetHeightLimit 콘크리트 4.0 → 5.0 (materialLimit과 통일)
- Geom: hidden_pipe면 basinReason null, 유입 배치에 requireCrossing 전달,
basinExtras 채널을 유입 벽 성토부선·다단으로 사용, inletFill 생략
- Solve: placeInletWall이 requireCrossing을 통과시킨다(기본 false 유지)
- Wire: 링크 카드에서 hidden_pipe면 유입측 다단·성토부선을 벽 연장에 맞춰 유지
- Panel/View_Structure: hiddenPipe deps 추가 — 집수정 형식 숨김, 유입 벽에도
단 수 행 표시
- Culvert: hidden_pipe의 basinFill 툴팁 이름을 "유입부"로
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
값 버튼을 누르면 refreshCard가 카드 DOM을 통째로 갈아끼우므로 조정창도 새로
만들어진다. 새 창의 show()가 기억해 둔 scrollTop을 되살리려 하지만 그 시점에는
카드가 아직 문서에 붙기 전이라 레이아웃이 없어 대입이 무시됐고, 붙은 뒤에는
0(제목 줄)으로 남았다.
restoreScroll()을 두어 대입이 먹지 않았으면 다음 프레임(카드가 붙은 뒤)에 한 번
더 넣는다. 그때 창이 닫혔거나 떨어져 나갔으면 건드리지 않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B06 조정창은 구간값(길이·전/후)을 `{role}_revet_length_m` 등 배관 기슭막이와 같은
키로 pipe_points에 저장한다. 그런데 독립 기슭막이만 백엔드가 전용 키(`length_m`·
`before_m`·`after_m`)를 읽어, 조정창에서 바꾼 값이 저장돼도 매번 원래 값으로
되돌아왔다(실데이터 확인: 234.1m에 두 벌의 키가 따로 존재).
배관과 옵션 키를 한 벌로 합쳐 원천을 하나로 만든다 — 이제 두 기슭막이의 차이는
관 유무(hidden_pipe)뿐이다.
- B06_Section_Engine_Culvert `_revet_side`: `{role}_revet_*` 우선, 없으면 옛 전용
키로 폴백(구 저장분 호환)
- B05_Profile_UI_Drainage_Facility: 독립 기슭막이 폼이 OUTLET_REVET_KEYS를 쓰고
저장 시 inlet_revet_* 에도 같이 싣는다. 읽기는 legacyRevetOptions로 옛 키 폴백
검증: tmp/tests/test_revet_span_source.py 3건 통과, tsc --noEmit 통과,
load_culvert_sets 실데이터로 234.1m 유입 10.5/5/5.5 · 유출 42/21/21 반영 확인.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
백로그 3건(2026-08-28).
① 기슭막이 조정창 — 배관 세트와 같은 4축
- RevetKey 에 "own" 추가. 조작값은 배관 벽과 같은 저장소를 쓴다
(design.revet_adjust["own"] + 세션 b06:revetx:{project}:{route}).
- 벽을 누르면 조정창이 열린다(활성 표시 포함). 좌우 ◀▶ · 사면 ▲▼ · 높이 ± ·
자동 자리 초기화만 보이고 집수정·단 수·구간·연동·재질·등간격 행은 숨는다
(재질은 구조물 옵션 형태가, 단 수는 단 수(다단)가 정본).
- computeRevetmentLayout 이 x(좌우)·d(사면, +는 아래)·h(높이)를 반영하고
3D도 같은 값을 읽는다(코리도 해시에 조작값 포함).
② 초기화가 프로젝트 단위 세션 값도 비운다 — b05:uphill / b06:std-cross.
키에 route_id 가 없어 새 노선에 그대로 되붙던 값이다. 표준횡단 세션 정리는
clearStandardCrossSession() 한 곳에서 한다.
③ 초기화가 주인 없는 코리도 파일을 지운다 — prune_corridor_files(비치명,
삭제 개수 로그). 실측 배경: 유효 route 1개인데 19개(약 85MB)가 남아 있었다.
검증: pytest 245 passed / 7 skipped(코리도 정리 4건 신규). tsc·ruff·prettier 통과.
실측 — 315m 벽 클릭 시 조정창 제목 "기슭막이(독립)", 좌 1회·사면 아래 1회·
높이 +0.1 조작에 벽 좌표가 각각 이동하고 세션에
{"315.00:own":{"x":1,"d":1,"h":1.1}} 저장. 임시 구조물은 삭제하고 정본 복구.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 사용자 확정(자동 배치는 만들지 않는다).
- 자리: 기준(1단)이 맨 위이고 추가 단은 아래로 붙는다. 다음 단은 설계 성토면을
따라 **벽 높이만큼 내려간 자리**에 서고, 성토면 끝을 지나면 자리가 없다.
- 자리가 없으면 배관 기슭막이처럼 토스트로 알린다("3단 중 1단만 세울 수
있습니다 … 기준 올림으로 1단을 올린 뒤 단을 늘리세요"). 같은 측점을 다시
그려도 한 번만 세도록 측점 단위로 집계한다.
- 이동 수단: 레지스트리에 lift_m(기준 올림·사면 위로) / shift_m(좌우) 추가.
배관 세트의 조정창 4축 대신 구조물 옵션으로 받는다 — 조정창 연결은 별건.
- 3D는 단마다 솔리드를 스윕. BUILD_VERSION 88, 구조물 해시에 단 수·이동값 포함.
폐기한 두 시도: ① 배관 세트의 간격 규칙(하단 수평선 +근입 ↔ 전면 경사선) —
벽이 성토면 위에 서면 다음 단 시작점이 곧바로 지반 아래라 3단 요청에 1단만
섰다. ② 성토끝부터 위로 등분 — 기준이 아래가 되어 "기준=상단"과 어긋났다.
검증: pytest 241 passed / 7 skipped. 실측 — 높이 2.0·올림 0 → 1단 + 토스트,
높이 1.0·올림 5m → 315m에서 3단(540.6 → 539.6 → 538.6, 1.0m씩 하강),
3D revet 솔리드가 2D와 같은 수(285:2 · 300:2 · 315:3). 임시 구조물은 삭제하고
정본 복구(구조물 0 · 측점 23).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 사용자 지시(자동 배치는 만들지 않는다).
- 레지스트리에 tiers(단 수, 기본 1 = 단일 벽) 추가. 서버가 스펙에 실어 보낸다.
- 자리 규칙: **성토선 하단(성토면 끝)이 1단**, 단을 늘리면 설계 성토면을 따라
위로 올라가며 그 아래에 단이 들어온다(배관 세트와 다른 점은 관이 없고 시작
자리가 성토선 하단이라는 것). 자리는 성토끝 → 노견 끝을 단 수로 등분해 잡는다.
- 3D는 단마다 솔리드를 스윕한다. BUILD_VERSION 87, 구조물 해시에 단 수 포함.
폐기한 시도: 배관 세트처럼 아래로 쌓기. 벽 상단이 이미 지반선이라 다음 단
시작점이 곧바로 원지반 아래가 되어 "묻힘" 가드에 걸렸다 — 3단 요청에 1단만
생성되는 것을 실측으로 확인하고 방향을 위로 바꿨다.
검증: pytest 241 passed / 7 skipped. 공용 브라우저 실측(285~315m·우측·높이 2.0·
3단) — 카드 3장 × 벽 3개 = 9개, 자리가 성토끝에서 노견 쪽으로 등간격
(315m: -5.42/538.10 → -4.28/538.94 → -3.14/539.77), 벽 하단은 상단 -2.5m
(높이 2.0 + 근입 0.5). 3D revet 솔리드 9개. 임시 데이터는 원래 정본으로 복구
(구조물 0 · 측점 23 · 관 4).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
--color-text-primary 는 이 프로젝트 테마에 없는 이름이라 stroke 가 none 으로
풀려 물넘이 바닥 실선·포장 빗금이 화면에서 통째로 사라졌다(점선만 보였다 —
그쪽은 --color-text-secondary 를 써서 살아 있었다).
확대 캡처를 찍다가 발견했다. 있는 이름(--color-text-body)으로 바꾼다.
검증: 같은 카드를 다시 찍어 바닥 실선·빗금 포장이 나오는 것을 확인.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 사용자 지시 4건.
① 물넘이 파임을 3D 코리도에도 반영
classifyStation 이 노면·노견 조각 표고를 횡단도와 같은 산식(fordDeckElevationAt)
으로 내린다. 노면 밖 비탈은 그대로라 노견 끝에 수직 단차가 선다. BUILD_VERSION 85.
② 표준횡단면 저장분을 포장 재계산에 싣는다
확정 때 종단 정본 options 에 실린 standard_cross_section 을 읽어
enforce_pavement_ranges / attach_default_designs 에 넘긴다. 없으면 config 기본값.
③ 포장 구간 기본값 10 / 5 / 5 (기슭막이와 같은 출발값).
④ 독립 기슭막이
- 측점: is_station_planting_type() 규칙 신설(A군 + D군 구간형). 구간형은
시작·기준·종료에 측점을 심는다 — 길이가 길수록 횡단도가 여러 장 나온다.
구조물 재이관 차단도 같은 규칙을 본다(기타 고스트 방지).
- 서버: B06_Section_Engine_Revetment 신설 — 구간 안 측점 전부에 section.revetment.
- 횡단도: B06_Section_UI_Cross_Revetment 신설 — 벽 상단 = 성토면 끝(설계선-지반
교차점), 아래로 높이 + 근입 0.5m, 전면 1:0.3(교본 7-3). 설치 측은 사용자가
좌/우로 고른다(레지스트리 side 옵션 신설 — 자동 판정 없음).
- 3D: 같은 폴리곤을 기준측점 전/후만큼 스윕. BUILD_VERSION 86 + 구조물 해시에
물넘이·기슭막이 제원 추가.
검증: pytest 241 passed / 7 skipped(신규 4건). 공용 브라우저 실측 —
물넘이 240m 투입 시 3D 노면이 계획고보다 0.38~0.42m 아래(대조군 200m 정상),
기슭막이 225~255 투입 시 측점 23→25·횡단 카드 3장·3D revet 솔리드 3개(31링).
임시 데이터는 원래 정본으로 복구했다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
[초기화] 후 관 지점 횡단(비정규 측점)이 통째로 사라지던 문제. 측점 정본은
longitudinal.json stations + cross_*.json 파일인데, 그 파일을 쓰는
run_section_generation 에는 공급자가 없었다. 유일한 공급자가 프론트
[임시저장]이라 초기화·반폭 재생성이 파일을 다시 쓸 때마다 측점이 날아갔다
(실측: 초기화 직후 cross 23 -> 19, irregular 4 -> 0).
- resolve_extra_stations 신설 — 관 정본(pipe_points.json) + 구조물 정본 A군에서
매번 파생한다. 라벨 형식은 프론트 structureLabel()과 같다. 물넘이포장은
비정규 측점으로 올리지 않는다(2026-08-28 사용자 확정).
- run_section_generation 이 generate_sections 앞에서 이 목록을 심는다. 호출자가
넘긴 extra_stations 는 무시한다 — DB 스냅샷으로 되쓰이는 값이라 되먹이면
B04에서 지운 관이 파일 정본을 이기고 부활한다.
- _load_pipe_anchors -> _load_pipe_points. 계획선 정착과 측점 생성이 같은 한 번의
읽기를 쓴다. 지문 불일치 경고에 버린 건수를 남겨 침묵 실패를 없앤다.
- B06_Section_Router._extra_stations 삭제(자기참조라 늘 빈 값이었다).
706 -> 689줄로 700줄 규칙 위반도 함께 해소.
- 격자와 같은 정수 미터인 비정규 측점은 격자로 스냅한다 — cross_*.json 파일명이
정수 미터라 측점 수와 파일 수가 어긋났다.
- 구조물 이관 필터를 A군 6종 라벨로 넓힌다 — 서버가 심은 세월교·BOX암거 라벨을
"기타"로 재이관하던 고스트를 막는다.
검증: pytest 229 passed / 7 skipped(신규 14건). 공용 브라우저 실측 — 초기화 직후
임시저장 없이 cross 23 / irregular 4(라벨 일치), B06 카드 23장, 반폭 재생성
20->25->20 에도 23 유지, DB cross_sections 23행.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
trimLinkedLayout이 링크(인접) 측점 카드에서 구조물 도형만 지우고, 그 구조물이
만든 설계선 트림(designTrim)과 성토부선은 스프레드로 통과시켰다. 집수정은 조작량이
0이면 대체 사면선(minSlope/maxSlope)도 없어서, 구조물은 안 그려지는데 설계선만
노견 밖에서 끊겼다 — 4측점(80.0m, 관 84.3m)·14측점(280.0m, 관 275.71m) 성토선 삭제.
- keepTrimSides(): 살아남지 못한 쪽의 트림을 ∓Infinity로 되돌리고 그쪽
Elevation·Slope를 뺀다. 양쪽 다 없으면 designTrim = null.
- 집수정은 링크 측점에 서지 않으므로 유입 쪽 트림은 언제나 지운다.
- 연장 밖 유출 벽의 outletFill, 집수정에 딸린 basinFill도 함께 비운다.
- 유입이 min·max 어느 쪽인지는 관 양 끝 offset 대소로 가른다.
3D 무영향: trimLinkedLayout은 export되지 않고 유일한 호출자 computeCardCulvert는
B06 Cross_View만 import한다. B05 코리도는 computeCulvertLayout을 직접 부르며 소유
측점에서만 돈다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
attach_culvert_sets()가 도로 방향 폭(span_m)의 절반을 reach로 잡아 소유 측점
전후 측점에도 같은 스펙을 붙였다. BOX암거는 폭 2.4m라 0.92m 떨어진 옆 측점까지
걸려 횡단도가 한 벌 더 그려졌고, 같은 값을 읽는 3D도 박스 솔리드를 어긋난 채
두 벌 만들었다.
구조물 종류별 연동 표(_SPAN_LINKED_TYPES)를 두고 reach를 분기한다. 지금은 빈
집합이라 세월교·BOX암거 모두 소유 측점 한 곳에만 선다. 3D 스윕 길이는 소유
측점이 span_m으로 직접 내므로 줄지 않는다.
기슭막이 연동은 프론트 culvertOwnerFor 별도 경로라 그대로 살아 있고, 집수정은
이미 링크 카드에서 빠져 있다.
검증(프로젝트 c1bb453f route 85): pytest 208 passed. 횡단도 200.0m 카드에서
box-barrel 사라지고 200.92m에만 남음. 3D 솔리드 42 -> 32, 200.0m 10 -> 0,
200.92m 10 유지, span_m 2.4 불변.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
채움 색이 --color-chart-0(파랑 rgb 91,141,239)이라 관 선 색(rgb 79,142,247)과
겹쳐 관 일부처럼 읽혔다. 사용자 확정에 따라 면 자체를 지운다 — 배수관 세트와
같이 관 위는 비워 둔다.
- FordLayout.fill·FordSideLayout.roadFace와 전용 보조 함수 제거
- .b06-chart__ford-fill 스타일 삭제
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 확정 모델: 초기 계산값은 **원복용으로 그대로 두고**, 사용자가 제어한 수정
1세트가 최종본이다. 여러 세트는 두지 않는다. 두 곳이 이 모델을 어기고 있었다.
① 지운 구조물이 되살아난다 — B05는 그려질 때마다 종단 정본의 비정규 측점을 모아
`/structures/migrate`를 부른다. 서버의 "멱등" 기준이 **지금 그 자리에 구조물이
있는가**여서, 사용자가 지우면 자리가 비고 다음 진입에서 같은 구조물이 다시
생성됐다(진행단계 오버레이로 오가면 매번). 옮긴 자리를 `structures.json`의
`migrated_legacy`에 **이력으로** 남기고, 이력에 있으면 구조물이 없어도 다시 만들지
않는다. 원천(비정규 측점)은 원복용으로 손대지 않는다. 이력은 일반 저장 경로에서도
보존한다 — 사라지면 삭제분이 부활한다.
② 임시저장이 캐시 수정분을 버린다 — B05 [임시저장]이 `cross_patches`를 보내지 않고
`invalidateSectionDetail`로 공유 캐시를 비웠다. B06에서 만져 캐시에 얹힌 구조물
조정(4축·다단·연동·표시 반폭)이 영구저장소에 못 가고 사라졌다. 계획선·비정규 측점
저장 **뒤에** 캐시 수정분을 `saveSections`로 남기고, 그 다음에 캐시를 비운다 —
순서가 뒤바뀌면 재계산이 사용자 수정을 덮는다.
`saveCachedCrossPatches`·`crossPatchesFromCache`는 공유 캐시 모듈에 뒀다 — B05 페이지
파일이 이미 700줄을 넘겨 더 불리지 않기 위함이다(기존 부채).
tsc/ruff/prettier 통과, pytest 200 passed(기존 실패 1건 유지).
신규 검증: tmp/tests/test_b05_structures_migration_history.py 4건.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 보고: 기슭막이 위치(본·연동)를 바꿔도, 추가(다단) 기슭막이를 세워도 3D가
바뀌지 않는다. 형상은 성토선 > 본 기슭막이 > 추가 성토선 > 추가 기슭막이 순이어야
한다. 원인이 둘이었다.
① 조작값이 캐시에 안 실렸다 — 사용자 확정 흐름은 "영구저장소 → 브라우저 캐시 →
3D는 캐시로 플롯, 편집하면 캐시를 갱신하고 그 캐시로 다시 플롯"이다. 그런데
조정창 조작값(4축·다단 단 수·유입 구조물·집수정·연동 플래그)은 sessionStorage에만
있고 확정·임시저장 때만 design에 실렸다. 이제 편집 즉시 `section.design`(B05·B06이
공유하는 같은 객체)에 함께 기록한다. 세션은 리로드 복원용 사본으로 남는다.
② 기하 캐시가 낡았다 — `culvertLayoutOf`의 WeakMap이 측점 객체만 키로 써서, 캐시를
제자리 고치면 옛 기하가 그대로 살아남는다. 입력 요약 해시를 함께 담아 무효화한다.
③ 비탈 실루엣이 순서를 잃었다 — 수집한 점을 편거리로 정렬한 뒤 재추출했다. 기슭막이
앞면이 거의 수직이라 편거리 정렬은 순서를 보장하지 못하고, 다단 계단은 안팎을 오가
뒤섞인다. 유출측은 수집 순서를 그대로 잇고, 추가 성토선과 추가 기슭막이를 번갈아
밀어 계단 순서를 맞춘다. 유입측(집수정·절토)은 범위 밖이라 종전 정렬 유지.
④ 성토 조각 해상도 20 → 48열 — 호길이 균등 재추출이 수직면을 뭉갠다. 열을 꼭짓점에
맞춰 배분하면 측점마다 열 수가 달라 리본이 꼬이므로, 파라미터화는 그대로 두고
해상도로 해결했다. 절토는 20 유지(다단이 붙지 않는다).
성토 물매 불변: 이번 수정은 trimSlope·outletFill 점을 만들지 않는다 — 잇는 순서와
재추출 열 수만 바꿨고, 물매 산출 경로(layout.fillSlope.ratio)는 손대지 않았다.
BUILD_VERSION 17 → 18(저장 코리도 만료).
tsc/prettier 통과, pytest 196 passed(기존 실패 1건 유지).
작업이력·자체검증 기록: docs/raw/PLAN.md §4-10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
기슭막이 범위(길이·기준측점 전/후)를 B06 횡단도 조정창에서 지정하게 하고, 그
범위가 덮는 옆 측점의 기슭막이를 "연동" 파생물로 다루도록 체계를 세웠다.
값의 원천은 배수관 옵션(pipe_points) 하나다 — 같은 숫자를 B06 정본에 따로 담으면
B05 배수관 카드와 갈려 어느 쪽이 참인지 알 수 없다.
① 집수정 전/후 필드 신설 — inlet_basin_before_m·after_m(기본 각 1m), 길이 2m 유지.
종방향 구간 계산을 revetSpanOfSpec·basinSpanOfSpec 한 곳으로 모아 2D 링크 판정과
3D 스윕이 같은 값을 쓰게 했다.
② B06 조정창에 길이·전·후 행 — 기슭막이(유입·유출)·집수정 모두. 캐시를 고쳐 즉시
보여주고 저장은 조작이 멎으면 한 번만 나간다(세부유역 재계산이 매번 돌지 않게).
길이를 바꾸면 늘어난 몫만 지금 비율대로 나눠 담는다 — 총길이에서 비율로 다시
계산하면 0.1m 반올림이 쌓여 5.0/5.0이 5.6/5.4로 어긋난다.
③ 링크 카드 선택 허용 — "소유 측점 통째 복사"에서 "위치 4축만 물려받고 원지반
접합은 이 측점 지형으로 재계산"으로 바꿨다. 구조물 형식은 언제나 소유 측점 값이다.
손대기 전에도 어긋나지 않게 소유 측점이 실제로 적용한 4축(revetShift)을 명시값으로
물려준다.
④ 연동(측점별)·경사(전체 공통) 토글 — 연동 조작은 소유 측점 키에 실리고 그 연장이
덮는 측점을 함께 다시 그린다. 경사는 3D 전용 스위치다(끄면 스윕 프레임 dz=0).
횡단도까지 -dz로 밀면 노견에서 출발하는 성토선까지 내려가 노견과 벌어진다.
⑤ 가림 구간 선 정리 — 유입부 접속선과 유출부 접지 후 절토선을 2D·3D 모두에서
빼되 기하는 남긴다. 면적·수량 계산은 건드리지 않았다(전면 개편 대상).
⑥ 집수정은 소유 측점 횡단도 하나에만 — 링크 카드에서 집수정·계류측 다단을 뺀다.
⑦ 3D 구간 겹침 정리 — 측점 간격이 구조물 연장보다 좁으면 두 구간 제어점이 번갈아
서서 짧은 쪽(집수정 2m)이 잘려 보였다. 두 소유 측점의 중간에서 끊는다.
⑧ 조정창 정리 — 십자·9키를 맨 아래로, 창을 위쪽 기준 오버레이로(낮은 횡단도에서
제목이 잘리던 문제), 연동·경사를 1행 토글 버튼으로, 스크롤 자리 유지.
파일 분리: B06_Section_UI_Cross_View_Structure.ts(조정창 배선),
B06_Section_Api_Culvert_Options.ts(구간값 저장) — 700줄 제한.
BUILD_VERSION 14 → 17(저장 코리도 만료).
tsc/ruff/prettier 통과, pytest 196 passed(기존 실패 1건 유지).
작업이력·자체검증 기록: docs/raw/PLAN.md §4-9.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3D 예상형상은 종단·횡단 확정 뒤의 최종 산출물이고 3D 쪽 편집 경로는 없다
(2026-08-24 사용자 확정). 그런데 구조물(비정규) 측점은 횡단 정본 자체가 없어
조회할 때마다 프리뷰 기본값이 재계산됐고, 조정창 조작값은 세션에서 사라졌다.
정본 경로를 열고, 그 위에서 코리도 단차를 걷어냈다.
① 구조물 측점 정본화 — 확정·임시저장이 종단 정본 stations 중 DB 행이 없는
측점을 대상에 넣어 upsert. cross_sections 19 → 23행.
② 조정창 조작값 정본화 — 기슭막이 4축(x·d·h·m)·다단 단 수를 design.revet_adjust
/ extra_wall_counts로 저장하고, 3D culvertLayoutOf가 그 값을 기하 입력으로 쓴다.
카드 우선순위는 세션 → 정본 → 자동.
③ 노폭 연장의 노견 편입 — PIECE_COLS.shoulder 2→3으로 원 노견 끝 꺾임을 남기고,
조기 return을 없애 확폭이 구조물 구간 내내 유지되게 했다.
④ 종단 기울기 반영 — 구조물 구간 복사 단면에 계획고 차이를 전량 얹는다.
노견 표고 단차 0.49~0.61m 제거.
⑤ 접속 테이퍼 — 구간 경계를 제어점으로 세워 "복사 vs 보간" 두 규칙을 하나로
통합. 노폭 3.0m·성토비탈 8.1m 단차 제거. 접속 길이 = 인접 측점까지.
제어점 생성은 B05_Profile_UI_Corridor_Frames.ts로 분리(Build 699 → 635줄).
⑥ 인접 측점 링크 — 기슭막이 연장(기본 10m)이 덮는 옆 측점 횡단도에도 소유 측점
단면을 계획고 차이만 얹어 그린다. 요소별 연장 판정(관·집수정 제외), 표시 전용.
BUILD_VERSION 11 → 14 (저장 코리도 만료).
tsc/ruff/prettier 통과, pytest 192 passed (기존 실패 1건 유지).
작업이력·자체검증 기록: docs/raw/PLAN.md §4-8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>