import type { BearCrushDefinition } from './BearCrush'; import type { OddJellyDefinition } from './OddJelly'; import type { JellyType } from '../jelly/JellyShapes'; import type { Tool } from '../cutting/Tool'; import type { SpinDefinition } from './Spin'; import type { StampSlot } from './Stamp'; import type { StampMotionDefinition } from './StampMotion'; import type { SlowFirmness } from './SlowCut'; import type { SwipeLaunch } from './SwipeTrajectory'; export type { JellyType }; /** 채점 방식: guide = 가이드 정확도, split = 조각 면적 비율(가이드 없음), stamp = 틀 찍기 IoU(F1, 가이드 없음), slow = 천천히 썰기 부드러움(G, 가이드 없음), swipe = 날아가는 젤리를 휘둘러 벤 횟수(H, 가이드 없음) */ export type LevelMode = 'guide' | 'split' | 'stamp' | 'slow' | 'swipe' | 'bearCrush' | 'oddJelly'; /** 새 요소가 처음 나오는 레벨의 소개 표시 */ export type LevelIntro = 'straight' | 'curve' | 'split' | 'spin' | 'recut' | 'stamp' | 'slow' | 'swipe' | 'bearCrush' | 'oddJelly'; /** * 가이드. 좌표는 젤리 좌표계 (x, z)이고 화면 좌표가 아니다. * curve 가 없으면 start → end 직선이다(끝점은 외곽 바로 바깥 0.04). * curve 가 있으면 start·c1·c2·end 는 젤리를 가로지르는 설계 베지에이고, guidePaths 가 외곽 바깥 0.04 지점에서 자른다. */ export interface GuideLineDefinition { id: string; start: [number, number]; end: [number, number]; curve?: { c1: [number, number]; c2: [number, number] }; } export interface LevelDefinition { oddJelly?: OddJellyDefinition; /** 연속 벨트 곰 압착: 모두 압착하거나 같은 보호색 여러 마리를 살린다. */ bearCrush?: BearCrushDefinition; /** 카탈로그의 불변 ID. 실제 배치에는 필수이고 합성 테스트 레벨은 생략 가능하다. */ stageId?: string; /** 현재 배치의 표시 번호, 1부터 연속 */ id: number; jellyType: JellyType; mode: LevelMode; /** 레벨이 정하는 칼(플레이어가 고르지 않는다) */ tool: Tool; targetPieceCount: 1 | 2 | 3 | 4 | 5 | 6; maxKnifeStrokes: 1 | 2 | 3 | 4 | 5; /** 실패선. 이 값 미만이면 별 0. STAR_TWO(75) 미만이어야 한다 */ minAccuracy: number; guideLines: GuideLineDefinition[]; intro?: LevelIntro; /** 회전판(D 스펙 2절): 젤리가 가이드와 함께 판 위에서 돈다. 손을 떼는 순간 판이 멈추고 그 각도로 자른다. 식칼 전용(Game 이 거부) */ spin?: SpinDefinition; /** * 조각 다시 자르기(D 스펙 3절): guideLines 는 첫 가이드 하나이고, 자를 때마다 방금 생긴 큰 조각 위에 그 조각을 반 가르는 다음 가이드가 생긴다. * angles[k] 는 k+2 번째 가이드 방향 = 직전 절단 직선 방향 + angles[k](°, 위에서 볼 때 반시계). 길이 = maxKnifeStrokes − 1. spin 과 함께 쓰지 않는다 */ recut?: { angles: number[] }; /** 타이밍 찍기: 자리는 정해진 순서대로, 젤리가 고정 틀 아래를 일정 속도로 왕복한다. */ stamp?: { slots: StampSlot[]; motion: StampMotionDefinition }; /** * 천천히 썰기(G 스펙 3.1·4.3절): mode 'slow' 일 때만. 긴 축(x)의 왼쪽 끝부터 폭 width 씩 떼어 내는 x = 상수 직선을 차례로 썬다 * (`slowCutLines`, 남은 덩어리가 썬 조각보다 크다). cuts = maxKnifeStrokes, firmness 는 칼이 들어가는 단단함. guideLines 는 비고 tool 은 'knife', minAccuracy 0(항상 통과) */ slow?: { cuts: 3 | 4 | 5; firmness: SlowFirmness; width: number; target?: boolean }; /** * 휘둘러 베기(H 스펙 8절): mode 'swipe' 일 때만. launches 는 발사 목록(정규화 좌표), par 는 ★★★ 휘두르기 수, * goal 'all' 은 날아온 젤리를 모두 베기(목표 = 발사 수), 'pieces' 는 한 젤리를 4조각까지 쪼개기. * jellyType 은 첫 발사의 종류와 같다. tool 'knife'·guideLines 비어 있음·minAccuracy 0·maxKnifeStrokes 1(자리 채우기, 채점은 swipeStars) */ swipe?: { launches: SwipeLaunch[]; par: number; goal: 'all' | 'pieces' }; }