ENGINEERING & ARCHITECTURE SPECIFICATION

별이 빛나는 밤에 by Junyeong 기술 설계서

기획 의도, 시스템 요구사항, 아키텍처 다이어그램, 3차 베지에 직소 곡선 수학적 알고리즘, Web Audio 절차적 음향 합성 및 크로스 플랫폼 최적화 기술 분석

📅 작성일: 2026년 10월 25일 👤 대상 독자: 프론트엔드 엔지니어, 게임 디자이너, 아키텍트 ⚡ 버전: 1.0.0 (Production Ready)

1. 프로젝트 개요 및 기획 배경

1.1 기획 의도 및 페르소나 분석

본 프로젝트는 타원형 나무 물감 팔레트에 직접 유화로 그려진 빈센트 반 고흐의 명작 「별이 빛나는 밤에 by Junyeong」을 디지털 인터랙티브 웹 게임으로 재탄생시키는 작업입니다.

🎯 핵심 페르소나 (Junyeong, 8세, 초등 2학년):
  • 인지 및 조작 특성: 소근육 조작 능력이 발달하는 시기로, 화면 드래그가 미숙할 수 있으므로 '터치 후 배치(Tap-to-Place)' 대안이 필수적임.
  • 심리적 보상: 퍼즐이 막혔을 때 좌절감을 느끼지 않도록 제한적 마법 찬스와 밑그림 가이드를 제공하여 자기주도적 성취감을 극대화함.
  • 교육적 가치: 원작의 붓터치, 명도 대비, 사이프러스와 초승달의 구도를 자연스럽게 체득하여 공간 지각력 및 예술적 관찰력을 함양함.

2. 시스템 요구사항 명세 (Requirements Matrix)

ID 구분 요구사항 명칭 상세 명세 및 구현 검증 기준
FR-01 기능 단계별 난이도 조절 4조각(2x2), 9조각(3x3), 16조각(4x4), 25조각(5x5), 36조각(6x6)의 5단계 동적 슬라이싱 지원
FR-02 기능 제한적 찬스 요청 게임당 최대 3회의 마법 찬스 제한. 미완성 조각을 자동 탐색하여 궤적 보간 비행 후 정답 슬롯에 스냅
FR-03 기능 완수 축하 애니메이션 마지막 조각 배치 시 퍼즐 선 투명화 페이드, Web Audio 승리 팡파르, 캔버스 물리 폭죽/별빛 파티클 3연속 폭발
FR-04 기능 듀얼 컨트롤 스킴 Pointer Events 기반 드래그 앤 드롭 + 원터치 선택 후 대상 슬롯 터치(Tap-to-Place) 동시 완벽 지원
FR-05 기능 인증서 자동 생성 오프라인 Canvas 2D 합성 엔진을 통해 플레이어 이름, 클리어 시간, 별점이 포함된 고해상도 PNG 파일 즉시 다운로드
NFR-01 비기능 무의존성 (Zero Dependency) 외부 라이브러리(CDN, React, NPM 등) 없이 순수 Vanilla JS, HTML5, CSS3, Web Audio API만으로 구현
NFR-02 비기능 크로스 플랫폼 호환성 Windows, Linux, macOS, iOS(Safari), iPadOS, Android(Chrome) 전 기기 OS 및 뷰포트에서 완전 동일 실행
NFR-03 비기능 60 FPS 렌더링 성능 requestAnimationFrame 기반 파티클 엔진 및 Canvas 오프스크린 캐싱을 통해 60 FPS 보장

3. 시스템 아키텍처 및 컴포넌트 설계

본 시스템은 유지보수성과 오프라인 단일 파일 패키징의 유연성을 위해 철저한 단일 책임 원칙(SRP) 기반의 모듈형 객체 지향 구조로 설계되었습니다.

┌─────────────────────────────────────────────────────────────────────────────┐ │ StarryPuzzleGame (App) │ │ - Game State (Difficulty, Moves, Timer, Chances, Active Selection) │ │ - Unified Pointer Events Manager (Drag & Drop + Tap-to-Place) │ │ - Viewport Resize & Layout Coordinator │ └────────┬──────────────────────┬───────────────────────┬─────────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────────┐ ┌─────────────────┐ ┌──────────────────────┐ │ JigsawGeometry │ │ SoundEngine │ │ ParticleEngine │ │ - Edge Map Gen │ │ - Web Audio API │ │ - Confetti Physics │ │ - 3D Bezier Tabs │ │ - Snap, Pick │ │ - Golden Star Burst │ │ - Canvas Slicing │ │ - Victory Brass │ │ - RAF Animation Loop │ └──────────────────┘ └─────────────────┘ └──────────────────────┘ │ │ ▼ ▼ ┌──────────────────────────────────┐ ┌──────────────────────┐ │ DOM Puzzle Pieces │ │ CertificateGenerator │ │ - Matrix Snapping │ │ - 1200x850 Hi-DPI │ │ - Wobble Shake on Mistake │ │ - Wax Seal & Frame │ └──────────────────────────────────┘ └──────────────────────┘

3.2 상태 머신 (State Machine) 전이도

게임 라이프사이클은 IDLE -> PLAYING -> DRAGGING / SELECTED -> SNAPPING -> VICTORY 단계로 명확히 관리됩니다.

4. 핵심 알고리즘 및 수학적 구현 상세

4.1 3차 베지에 곡선(Cubic Bézier) 기반의 유기적 직소 엣지 알고리즘

전통적인 직소 퍼즐의 볼록 탭(Tab)과 오목 홈(Blank)은 수학적으로 3차 베지에 곡선의 조합으로 정의됩니다. 시작점 \(P_0(x_0, y_0)\)와 끝점 \(P_1(x_1, y_1)\) 사이의 변위 벡터를 \(\vec{d} = P_1 - P_0\), 변의 길이를 \(L = \|\vec{d}\|\)라 할 때, 진행 방향 단위벡터 \(\vec{u}\)와 법선 단위벡터 \(\vec{n}\)은 다음과 같이 정의됩니다.

\[ \vec{u} = \frac{\vec{d}}{L} = \left(\frac{x_1 - x_0}{L}, \frac{y_1 - y_0}{L}\right) \] \[ \vec{n} = \text{dir} \cdot (-u_y, u_x) \quad (\text{dir} \in \{+1, -1, 0\}) \]

탭의 깊이를 \(D = L \times 0.24\)로 설정하고, 엣지 상의 임의의 점을 매개변수 \((s, d)\)로 표현하면: \[ P(s, d) = P_0 + s \cdot L \cdot \vec{u} + d \cdot D \cdot \vec{n} \] 곡선의 제어점은 자연스러운 직소 목(Neck)과 머리(Head)를 형성하도록 대칭적으로 배치됩니다:

// JigsawGeometry.drawEdge 3차 베지에 제어점 배치
const p1   = p(0.38, 0.0);  // 기준선 직선 구간 끝
const cp1a = p(0.36, 0.15); // 목 진입 제어점 A
const cp1b = p(0.32, 0.55); // 목 진입 제어점 B
const p2   = p(0.40, 0.95); // 둥근 탭 머리 시작점
ctx.bezierCurveTo(cp1a.x, cp1a.y, cp1b.x, cp1b.y, p2.x, p2.y);

const cp2a = p(0.46, 1.20); // 탭 정점 제어점 A (돌출 깊이 120%)
const cp2b = p(0.54, 1.20); // 탭 정점 제어점 B
const p3   = p(0.60, 0.95); // 탭 머리 반대편
ctx.bezierCurveTo(cp2a.x, cp2a.y, cp2b.x, cp2b.y, p3.x, p3.y);
            

4.2 인접 조각 간 엣지 정합성 보장 정리

퍼즐 조각 \((r, c)\)와 오른쪽 인접 조각 \((r, c+1)\)이 완벽히 맞물리기 위해서는 다음 불변 조건(Invariant)을 반드시 만족해야 합니다.

\[ \text{Edge}_{\text{right}}(r, c) = -\text{Edge}_{\text{left}}(r, c+1) \] \[ \text{Edge}_{\text{bottom}}(r, c) = -\text{Edge}_{\text{top}}(r+1, c) \] 외곽 경계 조각들의 바깥쪽 변은 모두 평평한 선(\(\text{dir} = 0\))으로 처리됩니다.

4.3 유클리드 거리 기반 마그네틱 스냅 (Magnetic Snap) 알고리즘

사용자가 드래그 중인 조각의 패딩 보정 중심점 \((X_p, Y_p)\)와 정답 그리드 슬롯 좌표 \((X_t, Y_t)\) 사이의 유클리드 거리 \(d\)를 매 포인터 릴리즈 시점에 계산합니다.

const dist = Math.hypot(pieceInternalX - targetX, pieceInternalY - targetY);
if (dist <= this.config.snapDistance) {
    // 45px 이내 진입 시 자석 흡착 및 물리 효과음 발동
    this.snapPiece(piece);
} else {
    this.returnPieceToTray(piece);
}
            

4.4 Web Audio API 기반의 무의존성 절차적 음향 합성 (Procedural Audio)

외부 MP3/WAV 오디오 다운로드 지연 및 네트워크 끊김 현상을 원천 방지하기 위해 브라우저 내장 신디사이저를 수학적으로 모델링하였습니다.

효과음 오실레이터 파형 주파수 변조 (Frequency Ramp) 게인 엔벨로프 (ADSR)
스냅 (Snap) Triangle + Sine 600Hz → 1200Hz → 400Hz (0.14s) + 1800Hz Click Linear Attack (0.01s), Expo Decay (0.14s)
픽 (Pick) Triangle 320Hz → 480Hz (0.07s) Fast Peak (0.15), Expo Decay (0.09s)
마법 찬스 (Hint) Sine (5노트) C5(523Hz) → E5 → G5 → C6 → E6 아르페지오 0.07초 간격 시차 발음, 0.35초 감쇠
승리 (Victory) Triangle + Sine C5, E5, G5, High C6 팡파레 화음 + 별빛 아르페지오 풍부한 서스테인 후 0.7초 감쇠 페이드아웃

5. 크로스 플랫폼 최적화 및 안정성 보장 기법

  • Pointer Events API 단일화: pointerdown, pointermove, pointerup을 통해 마우스, 애플 펜슬, 정전식 정밀 멀티터치를 단일 파이프라인으로 처리하고, setPointerCapture로 브라우저 밖으로 벗어나는 이탈을 방지.
  • iOS 사파리 고유 제스처 방지: 드래그 대상 요소 및 캔버스에 touch-action: none;을 지정하여 iOS 사파리의 기본 스크롤, 바운스 줌, 스와이프 뒤로가기 제스처 간섭을 원천 차단.
  • Retina 고밀도 디스플레이 대응: window.devicePixelRatio를 감지하여 캔버스 백킹 버퍼 해상도를 스케일업함으로써 고해상도 모바일 화면에서도 선명하고 매끄러운 렌더링 유지.
  • 종횡비 잠금 반응형 레이아웃: 1200:796(약 1.5:1) 원본 캔버스 종횡비를 수식적으로 계산하여 모바일 세로/가로 모드 및 데스크톱 와이드 모드에서 여백 왜곡 없이 최적 배율 유지.

6. 클라우드 배포 및 무료 호스팅 가이드

본 게임은 완전한 정적 클라이언트 애플리케이션(SPA)이므로 무료 클라우드 플랫폼에서 서버 비용 0원으로 즉시 전 세계 배포가 가능합니다.

1) GitHub Pages (완전 무료)

저장소 생성 후 본 폴더의 파일들을 푸시하고 [Settings] → [Pages]에서 Branch를 main으로 설정하면 1분 안에 HTTPS 도메인으로 서빙됩니다.

2) Cloudflare Pages / Vercel

GitHub 리포지토리 연동 시 무제한 대역폭과 전 세계 CDN 엣지 캐싱이 무료 티어로 제공되어 지연 시간 10ms 이내의 쾌속 로딩을 달성합니다.

7. 자동화 검증 및 E2E 테스트 결과

Chrome DevTools Protocol(CDP) 기반의 헤드리스 엔드투엔드(E2E) 자동화 검증 스위트(tests/e2e.mjs)를 통해 전 기능 검증을 통과하였습니다.

E2E Server listening on http://127.0.0.1:9989
✔ Game initialized and image loaded!
✔ Piece count: 16 (expected 16)
✔ Sound engine active: true
✔ Initial chances: 3
✔ Chances remaining after hint: 2, Placed count: 1
✔ Switched difficulty to easy9: piece count is 9
✔ Ghost toggle: false -> true
✔ Edge filter active: true
Testing full solve flow on easy4 (4 pieces)...
✔ Game completed: true, Victory Modal Active: true
✔ Certificate Generator generated PNG: true (length: 1634190)
✔ Zero console errors detected during entire test run!
========================================
🎉 ALL AUTOMATED E2E TESTS PASSED 100%!
========================================