feat(475-3 요구 275-3): 노선이 바뀌면 자리 칸(암 경계 · 2단 경사 · 손 상단측)은 같은 자리 측점만 넘김 — 새 자리 = 기본값

- 자리 칸 목록 한 벌 = TS `SEAT_KEYS`(Station_Swap · 파이썬 Router_Design 이 그 글을 읽음) — two_stage_slope · rock_boundary_offset_m · cut_slope_ratio_user · section_mode · ditch_side
- 자리 판정 한 벌 = `sameSeat` ↔ 파이썬 `same_seat`(중심 0.01 m · 왼쪽 축 1e-3 · 모르면 같은 자리 = 옛 동작)
- 끌기 세션 넘김 · Node stations.carry = `carryKeyedStates`(목록 측점 자리 · 값마다 쓴 때 자리 `@seat` — 되돌리기로 되살린 값도 제 자리로)
- 미리보기 = `pipelineCrossSource` 가 저장분 · 세션 자리 칸에 자리째 실음 → `crossAtSeat` 가 새 자리면 뺌
- B06 갈아끼움(노선 초안) = 자리 바뀐 측점 설계의 자리 칸 걷고 기본값(`unseated` · 브레인 덧)
- 서버 노선 저장 = `carry_cross_inputs` 자리 칸 같은 자리만 · 손 단면 유형 · 측구 방향 = `compute_default_designs` 입력
- 옛 단언 시험 고침(자리 모름 목록 = 같은 자리 = 손 상단측도 넘김 · 「@seat」 칸 거름) · 새 시험 test_475_3_seat_carry
- 위키 rules 넘김 규칙 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 14:29:38 +09:00
co-authored by Claude Opus 5.5
parent 11b307d1ca
commit a02367e615
23 changed files with 729 additions and 72 deletions
+32
View File
@@ -107,3 +107,35 @@ def curve_moves(before: list[tuple[str, float]], after: list[tuple[str, float]])
if best >= 0:
out[key] = free.pop(best)[0]
return out
#: 같은 자리로 보는 중심 차(m) · 축 차 — 브라우저 틀 ↔ 서버 틀 차(467)를 넘김.
SEAT_XY_M = 0.01
SEAT_AXIS = 1e-3
def station_seat(station: dict | None) -> tuple[float, float, tuple[float, float] | None] | None:
"""종단 측점 줄(``center_x`` · ``center_y`` · ``frame.left_xy``) → 자리 · 모르면 None."""
if not isinstance(station, dict):
return None
try:
x, y = float(station["center_x"]), float(station["center_y"])
except (KeyError, TypeError, ValueError):
return None
left = (station.get("frame") or {}).get("left_xy")
axis = (float(left[0]), float(left[1])) if isinstance(left, list) and len(left) == 2 else None
return x, y, axis
def same_seat(a, b) -> bool:
"""노선이 바뀐 뒤 그 측점이 같은 자리인가(요구 275-3) — 짝 ``sameSeat`` · 거울 475-3.
자리 = (x, y, 왼쪽 단위 벡터 | None) · 한쪽을 모르면 같은 자리로 봄(옛 동작).
"""
if a is None or b is None:
return True
if math.hypot(a[0] - b[0], a[1] - b[1]) > SEAT_XY_M:
return False
if a[2] is None or b[2] is None:
return True
return abs(a[2][0] - b[2][0]) <= SEAT_AXIS and abs(a[2][1] - b[2][1]) <= SEAT_AXIS
+27
View File
@@ -97,3 +97,30 @@ export function curveMoves(
}
return out;
}
/** 측점 자리 — 중심 좌표 · 왼쪽 단위 벡터(횡단 축 · 없으면 좌표만 봄). */
export interface StationSeat {
x: number;
y: number;
left?: readonly [number, number] | null;
}
/** 같은 자리로 보는 중심 차(m) · 축 차 — 브라우저 틀 ↔ 서버 틀 차(467)를 넘김. */
export const SEAT_XY_M = 0.01;
export const SEAT_AXIS = 1e-3;
/**
* 노선이 바뀐 뒤 그 측점이 같은 자리인가(요구 275-3) — 자리 · 축이 같으면 지형이 같아 「자리 칸」(암 경계 · 2단
* 경사 · 손 상단측)을 들고 감 · 한쪽 자리를 모르면 같은 자리로 봄(옛 목록 · 옛 동작) · 짝 `same_seat` ·
* 거울 시험 `test_475_3_seat_carry.py`.
*/
export function sameSeat(
a: StationSeat | null | undefined,
b: StationSeat | null | undefined,
): boolean {
if (!a || !b) return true;
if (Math.hypot(a.x - b.x, a.y - b.y) > SEAT_XY_M) return false;
if (!a.left || !b.left) return true;
return (
Math.abs(a.left[0] - b.left[0]) <= SEAT_AXIS && Math.abs(a.left[1] - b.left[1]) <= SEAT_AXIS
);
}