# VBT 메트릭 — Velocity-Based Training

> **핵심 가설**: bar speed가 부하(%1RM)의 가장 신뢰할 만한 객관적 지표.
> Roomfit은 cable speed와 force(icmd 기반)를 둘 다 측정 → VBT 풀스택 가능.

## 1. 속도 메트릭

### MCV — Mean Concentric Velocity

**정의**: concentric phase의 평균 속도. **VBT의 기준 지표.**
**공식**: `displacement / duration` (T-Force 표준).
```
MCV (mm/s) = |position_end − position_start| / (time_end − time_start) × 1000
```
**왜 displacement 기반**: 샘플 간격이 균일하지 않을 때 per-sample average보다 robust.
**언제 null**: `WeightMode.velocityMeaningful = false` (Iso/Hydro/Vibration).

**저장**: `reps.mean_conc_velocity`.

### MPV — Mean Propulsive Velocity

**정의**: concentric 중 **accel > -g**인 구간(propulsive phase)만의 평균 속도.
**왜 별도**: < 70% 1RM에서 마지막 구간은 감속(decel)이라 MCV가 노이즈를 머금음. MPV는 그 감속 구간을 잘라낸 더 깨끗한 신호.
**공식**:
```
propulsive 구간 = {sample : accel > -9810 mm/s²}  // -g 이하면 감속
MPV = displacement_propulsive / duration_propulsive × 1000
```
**언제 사용**: 가벼운 무게(< 70% 1RM), 탄도성 운동.
**저장**: `reps.mean_prop_velocity`.

### PCV — Peak Concentric Velocity

**정의**: rep 중 가장 빠른 순간 속도.
**저장**: `reps.peak_conc_velocity`.
**용도**: ballistic / plyometric. compound lift에서는 MCV를 보고 PCV는 보조.

### Eccentric Velocity

**`mean_ecc_velocity, peak_ecc_velocity`** — eccentric phase 분석용.
부호: 원시 raw는 음수(케이블이 줄어드는 방향)지만 **저장 시 절댓값**.
용도: eccentric 훈련(`WeightMode.eccentric`)에서 강도 트래킹.

## 2. 힘 메트릭

### Force (kg)

**정의**: rep 중 사용자에게 걸린 저항.
**Roomfit 차별점**: `fLoad`는 `icmd` 기반 직접 측정값 (다른 VBT 시스템은 accel × mass 추정).

**계산**:
- `mean_conc_force` = 평균 fLoadKg over concentric samples
- `peak_conc_force` = 최대 fLoadKg over concentric samples
- `mean_ecc_force, peak_ecc_force` 동일

**저장**: `reps.mean_conc_force` 외 4개 컬럼.

### Power (W)

**공식**: `Power = Force × Velocity` per sample, 평균/최댓값.
```
P_instant (W) = fLoadKg × 9.81 × (speedMmps / 1000)
mean_power = mean(P_instant) over concentric
peak_power = max(P_instant)
```
**언제 null**: `velocityMeaningful = false` (속도가 controller-clamped면 power 의미 없음).
**저장**: `reps.mean_conc_power, peak_conc_power`.

## 3. 일과 운동량 (BP advanced)

### Concentric / Eccentric Work — Joules

**정의**: rep 한 번에 한 기계적 일.
**공식**:
```
W (J) = Σ Force[i] × |Δposition[i, i+1]|
      = Σ (fLoadKg[i] × 9.81) × (|posMm[i+1] − posMm[i]| / 1000)
```
**저장**: `reps.concentric_work_cj, eccentric_work_cj`.
- 단위는 **centijoule (cJ = J × 100)**, 정수 변환 정밀도 확보.
- 도메인 모델은 `concentricWorkJ` (Joules) — repository write 시점에 ×100.

**용도**:
- 세션 누적 work = `total_work_kj` (workout_sessions)
- 메타볼릭 부하 추정 (calorie ≈ work × 1/efficiency)

### Concentric Impulse — N·s

**정의**: rep concentric 동안 force의 시간 적분.
**공식**:
```
J (N·s) = Σ Force[i] × Δt[i]
        = Σ (fLoadKg[i] × 9.81) × (Δt_ms / 1000)
```
**왜 별도**: ballistic movement에서 power가 misleading일 때 impulse가 진짜 임팩트 지표 (impulse-momentum theorem).
**저장**: `reps.concentric_impulse`.

### RFD — Rate of Force Development

**정의**: concentric 시작 직후 100ms간 force 증가 속도. **신경근 적응 지표**.
**공식**:
```
RFD (N/s) = (F_at_100ms − F_initial) / 0.1
```
**Roomfit 차별점**: 보통 RFD는 force-plate 같은 lab 장비로 측정. icmd가 ground truth라 50ms tick의 fLoad만으로 즉시 계산 가능.
**저장**: `reps.rfd_initial_100ms`. concentric 100ms 미만이면 `null`.

**해석**:
- 높은 RFD = 빠른 신경 활성
- 트레이닝으로 증가, 피로 시 감소
- 고급 athlete 추적의 hidden gem

## 4. ROM — Range of Motion

### Per-rep ROM

**`rom_mm`** = rep의 최대 position - 최소 position (mm).
**`concentric_rom_mm`** = concentric phase 내 max-min.
**`eccentric_rom_mm`** = eccentric phase 내 max-min.

이상적: concentric ≈ eccentric (대칭). 차이 크면 rep 품질 저하 (partial reps).

### Auto-learned ROM (per exercise)

`AutoRomLearner` (`packages/roomfit_exercise/lib/src/calibration/auto_rom_learner.dart`)가
종목별로 누적 학습:

- `position_min_mm, position_max_mm`: 사용자의 안정된 ROM 경계.
- `confidence`: rep 수 + 분포 안정도 기반 0~1 점수.
- 다음 세트의 force ramp-in/out 곡선 안내에 사용.

저장: `rom_estimates` (entry × side).

## 5. TUT — Time Under Tension

### `time_under_tension_ms` (= rep duration)

전체 rep 시간. 근비대 훈련의 1차 지표.

### `load_weighted_tut`

**공식**: `Σ fLoadKg[i] × Δt[i]` (kg·ms).
**왜 별도**: 같은 1초여도 50kg vs 100kg에서 메타볼릭 stress 차이. 부하 가중.
**저장**: `reps.load_weighted_tut`.

## 6. LVP — Load-Velocity Profile

### 모델

**선형 회귀**: `MCV = slope × load + intercept`.
- 대부분의 compound lift에서 R² > 0.95.
- Deadlift는 약간 비선형 (start friction)이지만 선형 근사로 충분.

### 파라미터

| 변수 | 의미 | 단위 |
|---|---|---|
| `slope` | load에 따른 velocity 감소율 | m/s per kg (음수) |
| `intercept` | load=0 가상 velocity (= V₀) | m/s |
| `v0` | 최대 무부하 속도 | m/s |
| `l0` | velocity=0 가상 load (= -intercept/slope) | kg |
| `mvt_used` | 1RM 추정에 사용한 MVT | m/s |
| `r_squared` | 회귀 적합도 | 0-1 |

### 측정 방법

**2-point method** (García-Ramos 2018, Roomfit 권장):
- ~40% 1RM warm-up + ~85% 1RM working set
- 2개 점으로 직선 fit, R² = 1
- 매 세션 자동 갱신 가능

**Multi-point method**:
- 4-5개 무게 (20%, 40%, 60%, 80% 1RM)
- 정확도 ↑, 피로 ↑

**구현**: `LoadVelocityProfile.fromPoints` (`packages/roomfit_exercise/lib/src/vbt/load_velocity_profile.dart`).
**저장**: `load_velocity_profile` 테이블 — 세션마다 새 row insert (immutable snapshot).

### Drift 모니터링

`profile.driftFrom(previous)` = `|Δslope| / |slope_prev|`.
- 매 세션 ~2-5% drift = 정상 훈련 적응
- 갑자기 큰 변화 = 부상/큰 컨디션 변화 시그널

## 7. e1RM — Estimated 1-Repetition Maximum

### MVT 기반 역산

**공식**: `1RM = (MVT − intercept) / slope`.

### MVT — Minimum Velocity Threshold

**정의**: 사용자가 1RM에서 보이는 MCV. 종목 고정 상수에 가까움.
**문헌 평균치** (Pérez-Castilla 2021):

| 종목 | MVT (m/s) |
|---|---|
| Deadlift | 0.15 |
| Bench Press | 0.17 |
| Overhead Press | 0.20 |
| Bent Row | 0.25 |
| Back Squat | 0.30 |

**저장**: `exercise_metadata.default_mvt_mps`.
**Dart**: `ExerciseMvt.benchPress`, `ExerciseMvt.forExerciseId(id)`.

### 단일 rep e1RM (LVP 없을 때 fallback)

`E1rmEstimator.fromSingleRep(loadKg, mcvMps)` —
사용자 LVP가 없을 때 **인구 평균 slope**(bench -0.010, squat -0.012)로 단일 rep extrapolation.

```
1RM ≈ load + (mcv − MVT) / |slope_assumed|
```

LVP 만들어지면 그 결과 사용 (정확도 ↑).

**저장**: `exercise_sets.estimated_1rm_kg` (per-set), `load_velocity_profile.estimated_1rm_kg` (LVP 기반).

## 8. Velocity Loss

### Set-level VL%

**정의**: 세트 내 첫 빠른 rep과 마지막 rep의 MCV 차이 비율.
**공식**:
```
baseline = max(MCV[0..2])  // 첫 3개 중 best
VL% = (baseline − MCV_last) / baseline × 100
```
**저장**: `exercise_sets.velocity_loss_pct`.

### Auto-cutoff threshold

2024 meta-analysis 합의:

| 목적 | VL% threshold | 저항 효과 |
|---|---|---|
| Power / 스피드 | **10%** | RIR > 5, 신경 적응 우선 |
| Max strength | **20%** | 1-3 RIR, 1RM 증가 최적 |
| Hypertrophy | **30%** | 더 많은 volume + TUT, 피로 비용 ↑ |
| (avoid) | 40%+ | 회복 지연, 비추 |

`VelocityLossMonitor` (`packages/roomfit_exercise/lib/src/vbt/velocity_loss_monitor.dart`):
- `power() / strength() / hypertrophy()` 프리셋
- **2회 연속 threshold 초과** 시 cutoff 권장 (false-positive 방지)
- `cutoffTrigger = 'velocity_loss_20'` 등 DB enum과 매핑

## 9. Roomfit-only metrics

### `icmd_ifb_rms_error_ma`

**정의**: rep 동안 모터 명령 vs 피드백 전류 차이의 RMS.
**공식**:
```
RMS = sqrt(mean((icmd − ifb)²))
```
**용도**: 모터 컨트롤 품질 지표. 큰 값 = hardware drift, calibration drift, load-cell saturation.
**저장**: `reps.icmd_ifb_rms_error_ma`.

### `region_transitions`

rep 중 region 변경 횟수. 이상치 = 자세 흔들림 / partial rep.

### `icmd_fload_error_pct`

icmd로부터 계산한 force와 fLoad 사이의 일치도. 펌웨어 측 환산식 검증용.

## 10. 데이터 제외 규칙

### 비-VBT 모드에서 null 처리

- `Isokinetic`: PI controller가 cable speed를 `Vtarget`에 클램프. **MCV가 controller artifact**.
- `Hydraulic`: `F = base × (v/Vref)^n`. **velocity가 force 정의에 들어감**.
- `Vibration`: 진동이 velocity에 주기적 노이즈. **시간 평균 의미 없음**.

→ `velocityMeaningful = false`이면 모든 velocity/power 메트릭 `null`.
대안 메트릭 (impulse, work, peak torque)은 계속 측정.

### Eccentric phase 길이 0

eccentric samples가 비어있거나 duration=0이면 `mean_ecc_*, peak_ecc_*` `null`.

## 관련 코드

- 메트릭 정의: `packages/roomfit_exercise/lib/src/metrics/completed_rep.dart`
- 계산기: `packages/roomfit_exercise/lib/src/metrics/calculators/standard_vbt_calculator.dart`
- LVP / e1RM: `packages/roomfit_exercise/lib/src/vbt/`
- 저장 매핑: `lib/features/workout/data/supabase_workout_repository.dart`

## 외부 레퍼런스

- García-Ramos 2018 — 2-point LVP method
- Pérez-Castilla 2021 — group-average MVT
- 2024 PMC meta-analysis (PMC9914552) — VL threshold consensus
- Vitruve / GymAware 공식 docs
