# 파괴 모드 6판 구현 계획 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. 진행 체크와 검증 결과는 이 문서와 `.superpowers/sdd/2026-10-30-destruction-six/progress.md`에 기록한다. **Goal:** 분쇄기·전동톱·망치 각 2판을 201~206에 연결하고, 영구적인 입체 파괴·전량 제거·시간별 별을 검증한다. **Architecture:** 순수 입체 격자가 남은 재료와 영구 손상을 소유하고, Three.js 장면이 동일한 격자에서 매끈한 표면과 분리 조각을 만든다. `DestructionMode`가 세 도구를 정의의 `kind`로 구분하며 기존 모드·입력·진행·결과 훅에 연결한다. 무거운 표면 갱신은 입력 이벤트 밖에서 제한된 주기로 수행하고, 접촉 변형·도구·입자는 매 프레임 갱신한다. **Tech Stack:** 기존 TypeScript·Three.js 0.186·WebGL2·Vite·Vitest·Playwright. 새 의존성·외부 이미지·음원 없음. **Spec:** `docs/superpowers/specs/2026-10-30-destruction-six-design.md` — 사용자 승인 완료. ## Global Constraints - 전량 제거가 클리어 조건. 최초 유효 접촉부터 흐르는 게임 시간으로 별을 정한다. 첫 판 20/30/45초, 둘째 판 25/40/60초를 초기값으로 쓰고 실제 입력 결과로 조정한다. - 전체 시각 검수: 390×844·1280×800에서 기본 48장과 대표 동작 연속 캡처. 검수 주체는 구현 세션과 `gpt-6.1-sol / medium` 독립 리뷰어. - 원본 `PieceData.polygon`·기존 200판 ID·기록·승인 오디오 규칙을 유지한다. 새 도구 입력은 `GameMode` 훅으로 연결한다. - 제품 문구 영어, 문서·주석·커밋 한국어. Android 에뮬레이터 금지, 이번 기본 검증은 웹이며 실기기 검증과 구분한다. - 기존 `node_modules` 연결과 기준 검사 완료: 185파일·3,055개, 빌드 통과. 작업 공간은 이미 격리된 관리형 worktree다. ## Review Focus - 마지막 잔여물: 보이지 않거나 도구가 닿지 않는 내부 재료 때문에 완료가 불가능해지지 않는가 → Task 1 제거 전수 검사·Task 3 실제 완주. - 입력 중단: 숨김·Home·Retry·컨텍스트 손실 뒤 보이지 않는 톱질·연타·시간 누적이 남지 않는가 → Task 3 취소 E2E. - 실제 파손: 입자와 본체가 따로 놀거나 분리된 덩어리가 원본에 남지 않는가 → Task 2 격자와 메시 계약·Task 4 동작 확인. - 화면 투영: 사선 카메라·모바일·깊은 홈에서도 도구 위치와 맞는 부위가 공격되는가 → Task 3 raycast·Task 4 대표 포인터 캡처. - 자원 수명: 반복 판 전환·재시작·컨텍스트 복구 뒤 메시·재질·도구 자원이 증가하지 않는가 → Task 3/4 수명·메모리 확인. ## 파일과 공통 인터페이스 - `src/stages/Destruction.ts`: `DestructionKind = 'shredder' | 'chainsaw' | 'hammer'`, `DestructionDefinition` (kind, shape: 'block'|'oval'|'dome', width, depth, height, colors: readonly number[], starTimes: readonly [number,number,number]). 정의는 실제 전체 폭·깊이를 사용한다. - `src/geometry/DestructionVolume.ts`: Three.js 없는 입체 재료 격자. `Point3 {x:number;y:number;z:number}`, `VolumeChange {removed:number; points:Point3[]; detached:number[][]}`. `DestructionVolume(def)`은 `resolution=32`, `span=2.6`, `origin={x:-1.3,y:-0.35,z:-1.3}`, `field:Float32Array`(0/1 재료), `version`, `initialCount`, `remainingCount`를 소유한다. `point(index):Point3`, `carve(from:Point3,to:Point3,radius:number,damage:number):VolumeChange`, `detach():VolumeChange`, `topAt(x:number,z:number):number|null`, `remainingRatio:number`. 격자 점 좌표는 `origin + i/resolution*span`이며 Three.js MarchingCubes 좌표와 일치시킨다. 도구 접촉에 누적되는 damage는 별도 상태로 관리하고 살아 있는 재료는 field=1이다. detach는 실제 단절된 성분을 돌려주고 가장 큰 성분만 본체에 유지한다. - `src/stages/DestructionSession.ts`: `DestructionSession(def)`, `start()`, `advance(dt:number,remainingCount:number)`, `snapshot()` → `{elapsed,started,finished,complete,stars,removedPercent}`. 원본 총량은 생성자 선택 인자 `initialCount`로 받는다. 최초 접촉 전에는 시계가 흐르지 않는다. 완료 경계 우선·제한 시간 FAIL·단위 상태는 프레임률에 독립적이어야 한다. - `src/core/destruction/DestructionScene.ts`: `DestructionScene(def,volume)`, `root:THREE.Group`, `body:THREE.Mesh`, `sync():void`, `pick(ndc:THREE.Vector2,camera:THREE.Camera):Point3|null`, `contact(point:Point3|null,active:boolean):void`, `burst(change:VolumeChange):void`, `update(dt:number,reducedMotion:boolean):void`, `dispose():void`. mode가 원본 volume을 넘기고 Scene이 field.version 변화 시 표면을 갱신한다. 신규 도구 모델·매끈한 body 재질·파편은 이 폴더의 책임별 파일로 분리 가능하다. body의 world position은 Volume.point와 일치한다. 분리 성분은 실제 격자로 메시를 만들어 떨어뜨린다. - `src/core/modes/DestructionMode.ts`: `GameMode`, `input:'stroke'`, `tablePieces:false`, 도구 전용 scene 포함. `stroke.start/move`는 게임 시각이 붙은 화면 표본을 저장하고, 프레임 훅이 고정 스텝별 보간·raycast·손상·시각 갱신을 수행한다. 비접촉 표본과 손 뗌에서 절단 경로를 끊는다. - `src/stages/DestructionStages.ts`: 신규 고유 ID 6개. `LevelDefinition`에 optional destruction 정의와 `mode:'destruction'`를 추가한다. 기존 일반 젤리 키는 도감 메타데이터로만 사용하며 제거 본체는 모드 정의 형상이다. - `src/ui/DestructionViews.ts`, 디버그 API `destruction()` 추가: kind, remainingCount, initialCount, removedPercent, elapsed, started, finished, active, bounds, 도구 위치, 접촉/타격 수, 표면 version과 성능 측정을 노출한다. 테스트 전용 강제 파괴 API는 만들지 않는다. ### Task 1: 순수 파괴 격자·시간 채점 **Files:** 위 Destruction.ts·DestructionVolume.ts·DestructionSession.ts, tests/DestructionVolume.test.ts·DestructionSession.test.ts. - [x] 위치별 재료 제거·빈 곳 중복 제거 없음·영구 손상·실제 단절 성분·전수 브러시로 0 도달·유한 좌표 경계 테스트를 먼저 작성하고 실패를 확인한다. - [x] 최초 접촉 전 시간 정지·시작 뒤 유휴 시간 포함·20/30/45와25/40/60 경계·완료 우선·미완료 FAIL·동일한 dt 합계의 채점 동등성 테스트를 먼저 작성하고 실패를 확인한다. - [x] 위 인터페이스대로 구현한다. 손상 누적·재료 상태·연결성은 렌더링 객체와 독립이다. - [x] `npx vitest run tests/DestructionVolume.test.ts tests/DestructionSession.test.ts` → 전부 PASS. 해당 변경을 독립 검토하고 작업 공간에 보존한다. ### Task 2: 젤리 표면·도구·실제 분리 연출 **Files:** src/core/destruction/{DestructionScene,DestructionTools,DestructionDebris}.ts, tests/DestructionScene.test.ts. Task 1의 격자 인터페이스를 소비한다. - [x] 제거 후 body 표면 갱신·원본 격자와 좌표 일치·분리 조각 실제 형상·dispose 자원 해제 테스트를 먼저 쓰고 실패를 확인한다. - [x] 기존 Three.js MarchingCubes 또는 동등한 재사용 버퍼 표면 생성으로 연속 표면을 만든다. 중앙 원본과 측면·내부 단면 색층이 자연스럽게 이어지게 한다. 뷰 상수에서 임의로 셰이더 프로그램을 매 프레임 재생성하지 않는다. - [x] 전동톱의 회전하는 체인·두꺼운 가이드 바·몸체·손잡이, 망치의 머리·그립·휘두르기, 분쇄기의 회전 롤러·입구·배출을 절차 모델로 만든다. 각 도구 접촉 부위는 실제 제거 위치와 일치한다. - [x] 접촉 국소 눌림·몸체 감쇠 출렁임을 넣고 실제 분리 조각은 원본 공간의 shape 그대로 떨어뜨린다. 작은 장식 파편은 풀과 단순 재질을 사용한다. - [x] `npx vitest run tests/DestructionScene.test.ts` → PASS. 대표 도구 장면은 Task 3 연결 직후 Task 4에서 선행 캡처한다. 독립 검토 후 작업 공간에 보존한다. ### Task 3: 모드·6판·진행·입력·UI 연결 **Files:** DestructionMode.ts·DestructionStages.ts·DestructionViews.ts, Game.ts(초기 등록), GameMode.ts·JellyDebug.ts·GameDebug.ts, LevelDefinition.ts·StageDefinitions.ts·stage-order.json·StageVariant.ts·LevelLabels.ts, tests/DestructionLevels.test.ts, e2e/destruction.spec.ts·e2e/debug-api.spec.ts. Task 1·2 인터페이스를 소비한다. - [x] 206판·기존200판 ID 유지·신규2판씩·도구별 라벨·각 종료 구간 테스트와 실제 입력 E2E를 먼저 작성하고 실패를 확인한다. - [x] 모드에서 raycast로 실제 표면을 찾는다. 톱의 연속 절단은 접촉 궤적과 체류 시간으로, 망치는 자동연타 간격으로, 분쇄기는 롤러 입구에 실제 들어간 만큼 제거한다. 최대 샘플 간격을 보간하여 터치 샘플 수로 제거 속도가 달라지지 않게 한다. - [x] 3D 상태 처리 고정 스텝·시각 갱신 주기·프레임 작업 상한을 정하고 지연을 디버그로 기록한다. 숨김/취소 시 동작 정리, Home/Retry 원본 복원, 종료 한 번, 전량 제거 시 finishLevel과 기존 저장/별/최종완료 연결. - [x] 기존 디버그 키와 테스트의 200판 콘텐츠 기준은 유지하되 활성 전체 판 수는206으로 확장한다. 신규 destruction()은 비대상 판에서 null이다. - [x] `npm run build`, 신규 단위 및 실제 마우스·터치 E2E → PASS. task review 후 작업 공간에 보존한다. ### Task 4: 대표 동작·전체 시각 검수·회귀·기획서·최종 리뷰 **Files:** .playwright-mcp/destruction-six/*, e2e/destruction.spec.ts, DocsSite 관련 문서와 생성기, PROGRESS.md와 이 계획 실행 기록. - [x] 203·205 대표에서 접촉·느린 절단·분리와 눌림·재타격 동작을 양해상도에서 먼저 확인한다. 리얼함 계약을 충족하지 못하면 표면·접촉·분리 방식을 수정하고 재검증한다. - [x] 6판 기본48장과 연속 동작 근거를 생성·직접 확인하고 배치·크기·색·파괴·완전제거의 결함을 수정한다. timeScale을 멈춰도 실제 포인터 입력을 쓰며 강제 결과 주입을 하지 않는다. - [x] 신규 실제 완주, 1/2/3별·FAIL·중단·복구·모드 전환·저장·메모리와 대표 프레임 비용 검사. 바뀐 코드 범위의 회귀 및 `npm test`, `npm run build`, 모바일 기능 E2E를 실행한다. 실패는 원인과 단독 재검증을 함께 기록한다. - [x] 프로젝트 docs-site 스킬을 읽고 최신206판·3도구의 규칙·미검증 상태를 기존 이력에 추가한다. 문서 테스트·검사 실행. main 병합·배포는 하지 않는다. - [x] `gpt-6.1-sol / medium` 최종 독립 리뷰에 설계·계획·코드·테스트·대표 원본/연속 캡처를 전달하고 중요한 지적을 수정·검증한다. 완료와 실기기 미검증을 구분해 보고한다. ## 실행 기록 - 2026-10-30 사용자 설계 승인. 전체 시각 검수·완전 제거 후 시간별 별 확정. 독립 설계 검토 완료. 기준 빌드·185파일3,055개 통과. - 구현 방식은 SDD로 순수 시뮬레이션과 렌더링 책임을 분리하고, 통합·실제 입력·시각 검수는 주 세션이 담당한다. 새로운 승인 요청 없이 계획과 구현을 이어간다. - Task 1~3 구현·중간 독립 리뷰 완료. `DestructionVolume`의 32³ 재료·누적 손상·실제 연결 성분과 `DestructionSession`의 최초 접촉/시간 경계/FAIL을 분리했다. 경로는 `carvePaths`로 합집합 손상을 한 번만 누적한다. - 입력은 `GameClock.inputTime()` 표본을 60Hz 스텝에 배분한다. 12px 이하 화면 간격으로 보간하고 실제 변형 표면의 접촉을 원본 좌표로 역변환한다. 정상 짧은 탭은 보존하고 강제 취소는 폐기한다. 분쇄기 누름 시작점/드래그도 각 스텝의 입력 시각으로 처리한다. - 중간 리뷰 수정: 셰이더 종류·높이별 프로그램 키, 압축/출렁임과 CPU raycast 일치, 음수 GLSL 상수 괄호, 평평한 면의 미정규화 법선으로 생기던 NaN·상면 소실, 같은 스텝의 프록시 정점 재사용, 빠른 드래그·짧은 탭·따라잡기 프레임 동등성. 실패 재현 후 회귀 검사를 통과했다. - 기존 200판 ID 순서 SHA-256 `b0dcec02aa877628ba360baf7a03cc2e0775c62098d0fdd14f5babb51fe910e6` 유지. 201~206만 추가하여 206판·8개 채점 모드·22개 세부 모드가 됐다. 206의 최종 형상은 더 두꺼운 oval이며 설계의 구현자 치수·형상 선택 범위에서 정했다. 분쇄기 첫 구현에는 막힘/압력 퍼즐을 추가하지 않고 누르고 밀어 넣는 기본 조작을 유지했다. - 도구별 초기 별 기준 20/30/45·25/40/60초 유지. 실제 입력 자동 경로에서 모두 3별 완료 가능, 유휴 시간을 넣은 1/2별과 시간초과 FAIL·Retry 검증. 수치는 사람 손맛/실기기 촉감 검증과 구분한다. - 1차 전체 단위 190파일·3,097개 통과 후 시간 입력·표면 회귀를 추가하여 190파일·3,102개 통과(71.12초). 마지막 카메라 여백 조정 후 재검사를 예정한다. TypeScript·Vite 빌드 통과, 번들 경고(약1.45MB)는 남아 있다. - 파괴·디버그 양해상도 E2E 56개 통과. 추가 파괴·생명주기 양해상도 E2E 50개 통과: 마지막 재료 0, 취소·숨김·Home·Retry, 컨텍스트 복구, 품질 교체, 206판 전체완료 저장/복원, 반복 전환/재시작 렌더 자원 비증가. - 전체 모바일 첫 실행은 시각 리뷰의 최종 여백 조정을 적용하기 위해 59통과·1중단·692미실행에서 종료했다(제품 실패 없음). 최종 빌드에서 기본 병렬 수 3으로 재실행한다. E2E 중 dist를 덮어쓰지 않았다. - 전체 시각 검수에서 평평한 면 소실을 수정하고, 48장과 도구별 연속 동작을 확인했다. 독립 리뷰의 안내 겹침·206 소개 배지 여백은 카메라 fitScale .85→.95로 보완했으며 완료 카드는 CSS 등장 안정화 후 재촬영한다. 최종 산출물·판정은 아래 후속 기록으로 확정한다. - main 병합/푸시·운영 배포·네이티브 빌드·Android/iOS 실기기 검증은 하지 않았다. 관리형 detached 작업 공간의 파일을 보존하며 이번 작업에서 커밋하지 않는다. - 최종 독립 리뷰(`gpt-6.1-sol / medium`) 승인: Critical·Important 없음. 12개 안정화 결과 원본과 48장 전체 개요·대표 원본·40ms 간격 망치 위상을 재확인했다. 공통 하단 안내가 결과 카드 뒤에 옅게 비치는 비차단 Minor는 남아 있다. - 후속 회귀 보완: `playContentToResult`에 실제 파괴 입력을 연결하고, 200판 회귀 묶음의 다음 진행 기대는 활성 목록 길이로 계산한다. 기존 200개 검사와 식별자는 보존하면서 신규 6판까지 순회하며, 신규 본체 여백은 전용 destruction bounds로 검사한다. 최종 리뷰에서 이 3개 테스트 변경도 승인했다. - 첫 입력 전의 불필요한 `vibrate(0)` 경고를 발견하여 실제 타격 진동이 시작된 경우에만 취소하도록 수정했다. 최초 입력 전 0회·타격 후 손 뗌 1회·exit 중복 없음 회귀의 RED→GREEN을 확인했다. 파괴 4파일46개 및 전체190파일3,103개(84.91초) 통과. 형상·타격 간격·채점은 바뀌지 않았다. - 검사 운영 문제: desktop/WebKit 70개 실행 중1개가 공용 trace 출력 디렉터리 충돌(ENOENT)로 실패했다. 해당 desktop 디버그 파일은 분리 출력 경로에서10/10통과했다. 같은 충돌의 모바일 app-lifecycle3건도 전체 종료 후 파일 단독 재검증한다. 이 실패를 제품 결함이나 통과로 잘못 합산하지 않는다. - 오디오 구현 범위: 전동톱/분쇄기는 기존 승인 long WAV 고리, 망치는 기존 `press` 압축음을 재사용한다. 새 외부 음원·전용 톱/망치 녹음은 추가하지 않았다. ## 최종 검증 결과 - TypeScript·프로덕션 빌드 통과: `npx tsc --noEmit`, `npx vite build --configLoader runner`. 번들 약1.45MB 경고는 남는다. 의존성 symlink의 Vite 임시 파일 쓰기 제한 때문에 runner를 사용했다. - 최종 단위 **190파일·3,103개 통과**(84.91초). `npx vitest run --configLoader runner`를 첫 진동 경고 수정 후 실행했다. - 최신 빌드의 파괴/생명주기 E2E는 Chromium 데스크톱·모바일·WebKit **75/75통과**. 201~206 실제 Next 연속 완주도 **3/3통과**. 강제 결과 주입 없이 실제 입력을 사용했다. - 전체 모바일 첫 회귀는 **713통과·12실패·27제외**(22.0분). 산출물 공유 충돌3건, 구 도우미/200판 고정 기대9건이다. 수정 후 app-lifecycle/home/progress 파일 **44/44**, stage-200의200~206 **7/7**, 활성206판 여백 **1/1**을 분리 출력 폴더에서 순차 재검증해 모두 통과했다. 기존200판 회귀나 품질 임계값을 줄이지 않았다. - 데스크톱/WebKit 최초 **69통과·1실패**는 trace ENOENT였고 디버그 파일 **10/10** 재검증 통과. 최신 제품의75개 검사에서도 통과했다. - E2E는 `.playwright-mcp/destruction-six/playwright.config.ts`에서127.0.0.1:4187 동일 dist를 사용했다. 검사 중 dist를 덮어쓰지 않고 재검증의 `--output`을 분리했다. 일반 개발 환경 기준 명령은 `npm run e2e:mobile`이다. - 최종 시각 산출물은 `.playwright-mcp/destruction-six/`의 **48장**, 양해상도 개요, `motion/`의6영상·연속36장·망치40ms18장이다. 실제1.2초 대기로 결과CSS를 안정화했다. 마지막 진동 수정은 화면 형상에 영향이 없다. - **최종 독립 리뷰 승인**, `gpt-6.1-sol / medium`, Critical·Important 없음. 테스트 호환·진동 취소·연속 진행 테스트의 후속 검토도 승인됐다. 비차단 Minor: 공통 안내가 결과 카드 뒤에 옅게 비침. 사용자 최종 손맛 평가는 별도다. ### 대표 성능(실기기 아님) Mac Chromium/Metal, medium 고정, desktop1280×800 DPR1·mobile390×844 DPR2, 장면별약3초. 다른 브라우저 검사를 종료한 뒤 단독 측정했다. `performance.json`이 원본이며 CPU 작업 수치는 GPU 시간이 아니다. | 해상도 | 도구 | 평균/p95 CPU(ms) | p95/최대 프레임 간격(ms) | 그리기 중 링크/미완료첫사용 | |---|---|---|---|---| | desktop | 분쇄기 | 3.00/5.10 | 22.3/113.7 | 0/0 | | desktop | 전동톱 | 3.30/5.30 | 22.2/29.3 | 3/0 | | desktop | 망치 | 2.82/3.90 | 22.7/45.4 | 1/0 | | mobile | 분쇄기 | 3.07/4.30 | 22.0/32.4 | 0/0 | | mobile | 전동톱 | 3.32/5.90 | 22.5/33.0 | 3/0 | | mobile | 망치 | 2.81/3.60 | 22.4/37.6 | 1/0 | - 단일 시뮬레이션 최대4.5~8.8ms·표면 갱신 최대4.6~6.6ms. 초기 분쇄기 단발113.7ms와 톱/망치 첫 파편 프로그램 링크가 남아 있어 무지연/0컴파일 달성으로 판정하지 않는다. 반복 전환/Retry의 렌더 자원 비증가는 확인했다. - Android/iOS 실기기 외관·촉감·성능, Windows 실제 실행·사용자 손 기준 별 난도는 미검증이다. 당시 최종 검증 보고 시점에는 배포·main 병합/푸시·커밋·네이티브 빌드/시뮬레이터 생성은 하지 않았고, 관리형 detached 작업 공간과 로컬 미리보기를 보존했다. 후속 로컬 main 통합은 아래 실행 기록을 따른다. - DocsSite206판 규칙·상태·목록·근거 원문을 갱신했다. 문서 테스트 **7/7**, 생성기 TypeScript·Astro 진단0오류/0경고, 정적HTML11개 및 링크·이미지·앵커·Markdown·색인·사이트맵 검사를 통과했다. 검증 중 상태를 완료로 바꾸며 해당 문서 상태 회귀 기대도 갱신했다. Astro 빌드의 기존404 콘텐츠 경고는 남아 있다. ### 후속 로컬 main 통합(2026-10-30) - 후속 사용자 요청으로 작업 변경을 한국어 커밋으로 기록하고 로컬 `main`에 일반 병합한다. `origin/main`은 `fa8cc42df7fe8d22fa0082d04c6f425651e25da3`이며 고정 원본 HEAD `fe48244e166c6607b32144718d3bf7b0f75a287d`의 조상이다. 원격 푸시·운영 배포는 하지 않는다. - 병합 결과에서 `npm run build`를 실행해 사용자가 로컬 `main`에서 빌드 가능한지 확인한다. 제품 소스는 최종 전체 검증 이후 수정되지 않았으므로 기존 단위·브라우저 검증 결과를 재사용한다.