fix(467 B1 덧 같은 자리): 노선 초안 없는 차례 = 같은 자리 앞 횡단(없으면 그 자리 옛 횡단) 기하 · 표본 그대로

- 격자 측점에 구조물을 놓고 빼면 열쇠가 바뀌어 브라우저 표본으로 갈렸음(검증_sub5 440 m · 소단 놓고 뺌 = 누가토량 0.04 ㎥ 갈림)
- 시험 test_272_swap_keeps_canon 에 격자 구조물 놓고 뺌 = 정본 표본 · 기하

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 07:26:10 +09:00
co-authored by Claude Opus 5.5
parent d28f1e8584
commit 1f00e13852
3 changed files with 52 additions and 21 deletions
+31 -21
View File
@@ -6,8 +6,9 @@
* 받음 — 설계 · 구조물 제원 · 이름 그대로 · 자리(누가거리 · 좌표 · 방위 · 확폭)만 새것(433-4).
* · 지반 표본 · 상단측 · 중심고 = 자리 · 축이 같은 앞 횡단이면 앞 값(서버 샘플링 정본 · 덧 측점만 넣어도 나머지
* 측점 면적이 브라우저 지형으로 안 바뀜 · 433-11) · 새 측점 · 옮긴 측점만 지반 표본(채워 왔으면).
* · 노선 초안 없는 차례(관 · 구조물 · 추가 측점만) = 같은 열쇠 · 누가거리 0.01 m 안 앞 횡단의 기하 · 표본 그대로
* (축 허용 1e-3 · 브라우저 측점 틀 ↔ 서버 틀 mm 차가 정본 표본을 안 덮음 · 467 · 요구 272).
* · 노선 초안 없는 차례(관 · 구조물 · 추가 측점만) = 같은 자리(누가거리 · 좌표 0.01 m · 축 1e-3) 앞 횡단 — 없으면
* 그 자리 옛 횡단(격자에 구조물을 놓고 뺌) — 의 기하 · 표본 그대로(브라우저 측점 틀 ↔ 서버 틀 mm 차가 정본 표본을
* 안 덮음 · 467 · 요구 272) · 설계는 지금 규칙.
* · 새 측점 = 지반 표본 + 기본 설계(계획고 0 → 낡음 → 브라우저 다시 셈이 곧 채움).
* · 상단측 = 새 지반 표본으로 다시 셈한 측점은 다시(단면 유형 기본값 재료 · 서버 횡단 파일 `uphill_side` 짝 · 428).
* · 0.5 m 넘게 옮긴 관 · 구조물 측점 = 기본 설계 + 사용자 칸(만진 칸 · 지반 등 입력 `ROW_CARRY_KEYS`) — 서버가
@@ -215,12 +216,15 @@ export function swapCrossSections(
moved = true;
if (prior && text(prior.chainage_m) !== text(station.chainageM))
moves.set(text(prior.chainage_m), text(station.chainageM));
// 노선 초안 없는 차례 · 같은 열쇠 · 같은 자리 = 앞 횡단 기하 · 표본 그대로(467 · 되돌리면 정본과 같음)
const canon =
!routeDraft &&
!!prior &&
byKey.get(station.key) === prior &&
samePlace(prior, station, CANON_AXIS_TOL);
// 노선 초안 없는 차례 · 같은 자리 = 앞 횡단(없으면 그 자리 옛 횡단 · 격자에 구조물을 놓고 뺌) 기하 · 표본 그대로
// (467 · 되돌리면 정본과 같음)
const atPlace = (section: CrossSection | undefined): section is CrossSection =>
!!section?.samples?.length && samePlace(section, station, CANON_AXIS_TOL);
const canon = routeDraft
? undefined
: atPlace(prior)
? prior
: detail.cross_sections.find(atPlace);
// 자리 같은 앞 횡단 = 서버 표본 그대로(433-11) · 아니면 새 지반 표본(채워 왔으면)
const fresh =
!canon && !!station.ground && !(prior?.samples?.length && samePlace(prior, station));
@@ -243,18 +247,20 @@ export function swapCrossSections(
label: formatStation(station.chainageM),
kind,
...(onGrid ? { grid: true } : {}),
center_z: fresh
? (station.ground?.centerZ ?? prior?.center_z ?? null)
: (prior?.center_z ?? null),
...(canon && prior
center_z: canon
? canon.center_z
: fresh
? (station.ground?.centerZ ?? prior?.center_z ?? null)
: (prior?.center_z ?? null),
...(canon
? {
azimuth_deg: prior.azimuth_deg,
plan_radius_m: prior.plan_radius_m,
curve_outer_side: prior.curve_outer_side,
curve_widening_m: prior.curve_widening_m,
center_x: prior.center_x,
center_y: prior.center_y,
frame: prior.frame,
azimuth_deg: canon.azimuth_deg,
plan_radius_m: canon.plan_radius_m,
curve_outer_side: canon.curve_outer_side,
curve_widening_m: canon.curve_widening_m,
center_x: canon.center_x,
center_y: canon.center_y,
frame: canon.frame,
}
: {
azimuth_deg: station.azimuthDeg,
@@ -267,8 +273,12 @@ export function swapCrossSections(
}),
};
longStations.push({ ...place, ...(prior?.structure ? { structure: prior.structure } : {}) });
const samples = fresh ? groundSamples(station) : (prior?.samples ?? []);
const uphill = fresh ? uphillSide(station) : (prior?.uphill_side ?? null);
const samples = canon ? canon.samples : fresh ? groundSamples(station) : (prior?.samples ?? []);
const uphill = canon
? (canon.uphill_side ?? null)
: fresh
? uphillSide(station)
: (prior?.uphill_side ?? null);
if (!prior) return { ...place, uphill_side: uphill, samples, design: freshDesign(uphill) };
const reseed =
(station.kind === "pipe" || station.kind === "structure") &&
@@ -169,6 +169,20 @@ const out = {};
};
}
/* 1-2. 격자 측점(40 m)에 구조물을 놓고 뺌 — 열쇠가 바뀌어도 같은 자리 = 정본 표본 · 기하 */
{
const onGrid = stationsAt(45).map((s) =>
s.key === "grid:2" ? { ...s, key: "structure:s1", kind: "structure", id: "s1" } : s,
);
const placed = m.swapCrossSections(canonDetail(), onGrid, KEYS, 20, false);
const removed = m.swapCrossSections(placed.detail, stationsAt(45), keysOf(onGrid), 20, false);
out.gridStructure = {
placed: report(placed.detail)["40.00"],
removed: report(removed.detail)["40.00"],
removedBytes: bytesSame(removed.detail, [45, 40]),
};
}
/* 2. 가게 길(swapSectionStations) — 가게 routeDraft 로 가름: 없음 · 있음 · 초안 버린 직후 */
const seed = (P, R) => {
m.writeState("section-detail", canonDetail(), P, R);
@@ -82,6 +82,13 @@ def test_되돌림_정본과_바이트_같음(out):
assert back["45.00"]["browser"] # 관 측점 = 브라우저 표본(정본은 저장 뒤 서버 값)
def test_격자에_구조물_놓고_뺌_같은_자리_정본(out):
g = out["gridStructure"]
assert g["placed"]["same"] and not g["placed"]["browser"] # 열쇠 바뀜(구조물) · 같은 자리 = 정본
assert g["removed"]["same"] and not g["removed"]["browser"] # 다시 격자 = 정본 표본 · 기하
assert g["removedBytes"]
def test_노선_초안_차례는_옛_규칙(out):
draft = out["pure"]["draft"]
for at in CURVES: # 축 차 > 1e-4 = 새 표본