feat(B06): 다단 기슭막이 구간값을 단별로 손수 잡는다

다단은 소유 벽 연장에 종속이라 길이·전/후를 따로 못 잡았다. 계곡부·능선부에서는
아래 단일수록 연장이 달라져 자동 규칙으로 못 잡으므로 수동 입력으로 간다.
조정창 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>
This commit is contained in:
2026-08-29 10:54:03 +09:00
co-authored by Claude Opus 5
parent 35547e551d
commit 1e55322f2f
11 changed files with 245 additions and 36 deletions
@@ -22,9 +22,13 @@ import type {
StructureSpanControl,
} from "./B06_Section_UI_Cross_Culvert_Wire";
/** 다단(추가) 벽 키인가 — 유출측 `extra0…`·유입측 `bextra0…`. */
export const isTierKey = (key: RevetKey): boolean =>
key.startsWith("extra") || key.startsWith("bextra");
/**
* 선택된 벽이 구간값을 갖는 요소인지 — 유입·유출 기슭막이와 유입 집수정만 자기
* 길이·전/후를 갖는다. 다단(extra·bextra)은 소유 벽 연장을 그대로 따른다.
* 선택된 벽이 **기준벽 구간값**을 갖는 요소인지 — 유입·유출 기슭막이와 유입 집수정.
* 다단(extra·bextra)은 자기 단별 구간값을 따로 갖는다(`isTierKey` — 2026-08-29).
*/
export function spanRoleOf(key: RevetKey, inletIsBasin = false): SpanRole | null {
if (key === "inlet") return inletIsBasin ? "basin" : "inlet";
@@ -163,13 +167,19 @@ export function structurePanelDeps(ctx: StructurePanelContext): StructurePanelDe
ctx.extraWalls?.setCount(ctx.adjustChainage(), count, "basin");
},
equalizeExtras: () => ctx.extraWalls?.equalize(ctx.adjustChainage()),
// 구간값(길이·전·후) — 다단은 소유 벽 연장을 따르는 파생물이라 대상이 아니다.
// 구간값(길이·전·후). 기준벽은 배관 옵션 정본, **다단은 단별 세션 값**이다
// (2026-08-29 사용자 — 계곡부·능선부에서 단마다 연장이 달라 수동으로 넣는다).
// 링크 카드에서 만져도 제어기가 소유 측점 값을 고친다(원천은 하나).
spanFor: (key) => {
if (isTierKey(key)) return ctx.structureSpan?.tierValuesFor(ctx.section, key) ?? null;
const role = spanRoleOf(key, ctx.inletIsBasin());
return role && ctx.structureSpan ? ctx.structureSpan.valuesFor(ctx.section, role) : null;
},
setSpan: (key, patch) => {
if (isTierKey(key)) {
ctx.structureSpan?.updateTier(ctx.section, key, patch);
return;
}
const role = spanRoleOf(key, ctx.inletIsBasin());
if (role) ctx.structureSpan?.update(ctx.section, role, patch);
},