test(structure): 65 화면 확인 스크립트 — 세월교 · 박스 · 물넘이 좌측 패널

화면 확인(검증_sub5 · ORCA):
- 세월교 1 = 관경 → 수량(련) → 폭 → 높이 · 유입 흙막이 = 찰 10 · 1.5 · 5 · 5 + 옵션 줄 · 유출 없음 = 램프 3→2 · 잇기 줄 없음
- 추가 흙막이 · 도수로 상자 · [저장] → 파일 inlet_revet_* · soil_add_1_* · chute_add_1 · 다시 열어 같음 · 램프 4
- B06 측벽 고름 = 유입 상자 강조 · 좌측 측벽 높이 · 좌우 · 상하 · 바닥판 줄 0
- 새 세월교 = 1000 · 련 2 · 폭 10 · 높이 0.04(유량 뒤 자동) · 배관 → 세월교 = 높이 0.08 · 새 물넘이 = 경사 3 · 폭 5 · 높이 0.01
- 세월교 → 박스 = 2.0×2.0 · 날개벽 · 유출 없음 = 램프 3→2
- 검증 파일 · 세션 캐시는 미리 뜬 사본으로 되돌림

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
2026-10-06 23:22:20 +09:00
co-authored by Claude Opus 5.5
parent 4764977f9b
commit a503683b9b
@@ -0,0 +1,93 @@
/* 65 — B05 · B06 좌측 「구조물 배치」 세월교 · 박스암거 · 물넘이 포장(PLAN 65-1 ~ 65-5)
* 시작 화면: B05 종단설계(#/b05-profile) · 검증 프로젝트(검증_sub5) · 목록에 「세월교 1」
* 부르는 법: orca eval --expression "window.__m65='단계'; $(cat 이 파일)"
* shape — 세월교 1 고름 → 상자 · 칸 · 램프 수
* edit — 유입 흙막이(기본값 칸) · 유출 없음(램프 줄어듦) · 추가 흙막이 · 도수로 · [저장]
* verify — 다시 연 값 · 램프 수
* newford — 새 세월교(관경 · 련 · 폭 · 높이 자동) · 새 물넘이(경사각 3 · 폭 · 높이) — 저장 안 함
* 되돌림: 검증 프로젝트 파일을 미리 떠 둔 사본으로(스크립트 밖)
*/
(async () => {
const MODE = window.__m65 || "shape";
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const until = async (fn, ms = 20000) => {
for (let t = 0; t < ms; t += 200) {
if (fn()) return true;
await w(200);
}
return false;
};
const spec = () =>
[...document.querySelectorAll(".b05-structure__spec")].find((n) => n.offsetParent !== null);
const box = (c, i = 0) => spec()?.querySelectorAll(`[data-spec-container="${c}"]`)[i];
const ctl = (c, item, i = 0) =>
box(c, i)?.querySelector(`[data-spec-item="${c}.${item}"]`)?.querySelector("select, input");
const set = async (c, item, v, i = 0) => {
const el = ctl(c, item, i);
el.value = v;
el.dispatchEvent(new Event("change", { bubbles: true }));
await w(300);
};
const rows = () =>
[...document.querySelectorAll(".b05-route__irregular-list li")].filter((l) => l.offsetParent);
const row = (re) => rows().find((l) => re.test(l.textContent.trim()));
const lamps = (re) => row(re)?.querySelectorAll(".b05-route__lamps i").length ?? -1;
const pick = async (re) => {
await until(() => !!row(re));
const li = row(re);
(li.querySelector("button") ?? li).click();
await w(800);
await until(() => !!spec());
await w(400);
};
const shape = () =>
[...spec().querySelectorAll("[data-spec-container]")].map((b) => ({
c: b.dataset.specContainer,
legend: b.querySelector("legend")?.firstChild?.textContent,
items: [...b.querySelectorAll("[data-spec-item]")]
.filter((n) => !n.hidden)
.map((n) => [n.dataset.specItem, n.querySelector("select, input")?.value ?? ""]),
fields: [...b.querySelectorAll(".b05-route__field")]
.filter((n) => !n.hidden && n.offsetParent)
.map((n) => n.querySelector("span")?.textContent),
link: !!b.querySelector("[data-spec-link]"),
option: [...b.querySelectorAll(".b05-structure__half-pair button")].map(
(x) => `${x.textContent}${x.disabled ? "(끔)" : ""}`,
),
}));
const out = { mode: MODE };
const FORD = /세월교 1$/;
if (MODE === "shape") {
await pick(FORD);
out.shape = shape();
out.lamps = lamps(FORD);
}
if (MODE === "edit") {
await pick(FORD);
out.lampsBefore = lamps(FORD);
await set("inlet", "structure", "흙막이");
out.inlet = shape().find((b) => b.c === "inlet");
await set("outlet", "structure", "없음");
await w(800);
out.outlet = shape().find((b) => b.c === "outlet");
out.lampsAfterNone = lamps(FORD);
for (const c of ["soil_add", "chute_add"]) {
spec().querySelector(`[data-spec-add="${c}"]`)?.click();
await w(400);
}
await set("chute_add", "structure", "도수로");
await set("inlet", "length", "8");
out.lampsAfterAdd = lamps(FORD);
out.edited = shape();
document.querySelector('[data-dock-action="save"]').click();
await w(500);
out.saved = await until(() => !document.querySelector(".ui-overlay.is-active"), 60000);
}
if (MODE === "verify") {
await pick(FORD);
out.reopened = shape();
out.lamps = lamps(FORD);
}
return JSON.stringify(out);
})();