From 0fb39076b89b5fd64ace828668ff6c7227a741b9 Mon Sep 17 00:00:00 2001 From: ypshim Date: Tue, 10 Nov 2026 15:30:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(shape):=20=ED=9A=A1=EB=B0=A9=ED=96=A5=20?= =?UTF-8?q?=EB=8B=A8=EC=B0=A8=20=EC=BD=94=ED=95=91=EC=9D=84=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EC=A0=84=EC=97=B4=C2=B7=ED=9B=84=EC=97=B4=20?= =?UTF-8?q?=EC=AA=BD=EC=97=90=20=EB=82=98=EB=88=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 코핑 두 쪽(전열 상면 · 후열 상면)을 늘 축의 (−) 쪽 = 전열로 두어, 전열이 (+) 쪽에 있는 교각(P1, 산외교 P11)에서는 전열 블럭이 후열 상면에 묻히고 후열 블럭이 떠 있었다. 각 쪽을 그 열 받침의 평균 n 쪽에 둔다 (순서는 [전열, 후열] 유지). 기존 테스트가 틀린 쪽을 기대하던 것을 고치고, 전열이 어느 쪽이든 블럭이 아래 코핑 상면에 놓이는지 검사한다. Co-Authored-By: Claude Haiku 5.5 --- src/core/geometry.ts | 7 +++++-- tests/validate-and-foundation.test.ts | 28 +++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/core/geometry.ts b/src/core/geometry.ts index 6413581..01c5271 100644 --- a/src/core/geometry.ts +++ b/src/core/geometry.ts @@ -404,9 +404,12 @@ export function buildPier(input: PierInput, ctx: PierContext = {}): PierModel { ? (meanN(fRows) + meanN(bRows)) / 2 : (meanN(frontPlaced) + meanN(backPlaced)) / 2; if (lateralStep) { + // The front row may lie on either side of the axis (it depends on the IP1 → IP3 direction), so each part takes the + // side of its own row; the order stays [front, back]. + const frontLow = (fRows.length && bRows.length ? meanN(fRows) - meanN(bRows) : meanN(frontPlaced) - meanN(backPlaced)) <= 0; copingParts.push( - { k, nFrom: -planHalfB, nTo: nStep, profile: shifted(outline(segments), base[k]) }, - { k, nFrom: nStep, nTo: planHalfB, profile: shifted(outline(backSegments), base[k]) }, + { k, nFrom: frontLow ? -planHalfB : nStep, nTo: frontLow ? nStep : planHalfB, profile: shifted(outline(segments), base[k]) }, + { k, nFrom: frontLow ? nStep : -planHalfB, nTo: frontLow ? planHalfB : nStep, profile: shifted(outline(backSegments), base[k]) }, ); } else { copingParts.push({ k, nFrom: -planHalfB, nTo: planHalfB, profile: shifted(outline(segments), base[k]) }); diff --git a/tests/validate-and-foundation.test.ts b/tests/validate-and-foundation.test.ts index e5c79b5..5559103 100644 --- a/tests/validate-and-foundation.test.ts +++ b/tests/validate-and-foundation.test.ts @@ -219,10 +219,12 @@ describe('횡방향 단차 (spec §4.3, Q1·Q2 추천안)', () => { const m = buildPier({ ...p1, bearings }); expect(m.copingParts).toHaveLength(2); const [front, back] = m.copingParts; - expect(front.nTo).toBeCloseTo(back.nFrom, 9); - expect(front.nFrom).toBeCloseTo(-m.copingB / 2, 9); - expect(back.nTo).toBeCloseTo(m.copingB / 2, 9); - expect(front.nTo).toBeCloseTo((meanN(m, '전열') + meanN(m, '후열')) / 2, 9); + // P1's front row is on the + side of the axis (n ≈ +0.70, back ≈ −0.60), so the front part is the + half (2026-11-10 fix). + expect(meanN(m, '전열')).toBeGreaterThan(meanN(m, '후열')); + expect(front.nFrom).toBeCloseTo(back.nTo, 9); + expect(front.nTo).toBeCloseTo(m.copingB / 2, 9); + expect(back.nFrom).toBeCloseTo(-m.copingB / 2, 9); + expect(front.nFrom).toBeCloseTo((meanN(m, '전열') + meanN(m, '후열')) / 2, 9); // Segment 2 (bearing no. 7) is the one raised by 20 mm on the back row only. const topAt = (part: typeof front, s: number) => part.profile.find(([ps, pz]) => Math.abs(ps - s) < 1e-9 && pz > m.levelBottom)![1]; const s2 = m.segments[2].s0; @@ -235,6 +237,24 @@ describe('횡방향 단차 (spec §4.3, Q1·Q2 추천안)', () => { expect(buildPier({ ...p1, bearings }).copingParts).toHaveLength(2); }); + it('each block sits on the coping part under it, whichever side of the axis the front row is on (2026-11-10)', () => { + const p1 = loadP1(); + const raise = (b: (typeof p1.bearings)[number]) => (b.row === '후열' && b.no === 7 ? { ...b, z: b.z + 0.3, seatEl: b.seatEl! + 0.3 } : b); + const swapRows = (b: (typeof p1.bearings)[number]) => ({ ...b, row: (b.row === '전열' ? '후열' : '전열') as typeof b.row }); + // P1 as given (front on +n) and with the row labels swapped (front on −n). + for (const bearings of [p1.bearings.map(raise), p1.bearings.map(raise).map(swapRows)]) { + const m = buildPier({ ...p1, bearings }); + expect(m.copingParts).toHaveLength(2); + for (const b of m.blocks) { + const part = m.copingParts.find((q) => q.k === b.k && b.n >= q.nFrom && b.n <= q.nTo)!; + // The last top point at or before s on the stepped outline is the step top under the block. + const before = part.profile.slice(0, part.profile.findIndex(([ps]) => ps > b.s + 1e-9)); + const top = before.filter(([, pz]) => pz > m.endBottom + 1e-9).pop()![1]; + expect(b.bottom).toBeCloseTo(top, 6); + } + } + }); + it('a 3 mm difference is below the 5 mm tolerance: no step', () => { const p1 = loadP1(); const bearings = p1.bearings.map((b) => (b.row === '후열' && b.no === 7 ? { ...b, z: b.z + 0.003 } : b));