미결 7번(프레임레이트에 따라 점수가 달라진다)의 원인을 규명했다. 수정은 하지 않았다 — 이번 단계는 원인 규명 전용이다.

먼저, 이 질문에는 정답이 필요 없다

“어느 fps가 옳은가”는 묻지 않았고 답하지도 않았다. 그건 팔 종목 릴리스 프레임의 정답을 요구하고, 그 정답은 미결 5번이 보류된 이유 그대로 없다.

대신 물은 것은 “왜 달라지는가” 하나다. 같은 영상이 같은 점수를 받아야 한다는 요구는 측정 없이 성립하므로, 이 항목은 정답 없이 끝까지 갈 수 있다.

결론부터

원인은 하나가 아니다. 구조 하나 위에 섭동 둘이다.

  • 구조 — segment_phases의 전역 argmax는 승자 마진이 얇다. 1위와 2위의 비율이 p10 1.12배, 마진 1.5배 미만이 29%다.
  • 섭동 1 — 데시메이션하면 argmax 후보가 짝수 프레임만 남는다(후보 격자 축소).
  • 섭동 2 — np.gradient의 중심차분 스텐실이 물리 시간으로 2배 넓어진다.

두 섭동의 기여가 거의 같다. 그리고 스텐실 확대는 균일하지 않다 — 날카로운 사건을 완만한 사건보다 2.33배 더 깎는다. 이게 핵심이다.

데이터

PitcherMotion 400클립(MLB 투구 포즈, 60fps)을 [::k]로 솎아 30·20·15·12fps를 만들었다. 같은 물리 프레임의 정확한 부분집합이라 샘플링·검출 오차가 섞이지 않는다. GPU는 쓰지 않았다 — 포즈가 이미 들어 있는 데이터라 재추출이 필요 없었다.

08-26 증상부터 재현했다. 60 vs 30 공통 264건에서 팔꿈치각 |Δ| > 10도가 45%로 그때 기록한 50%와 같다.

2×2 분해 — 격자냐 스텐실이냐

두 섭동을 떼어 놓기 위해 미분값과 후보 집합을 각각 고정했다. B는 미분값은 60fps 그대로 두고 후보만 짝수 프레임으로 제한한 경우다.

경로 같은 프레임 |이동| 중앙 평균
C→A 전체 (60fps → 30fps 실제) 33% 1 16.1
C→B 후보 격자만 49% 1 22.9
B→A 스텐실만 48% 2 26.6

둘의 기여가 거의 같다. 그리고 C→B 이동의 63%는 1프레임 이내인데 평균이 22.9프레임이다 — 대부분은 격자 반올림으로 1프레임 밀릴 뿐이지만, 이기던 프레임이 격자에서 빠지는 순간 승리가 클립 반대편으로 넘어간다.

넘어간 자리가 어디인지도 봤다. 30fps 승자 자리의 91%는 60fps에서도 이미 국소 피크였다. 저 fps가 없던 인공물을 만든 게 아니라, 원래 근소하게 지고 있던 경쟁자가 이긴 것이다.

왜 하필 진짜 사건이 지는가 — 차등 배율 2.33배

중심차분 스텐실이 넓어지면 각속도가 얼마나 회수되는지 재 봤다. 선형이면 1.00이다.

   
60fps 임팩트 프레임에서 0.46
완만한 구간(하위 50%)에서 1.06
차등 배율 2.33배

넓은 창은 릴리스처럼 두세 프레임 안에 끝나는 사건을 절반 이하로 깎고, 와인드업 처럼 넓게 퍼진 움직임은 그대로 통과시킨다. 마진이 1.12~1.37배뿐인 경쟁에서 한쪽만 2.33배 불리하면 승자가 바뀐다.

20fps 이하에서 팔꿈치각 중앙이 155도에서 121도로 무너지는 절벽이 이것이다. 60과 30이 비슷했던 것은 파이프라인이 견고해서가 아니라 2배 관계라 격자가 포개졌기 때문이다.

하류에서 얼마나 커지는가

지표 동일률이 29~42%다. 임팩트 프레임 하나가 거의 모든 지표를 읽는 자리라 프레임이 흔들리면 전부 흔들린다.

야구 투구 루브릭으로 등급까지 태우면(264건):

   
릴리스 팔 신전 항목 등급 변화 36%
최종 등급(A~D) 변화 37%
총점 동일 46%
|Δ총점| 중앙 / 최대 4점 / 55점

경계 밀집도 있지만(골반-어깨 분리 21%가 구간폭 10% 안) 부차적이다. 크기의 주된 몫은 임팩트가 멀리 튀면서 지표 값 자체가 달라지는 데서 온다.

프레임 단위 규칙도 딸려 간다. impact - first < 2는 60fps에서 0.033초를, 12fps에서 0.167초를 요구하는 셈이라 저 fps에서 반려가 5배로 는다 (60fps 2건 → 15·12fps 각 10건).

배제한 것 두 가지

샘플링은 원인이 아니다. 프레임 번호를 밝기로 새긴 합성 영상으로 read_frames가 고른 자리를 역추적했더니 60fps 원본과 30fps 판이 같은 물리 인덱스(0 4 8 12 …)를 골랐다. 단, 이건 2배 관계라서 그렇다 — 아래 “덤” 참고.

각도 계열도 원인이 아니다. 같은 물리 프레임에서 잰 팔꿈치각의 최대 오차가 1.27e-10도다(379클립 전수). joint_angle은 평행이동·스케일에 불변이고 시계열에 시간 필터가 없다. 그래서 팔꿈치각 차이의 100%가 “어느 프레임을 골랐는가”에서 온다. 검출도 정규화도 아니다.

미분의 단위도 임팩트를 옮기지 못한다. argmax는 양의 상수배에 불변이라 도/프레임을 도/초로 고쳐도 고르는 프레임은 한 프레임도 안 바뀐다. 단위가 실제로 새는 곳은 follow_through_duration_frames(60·30 동일 6%)와 impact_frame (중앙비 정확히 0.50)이고, 둘 다 fps에 따라 변한 값이 판정 근거로 넘어간다.

부수 원인 — 스윙 팔도 fps에 따라 뒤집힌다

identify_limb의 travel이 경로길이 합이라 프레임 수만큼 누적된다. 흔들리는 관절은 노이즈가 쌓이고 진짜 빠른 관절은 그렇지 않아, 데시메이션이 둘을 다르게 줄인다. 60fps 대비 스윙 팔이 달라지는 비율이 30fps 5%, 12fps 10%다. 임팩트 경로와 독립이고 미결 6번과 같은 코드다.

덤 — 함께 나온 인접 결함 둘

step이 정수라 정수배가 아닌 소스는 격자가 어긋난다: 24fps → 실효 12.0, 25fps → 12.5, 50fps → 16.67. 같은 영상을 25와 50으로 인코딩하면 0.08초 격자와 0.06초 격자로 갈린다.

max_frames=300이 덮는 실시간 길이도 fps에 따라 다르다: 25fps 소스는 24.0초, 50fps 소스는 18.0초. 긴 영상에서 뒷부분이 다르게 잘린다.

둘 다 이번 증상의 원인은 아니지만 미결 7번 안에 기록해 뒀다.

미결 5번과 같은 뿌리다

5번이 축구 16건에서 본 “신호는 언제나 상위권인데 1.37·1.41·1.51배 차이로 진다”가 야구 400클립에서 마진 p10 1.12배로 재현됐다. 얇은 마진은 leg 종목만의 사정이 아니라 전역 argmax 정의 자체의 성질이다. 5번의 “편향이 아니라 산포다”도 그대로다 — 클립 길이의 5% 넘게 점프한 20%가 앞뒤 50:50이었다.

그래도 7번을 고쳐도 5번은 열리지 않는다. 격자와 스텐실을 fps 불변으로 만들면 같은 영상이 같은 점수를 받게 되지만, 그렇게 고정된 프레임이 실제 릴리스인지는 여전히 모른다. 5번의 재개 조건은 그대로다.

다음

수정은 원인이 확정된 뒤 별도 단계에서 한다. 정답 없이 검증되는 것부터다 — 스텐실을 물리 시간으로 고정(섭동 2), 각도 계열을 공통 시간 격자로 리샘플(섭동 1). 이 둘이면 fps 불변성은 확보되고, 탐색 범위 제한은 5번이 재개될 때 그 위에 얹는다.

production code는 한 줄도 바꾸지 않았다. 테스트 109건 전부 통과.