# BC-Game Up/Down - 2026-01-27 작업 내역

## 프로젝트 개요

BC-Game Up/Down은 Binance BTCUSDT 가격을 기반으로 한 실시간 배팅 게임입니다.

### 핵심 아키텍처
- **Frontend**: TypeScript/React + Vite
- **Backend**: PHP + WebSocket (Ratchet)
- **Binance Relay**: Node.js WebSocket Server (포트 8081)
- **Round Duration**: 30초
- **Price Precision**: 소숫점 2자리 (Binance BTCUSDT tickSize = 0.01)
- **Timezone**: Asia/Seoul (한국 표준시)

---

## 2026-01-27 주요 작업 내역

### 1. 소숫점 자리수 변경 (4자리 → 2자리)

**목적**: Binance BTCUSDT는 소숫점 2자리까지만 제공하므로, 모든 가격 표시를 2자리로 통일

#### Frontend 파일 변경

**`frontend/apps/up-down/src/utils/format-price.ts`**
```typescript
export function formatPrice(price: number | null | undefined): string {
  if (price === null || price === undefined || isNaN(price)) {
    return '0.00';  // Changed from '0.0000'
  }
  return price.toFixed(2);  // Changed from toFixed(4)
}
```

**`frontend/node_modules/@bc-game/chart/index.js`**
```javascript
// LivePrice 컴포넌트 수정
minimumFractionDigits: 2,  // Changed from 4
maximumFractionDigits: 2   // Changed from 4
```

#### Backend 파일 변경

**`backend/includes/Game.class.php`**
```php
// 가격 반올림 변경
$startPrice = round(floatval($round['start_price']), 2);  // Changed from 4
$endPrice = round(floatval($endPrice), 2);                // Changed from 4

// TIE 판정 tolerance 변경
if (abs($endPrice - $startPrice) < 0.01) {  // Changed from 0.0001
    $result = self::RESULT_TIE;
}
```

**`backend/websocket/game-manager.php`**
```php
// 모든 printf 포맷 변경
printf format: %.2f  // Changed from %.4f

// TIE tolerance 변경
if (abs($endPrice - $startPrice) < 0.01) {  // Changed from 0.0001
```

**변경된 파일 목록** (총 9개):
1. `frontend/apps/up-down/src/utils/format-price.ts`
2. `frontend/apps/up-down/src/components/live-chart.component.tsx`
3. `frontend/apps/up-down/src/components/stastics-list.component.tsx`
4. `frontend/apps/up-down/src/components/benefit.component.tsx`
5. `frontend/node_modules/@bc-game/chart/index.js`
6. `backend/includes/Game.class.php`
7. `backend/websocket/game-manager.php`
8. `backend/api/bet.php`
9. `backend/api/user.php`

---

### 2. 실시간 통계 시스템 구축

#### 2.1 Statistics API 생성

**`backend/api/statistics.php`** (NEW)

24시간 통계 및 승률 계산 API:
- `players_24h`: 최근 24시간 플레이어 수
- `payouts_24h`: 최근 24시간 총 지급액
- `total_payouts`: 전체 누적 지급액
- `up_rate`: UP 승률 (최근 30라운드 기준)
- `down_rate`: DOWN 승률 (최근 30라운드 기준)

```php
date_default_timezone_set('Asia/Seoul');

// 24시간 플레이어 수 계산
$sql = "SELECT COUNT(DISTINCT session_id) as count
        FROM bets
        WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)";

// 24시간 지급액 계산
$sql = "SELECT SUM(payout) as total
        FROM bets
        WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
        AND payout > 0";

// 전체 누적 지급액
$sql = "SELECT SUM(payout) as total FROM bets WHERE payout > 0";

// 최근 30라운드 승률
$sql = "SELECT result, COUNT(*) as count
        FROM game_rounds
        WHERE status = 'COMPLETED' AND result != 'TIE'
        ORDER BY round_number DESC LIMIT 30";
```

#### 2.2 Statistics 컴포넌트 업데이트

**`frontend/apps/up-down/src/components/stastics.component.tsx`**

- 하드코딩된 값 제거
- API에서 실시간 통계 로드
- USDT 아이콘을 ₩ (원화) 기호로 변경
- formatKRW 함수로 한국 통화 형식 적용

```typescript
const [statistics, setStatistics] = useState<Statistics>({
  players_24h: 0,
  payouts_24h: 0,
  total_payouts: 0,
  win_rate_24h: 0,
});

useEffect(() => {
  const loadStatistics = async () => {
    const response = await fetch('/api/statistics.php');
    const data = await response.json();
    if (data.success) {
      setStatistics(data.data);
    }
  };
  loadStatistics();
  const interval = setInterval(loadStatistics, 30000); // 30초마다 갱신
  return () => clearInterval(interval);
}, []);

// 한국 통화 형식
const formatKRW = (value: number): string => {
  return new Intl.NumberFormat('ko-KR', {
    maximumFractionDigits: 1,
  }).format(value);
};
```

---

### 3. 개인 배팅 정보 실시간 표시

#### 3.1 WebSocket 서버 업데이트

**`backend/websocket/GameWebSocket.php`**

개인 배팅 정보 및 잔액 전송 기능 추가:

```php
/**
 * Get my bets for current round
 */
private function getMyBets($roundId, $sessionId) {
    // 내 UP/DOWN 배팅 금액 조회
    $sql = "SELECT direction, SUM(amount) as total_amount
            FROM bets
            WHERE round_id = ? AND session_id = ?
            GROUP BY direction";

    $bets = $db->fetchAll($sql, [$roundId, $sessionId]);

    // 배당률 계산
    $totalPool = $totalUpAmount + $totalDownAmount;
    if ($totalPool > 0) {
        $downMultiplier = $totalDownAmount > 0 ? $totalPool / $totalDownAmount : 1;
        $upMultiplier = $totalUpAmount > 0 ? $totalPool / $totalUpAmount : 1;
    }

    return [
        'down_amount' => $downAmount,
        'up_amount' => $upAmount,
        'down_multiplier' => round($downMultiplier, 2),
        'up_multiplier' => round($upMultiplier, 2)
    ];
}

/**
 * Get user balance by session ID
 */
private function getUserBalance($sessionId) {
    $sql = "SELECT balance FROM users WHERE session_id = ? LIMIT 1";
    $user = $db->fetchOne($sql, [$sessionId]);
    return $user ? floatval($user['balance']) : null;
}
```

**game.state 메시지에 추가된 필드**:
- `my_bets`: 개인 배팅 정보 (down_amount, up_amount, multipliers)
- `user_balance`: 실시간 잔액

#### 3.2 Benefit 컴포넌트 업데이트

**`frontend/apps/up-down/src/components/benefit.component.tsx`**

- 하드코딩된 값(₩1, 100%) 제거
- WebSocket에서 받은 my_bets 데이터 표시
- Statistics API에서 승률 데이터 로드

```typescript
const [myBets, setMyBets] = useState<BetInfo>({
  downAmount: 0,
  upAmount: 0,
  downPotentialPayout: 0,
  upPotentialPayout: 0,
});

const [winRate, setWinRate] = useState<WinRate>({
  upRate: 0,
  downRate: 0,
});

useEffect(() => {
  const handleGameState = (event: any) => {
    const data = event.detail;
    if (data.my_bets) {
      setMyBets({
        downAmount: data.my_bets.down_amount || 0,
        upAmount: data.my_bets.up_amount || 0,
        downPotentialPayout: (data.my_bets.down_amount || 0) * (data.my_bets.down_multiplier || 1),
        upPotentialPayout: (data.my_bets.up_amount || 0) * (data.my_bets.up_multiplier || 1),
      });
    }
  };

  window.addEventListener('game:state', handleGameState);
  return () => window.removeEventListener('game:state', handleGameState);
}, []);
```

---

### 4. 실시간 잔액 업데이트 시스템

#### 4.1 Balance Store 생성

**`frontend/apps/up-down/src/stores/balance-store.ts`** (NEW)

Zustand를 사용한 전역 잔액 관리:

```typescript
interface BalanceStore {
  balance: number;
  setBalance: (balance: number) => void;
  updateBalance: (delta: number) => void;
  loadBalance: () => Promise<void>;
}

export const useBalanceStore = create<BalanceStore>((set, get) => ({
  balance: 10000, // 기본 잔액

  setBalance: (balance: number) => {
    set({ balance });
    localStorage.setItem('user_balance', balance.toString());
  },

  updateBalance: (delta: number) => {
    const newBalance = get().balance + delta;
    set({ balance: newBalance });
    localStorage.setItem('user_balance', newBalance.toString());
  },

  loadBalance: async () => {
    // 1. localStorage에서 로드
    const storedBalance = localStorage.getItem('user_balance');
    if (storedBalance) {
      set({ balance: parseFloat(storedBalance) });
    }

    // 2. 로그인 사용자: API에서 최신 잔액 조회
    const userData = JSON.parse(localStorage.getItem('user') || '{}');
    if (userData.user_id) {
      const response = await fetch(`/api/auth.php?action=balance&user_id=${userData.user_id}`);
      const data = await response.json();
      if (data.success) {
        set({ balance: parseFloat(data.data.balance) });
      }
    }

    // 3. 게스트 사용자: session 기반 조회
    const sessionId = localStorage.getItem('session_id');
    if (sessionId && !userData.user_id) {
      const response = await fetch(`/api/user.php?session_id=${sessionId}`);
      const data = await response.json();
      if (data.success) {
        set({ balance: parseFloat(data.balance) });
      }
    }
  },
}));

// 초기 로드
if (typeof window !== 'undefined') {
  useBalanceStore.getState().loadBalance();
}
```

#### 4.2 Betting 컴포넌트 통합

**`frontend/apps/up-down/src/components/betting.component.tsx`**

```typescript
const balance = useBalanceStore((state) => state.balance);
const setBalance = useBalanceStore((state) => state.setBalance);
const loadBalance = useBalanceStore((state) => state.loadBalance);

// 초기 잔액 로드
useEffect(() => {
  if (user) {
    setBalance(user.balance);
  } else {
    loadBalance();
  }
}, [user]);

// 배팅 성공 시 Optimistic Update
const handleBet = async (direction: 'UP' | 'DOWN') => {
  const result = await BackendAPI.placeBet(direction, betAmount, user?.user_id);

  if (result.success) {
    // 즉시 잔액 차감
    setBalance(balance - betAmount);

    // 서버에서 정확한 잔액 다시 로드
    setTimeout(async () => {
      await loadBalance();
    }, 500);
  }
};
```

#### 4.3 GameStateSyncService 통합

**`frontend/apps/up-down/src/services/GameStateSyncService.ts`**

WebSocket으로 받은 잔액 자동 업데이트:

```typescript
private syncGameState(data: WSGameState): void {
  // 잔액 업데이트 (WebSocket에서 받은 경우)
  if (data.user_balance !== undefined && data.user_balance !== null) {
    const balanceStore = useBalanceStore.getState();
    balanceStore.setBalance(data.user_balance);
  }
}
```

---

### 5. Timezone 설정 (Asia/Seoul)

타임스탬프 오류 해결을 위해 모든 백엔드 파일에 timezone 설정 추가:

**변경된 파일**:
- `backend/api/statistics.php`
- `backend/api/bet.php`
- `backend/api/user.php`
- `backend/api/auth.php`
- `backend/includes/Database.class.php`

```php
// 모든 PHP 파일 상단에 추가
date_default_timezone_set('Asia/Seoul');
```

---

### 6. "작동 방식" UI 모달 추가

#### 6.1 How It Works Modal 생성

**`frontend/apps/up-down/src/components/how-it-works-modal.component.tsx`** (NEW)

4단계 가이드 모달:
1. **단계 1**: 방향 선택 (UP 또는 DOWN)
2. **단계 2**: UP 설명 - 가격이 상승하면 승리
3. **단계 3**: DOWN 설명 - 가격이 하락하면 승리
4. **단계 4**: 배당금 계산 - 풀 기반 배당률

```typescript
interface HowItWorksModalProps {
  isOpen: boolean;
  onClose: () => void;
}

export const HowItWorksModal: React.FC<HowItWorksModalProps> = ({ isOpen, onClose }) => {
  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
      <div className="bg-white dark:bg-gray-800 rounded-lg max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
        {/* 4단계 가이드 내용 */}
      </div>
    </div>
  );
};
```

#### 6.2 Header 컴포넌트에 버튼 추가

**`frontend/apps/up-down/src/components/header.component.tsx`**

```typescript
const [isModalOpen, setIsModalOpen] = useState(false);

// "작동 방식" 버튼 추가
<button onClick={() => setIsModalOpen(true)}>
  작동 방식
</button>

<HowItWorksModal
  isOpen={isModalOpen}
  onClose={() => setIsModalOpen(false)}
/>
```

---

## 최종 빌드 정보

**빌드 시간**: 2026-01-27 02:20 (KST)

**빌드 결과**:
```bash
dist/apps/up-down/index.html                  0.51 kB │ gzip:  0.32 kB
dist/apps/up-down/assets/index-Bgy9nGp_.js  556.52 kB │ gzip: 153.37 kB
```

**배포 경로**: `C:\laragon\www\bc-game-updown\public\`

**배포 명령어**:
```bash
cd frontend && npm run build
cp -r frontend/dist/apps/up-down/* public/
```

---

## 테스트 체크리스트

### Frontend 테스트
- [ ] 모든 가격 표시가 소숫점 2자리로 표시되는지 확인
- [ ] LivePrice (현재가)가 2자리로 표시되는지 확인
- [ ] 통계 페이지의 모든 숫자가 실시간으로 업데이트되는지 확인
- [ ] Benefit 컴포넌트에 내 배팅 정보가 실시간으로 표시되는지 확인
- [ ] 배팅 후 잔액이 즉시 차감되는지 확인
- [ ] 승리 시 잔액이 자동으로 증가하는지 확인
- [ ] "작동 방식" 모달이 정상적으로 열리고 닫히는지 확인
- [ ] 다크모드에서 모든 컴포넌트가 정상 작동하는지 확인

### Backend 테스트
- [ ] `/api/statistics.php` API가 정확한 통계를 반환하는지 확인
- [ ] WebSocket이 my_bets와 user_balance를 올바르게 전송하는지 확인
- [ ] TIE 판정이 0.01 tolerance로 정확하게 작동하는지 확인
- [ ] 타임스탬프가 한국 시간으로 정확하게 표시되는지 확인
- [ ] 배당률 계산이 정확한지 확인

### Integration 테스트
- [ ] 여러 클라이언트에서 동일한 게임 상태가 표시되는지 확인
- [ ] 새 라운드 시작 시 모든 클라이언트가 동기화되는지 확인
- [ ] Binance 가격이 실시간으로 차트에 반영되는지 확인

---

## 알려진 이슈 및 해결

### 이슈 1: 차트 precision 설정
- **문제**: 차트 precision이 이미 이전에 2로 설정되어 있었음
- **해결**: 추가 변경 불필요, LivePrice 컴포넌트만 수정

### 이슈 2: Timestamp 5804년 표시
- **문제**: 서버 timezone 설정 누락으로 인한 timestamp 오류
- **해결**: 모든 백엔드 파일에 `date_default_timezone_set('Asia/Seoul')` 추가

### 이슈 3: 빌드 파일 경로
- **문제**: 빌드 후 브라우저에서 변경사항이 보이지 않음
- **해결**: `cp -r frontend/dist/apps/up-down/* public/` 명령어로 public 폴더에 복사

---

## 기술 스택 상세

### Frontend
- **Framework**: React 18
- **Build Tool**: Vite
- **State Management**: Zustand
- **WebSocket**: Custom BackendWebSocket class
- **Chart Library**: @bc-game/chart (Custom)
- **Styling**: Tailwind CSS

### Backend
- **Language**: PHP 8.x
- **WebSocket**: Ratchet (React PHP)
- **Database**: MySQL/MariaDB
- **Binance Integration**: Node.js WebSocket Relay (포트 8081)

### DevOps
- **Environment**: Laragon (Windows)
- **Working Directory**: C:\laragon\www\bc-game-updown
- **Public Directory**: public/
- **API Endpoint**: /api/

---

## 파일 구조

```
bc-game-updown/
├── frontend/
│   ├── apps/
│   │   └── up-down/
│   │       └── src/
│   │           ├── components/
│   │           │   ├── betting.component.tsx          # 배팅 UI + Balance Store 통합
│   │           │   ├── benefit.component.tsx          # 개인 배팅 정보 (실시간)
│   │           │   ├── stastics.component.tsx         # 통계 (실시간)
│   │           │   ├── header.component.tsx           # "작동 방식" 버튼
│   │           │   └── how-it-works-modal.component.tsx # NEW: 가이드 모달
│   │           ├── services/
│   │           │   ├── GameStateSyncService.ts        # WebSocket 동기화 + 잔액 업데이트
│   │           │   └── BackendAPI.ts
│   │           ├── stores/
│   │           │   └── balance-store.ts               # NEW: 전역 잔액 관리
│   │           └── utils/
│   │               └── format-price.ts                # 가격 포맷 (2자리)
│   └── node_modules/
│       └── @bc-game/
│           └── chart/
│               └── index.js                           # LivePrice 수정
├── backend/
│   ├── api/
│   │   ├── statistics.php                             # NEW: 통계 API
│   │   ├── bet.php                                    # Timezone 추가
│   │   ├── user.php                                   # Timezone 추가
│   │   └── auth.php                                   # Timezone 추가
│   ├── includes/
│   │   ├── Game.class.php                             # 소숫점 2자리 + TIE tolerance
│   │   └── Database.class.php                         # Timezone 추가
│   └── websocket/
│       ├── GameWebSocket.php                          # my_bets, user_balance 추가
│       └── game-manager.php                           # 소숫점 2자리
├── public/                                            # 빌드 배포 폴더
└── 0127.md                                            # 이 문서
```

---

## 다음 단계 제안

1. **Performance Monitoring**
   - WebSocket 메시지 전송량 모니터링
   - API 응답 시간 측정

2. **Security Enhancement**
   - API rate limiting 추가
   - Session validation 강화

3. **UX Improvements**
   - 배팅 애니메이션 추가
   - 승리 시 축하 이펙트
   - 사운드 이펙트 추가 (선택적)

4. **Feature Additions**
   - 배팅 히스토리 페이지
   - 순위 시스템
   - 소셜 기능 (채팅)

---

## 참고 사항

- **Binance API**: BTCUSDT ticker (tickSize = 0.01)
- **Round Duration**: 30초 고정
- **TIE Tolerance**: ±0.01 (1센트)
- **Default Balance**: ₩10,000
- **Min Bet**: ₩1
- **Max Decimals**: 2자리

---

### 7. 네트워크 지연 및 시간 동기화 문제 해결

#### 7.1 문제 발견

**증상**:
- 로컬 환경(1ms): 정상 작동
- 한국 컴퓨터(49ms 네트워크 지연): 깃발 타이밍이 약 3초 어긋남

**분석 로그**:
```
[ExchangeTimeAdapter] Time Sync Info: {
  offset: '3063ms',
  serverTime: '2026-01-27T06:32:27.232Z',
  clientTime: '2026-01-27T06:32:24.169Z'
}
```

**근본 원인**:
- 단순 네트워크 지연(49ms)이 아니라 **서버와 클라이언트의 시스템 시간 차이 3초**
- 서버 timestamp를 그대로 차트에 전달하면 3초 이전 위치에 깃발 표시
- 실시간 가격 데이터도 동일한 문제 발생

#### 7.2 해결 방법

**`frontend/apps/up-down/src/services/GameStateSyncService.ts`**

**라운드 시작 이벤트 보정**:
```typescript
// 라운드 시작 시
const serverTimestamp = data.start_timestamp;  // 서버 시간
const offset = timeSync.getOffset();            // 서버 - 클라이언트 차이 (3063ms)
const clientTimestamp = serverTimestamp - offset; // 클라이언트 시간으로 변환

console.log(`[GameStateSync] 🕐 Time correction: server=${serverTimestamp}, offset=${offset}ms, client=${clientTimestamp}`);

// 차트 초기화 이벤트 (보정된 클라이언트 시간)
emitEvent('exchange:tradeData', {
  trade: {
    price: data.start_price,
    timestamp: clientTimestamp, // 🔥 보정됨
    quantity: 0,
    symbol: 'BTCUSDT',
  },
});

emitEvent('game:roundStart', {
  timestamp: clientTimestamp, // 🔥 보정됨
  roundNumber: this.serverRound,
  duration: 30,
  startPrice: data.start_price,
});
```

**실시간 가격 데이터 보정**:
```typescript
// handleBinancePrice 함수
private handleBinancePrice(data: any): void {
  // 서버 시간 동기화
  if (data.timestamp) {
    timeSync.sync(data.timestamp);
  }

  // 🔥 서버 timestamp를 클라이언트 시간으로 보정
  const serverTimestamp = data.timestamp;
  const offset = timeSync.getOffset();
  const clientTimestamp = serverTimestamp - offset;

  const tradeData = {
    price: data.price,
    timestamp: clientTimestamp, // 🔥 보정됨
    quantity: 0,
    symbol: data.symbol,
  };

  emitEvent('exchange:tradeData', { trade: tradeData });
}
```

#### 7.3 작동 원리

1. **Time Sync 시스템**:
   - 서버에서 받은 timestamp와 클라이언트 `Date.now()` 비교
   - Offset 계산: `offset = serverTime - clientTime`
   - 예: 3063ms (서버가 3초 빠름)

2. **Timestamp 보정**:
   - 서버 timestamp에서 offset을 빼서 클라이언트 시간으로 변환
   - `clientTimestamp = serverTimestamp - 3063`

3. **차트 렌더링**:
   - 차트는 `Date.now()`를 기준으로 위치 계산
   - 보정된 clientTimestamp를 사용하므로 정확한 위치에 표시

**효과**:
- ✅ 로컬(1ms): 기존과 동일하게 정확
- ✅ 한국(49ms): 3초 시간 차이 자동 보정
- ✅ 클라우드플레어(80ms): 자동 보정
- ✅ 깃발, 가격 데이터, 모든 이벤트의 타이밍 일치

---

### 8. 깃발 애니메이션 개선

#### 8.1 문제점
- 깃발 확대/축소 모션이 너무 빠르게 느껴짐
- 라운드 시작 시 깃발이 갑자기 나타나서 부자연스러움

#### 8.2 해결 방법

**`frontend/node_modules/@bc-game/chart/index.js`**

깃발 요소에 부드러운 CSS transition 추가:

```javascript
// 첫 번째 깃발 (flag0)
_("g", {
  ref: o,
  style: {
    opacity: 0,
    willChange: "transform",
    transition: "transform 0.8s ease-out, opacity 0.8s ease-out"  // 🔥 추가
  },
  children: [...]
})

// 두 번째 깃발 (flag1)
_("g", {
  ref: c,
  style: {
    opacity: 0,
    willChange: "transform",
    transition: "transform 0.8s ease-out, opacity 0.8s ease-out"  // 🔥 추가
  },
  children: [...]
})
```

#### 8.3 Transition 설명

- **Duration**: `0.8s` - 깃발이 0.8초 동안 부드럽게 이동/표시
- **Easing**: `ease-out` - 처음엔 빠르게 시작, 끝에선 천천히 감속 (자연스러운 효과)
- **Properties**: `transform, opacity` - 위치 이동과 투명도 변화 모두 부드럽게

**효과**:
- ✅ 라운드 시작 시 깃발이 부드럽게 나타남
- ✅ 확대/축소 시 자연스러운 모션
- ✅ 차트 스크롤 시에도 부드러운 위치 이동

**조정 가능한 옵션**:
- 더 천천히: `1.2s` 또는 `1.5s`
- 더 빠르게: `0.5s` 또는 `0.3s`
- 다른 easing: `ease-in-out` (균등), `ease` (표준), `linear` (일정 속도)

---

## 알려진 이슈 및 해결

### 이슈 1: 차트 precision 설정
- **문제**: 차트 precision이 이미 이전에 2로 설정되어 있었음
- **해결**: 추가 변경 불필요, LivePrice 컴포넌트만 수정

### 이슈 2: Timestamp 5804년 표시
- **문제**: 서버 timezone 설정 누락으로 인한 timestamp 오류
- **해결**: 모든 백엔드 파일에 `date_default_timezone_set('Asia/Seoul')` 추가

### 이슈 3: 빌드 파일 경로
- **문제**: 빌드 후 브라우저에서 변경사항이 보이지 않음
- **해결**: `cp -r frontend/dist/apps/up-down/* public/` 명령어로 public 폴더에 복사

### 이슈 4: dist 폴더 삭제 사고
- **문제**: 디버깅 중 `dist` 폴더 삭제로 빌드 불가 상태
- **원인**: `node_modules/@bc-game/*`가 `dist/@bc-game/*`에 symlink로 연결됨
- **해결**: 휴지통에서 `dist` 폴더 복원 후 빌드 성공

### 이슈 5: Statistics API 오류
- **문제**: `{"success":false,"error":"Failed to fetch statistics"}`
- **원인**: DB 컬럼명 불일치 (`timestamp` vs `created_at`, `session` vs `session_id`)
- **해결**: 간소화된 쿼리로 재작성, 에러 시 기본값(0) 반환

### 이슈 6: 네트워크 지연으로 인한 깃발 타이밍 불일치 ⭐
- **문제**: 한국 컴퓨터에서 깃발이 약 3초 늦게 표시됨
- **원인**: 서버-클라이언트 시스템 시간 차이 (3063ms)
- **근본 원인 분석**:
  ```
  서버 시간: 2026-01-27T06:32:27.232Z
  클라이언트: 2026-01-27T06:32:24.169Z
  차이: 3063ms (약 3초)
  ```
- **해결**:
  - `timeSync.getOffset()`으로 시간 차이 계산
  - 모든 서버 timestamp를 클라이언트 시간 기준으로 보정
  - 라운드 시작 이벤트 + 실시간 가격 데이터 모두 적용
- **결과**: 모든 환경에서 정확한 타이밍 동기화

---

## 최종 빌드 정보

**빌드 시간**: 2026-01-27 16:10 (KST)

**최종 빌드**:
```bash
dist/apps/up-down/index.html                  1.25 kB │ gzip:  0.62 kB
dist/apps/up-down/assets/index-CbW_-1O6.js  679.11 kB │ gzip: 216.91 kB
```

**배포 경로**: `C:\laragon\www\bc-game-updown\public\`

**배포 명령어**:
```bash
cd frontend && npx nx build up-down --skip-nx-cache
cp -r frontend/dist/apps/up-down/* public/
```

---

## 변경 이력

### 2026-01-27 (오전)
- ✅ 소숫점 4자리 → 2자리 변경 (9개 파일)
- ✅ Statistics API 생성
- ✅ Benefit 컴포넌트 실시간 데이터 연동
- ✅ Statistics 컴포넌트 실시간 통계
- ✅ Balance Store 생성
- ✅ WebSocket my_bets/user_balance 전송
- ✅ How It Works Modal 추가
- ✅ Timezone 설정 (Asia/Seoul)
- ✅ TIE tolerance 조정 (0.0001 → 0.01)
- ✅ 초기 빌드 배포 (index-Bgy9nGp_.js)

### 2026-01-27 (오후)
- ✅ dist 폴더 삭제 사고 복구
- ✅ Statistics API 쿼리 오류 수정
- ✅ 네트워크 지연 문제 분석 (3초 시간 차이 발견)
- ✅ Time Sync 보정 로직 구현
- ✅ 라운드 시작 이벤트 timestamp 보정
- ✅ 실시간 가격 데이터 timestamp 보정
- ✅ 깃발 애니메이션 부드럽게 개선 (0.8s ease-out transition)
- ✅ 최종 빌드 배포 (index-CbW_-1O6.js)
