상태: 초안 (2026-10-26), 실측 반영. 규칙과 동작은 2026-10-25 기준 저장소 코드에서 읽었고, 녹화 편집의 숫자는 2026-10-13 에 공개 경기 영상으로 직접 잰 값입니다. 휴대폰에서의 녹화 비용(배터리·발열)과 사람이 득점을 누르는 늦음은 미실측입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」으로 표시합니다.
목차
- 1. 왜 손 점수판부터인가
- 2. 규칙 엔진: 규정집 조문을 코드로
- 3. 준비 화면: 경기 전에 정하는 것
- 4. 상태가 아니라 이력
- 5. 잘못 누름 막기와 리모컨
- 6. 점수 읽어 주기
- 7. 화면: 전체 화면, 점수판 크기, 꺼지지 않기
- 8. 녹화와 쉬는 시간 빼기
- 9. 복식은 어디까지
- 10. 정리
- 참고문헌
- 함께 읽기
1. 왜 손 점수판부터인가
TT Lab 의 대표 기능은 카메라 점수판이지만, 그 안에서 가장 많이 쓰이는 것은 자동 판정이 아니라 사람이 누르는 점수판입니다. 이유는 간단합니다. 자동 판정은 서버 GPU 와 로그인과 시간 몫이 필요하고, 조건이 나쁘면 「확인 필요」를 자주 띄웁니다. 손 점수판은 휴대폰 하나로 언제나 돕니다.
그래서 이 점수판은 자동 판정이 없어도 완결된 제품이어야 했고, 자동 판정은 그 위에 「제안」으로 얹히도록 설계했습니다(3편). 이 글은 그 바탕이 되는 규칙 엔진과 손 점수 기능을 다룹니다.
2. 규칙 엔진: 규정집 조문을 코드로
근거 문서
규칙은 ITTF 규정집(Statutes 2026, 2026-01-01 시행) 2장 「The Laws of Table Tennis」를 따릅니다[1]. 조문 번호는 원문을 열어 확인했고, 아래에는 우리 말로 줄여 적습니다.
| 조문 | 내용(요약) | 코드 |
|---|---|---|
| 2.11.1 | 먼저 11점을 낸 쪽이 게임을 이긴다. 둘 다 10점이 되면 그 뒤 먼저 2점 앞선 쪽이 이긴다 | award() 의 「11점 이상이면서 2점 차」 |
| 2.12.1 | 경기는 홀수 게임 중 과반을 이긴 쪽이 이긴다 | games_to_win 2·3·4 = 3·5·7게임제 |
| 2.13.3 | 2점마다 서브가 바뀐다. 10:10 이 되면 순서는 같고 한 사람이 1점씩만 서브한다 | turnIndex() |
| 2.13.6 | 한 게임에서 먼저 서브한 쪽은 다음 게임에서 먼저 리시브한다 | servingTeam() |
| 2.13.7 | 게임이 끝나면 자리를 바꾼다. 마지막 게임에서는 한쪽이 먼저 5점을 내면 자리를 바꾼다 | leftPlayer(), award() 의 decider |
| 2.13.4–2.13.6 | 복식의 첫 서버·리시버, 순환, 마지막 게임의 리시브 순서 바꾸기 | cycleFor(), dswap |
| 2.14.3 | 서브·리시브·자리 순서가 틀렸으면 발견 즉시 바로잡고, 그전에 난 점수는 모두 인정한다 | 「서브권 직접 지정」(4절) |
헷갈리기 쉬운 것 하나. 게임 사이 자리 바꾸기는 2.13.7 이고, 바로 앞의 2.13.6 은 서브·리시브 순서입니다. 번호가 붙어 있어 섞어 인용하기 쉽습니다.
싣지 않은 것도 있습니다. 촉진 규칙(2.15, 10분이 지나면 1점씩 교대 서브하고 리시버가 13번 바르게 넘기면 리시버 득점)은 점수판에 넣지 않았습니다. 사이트의 규칙 퀴즈에만 있습니다. 타임아웃과 게임 사이 휴식 시간은 규정 원문과 대조하지 못해 넣지 않았습니다.
원본은 파이썬, 화면은 자바스크립트 사본
규칙의 원본은 자동 판정 엔진의 ttscore/rules.py 입니다. 휴대폰 화면은 서버 없이 점수를 세야 하므로, 같은 규칙을 자바스크립트로 옮긴 backend/static/tt/play/lib/scorekeeper.mjs 를 씁니다. 두 구현은 같은 시험 사례 파일(backend/static/tt/play/data/rules-cases.json)을 읽습니다. 파이썬 시험은 엔진으로, node 시험은 자바스크립트로 같은 사례를 돌리므로 한쪽만 고치면 다른 쪽이 떨어집니다. 사이트의 규칙 안내 글에 나오는 숫자(11점, 2점 차, 5점 교대 등)도 한곳에 모아 두고 시험이 엔진과 대조합니다.
서브 차례: 한 줄의 식
서브 차례는 「지금까지 난 점수의 합」만으로 정해집니다.
// backend/static/tt/play/lib/scorekeeper.mjs turnIndex() 에서
const total = points.A + points.B;
const deuceAt = 2 * (points_to_win - 1); // 11점 게임이면 20 = 10:10
return total < deuceAt ? Math.floor(total / 2) // 2점마다 한 차례
: deuceAt / 2 + (total - deuceAt); // 10:10 부터 1점마다
차례 번호가 짝수면 그 게임의 첫 서버, 홀수면 상대가 서브합니다. 그 게임의 첫 서버는 홀수 게임이면 처음 정한 쪽, 짝수 게임이면 상대입니다(2.13.6). 점수판이 「서브 상태」를 따로 저장하지 않는다는 점이 중요합니다. 서브는 점수와 첫 서버에서 계산되는 값이라, 점수가 바뀌면 서브는 저절로 맞습니다. 이 성질이 4절의 「이력」 설계를 가능하게 합니다.
자리 바꾸기: 세는 것은 횟수뿐
화면 왼쪽에 누가 서 있는지도 계산입니다.
// leftPlayer() 에서 줄임
const swaps = (game_no - 1) + (decider_swapped ? 1 : 0);
return swaps % 2 === 0 ? start : other(start);
게임마다 한 번, 그리고 마지막 게임에서 5점이 되는 순간 한 번 더 바꿉니다. 마지막 게임은 game_no === 2 × games_to_win − 1 이고, 5점은 floor((points_to_win + 1) / 2) − 1 로 계산합니다. 11점 게임이면 5입니다. 마지막 게임에서 5점 교대가 일어나면 award() 가 endChange 를 돌려주고, 화면은 「마지막 게임 5점 — 자리를 바꿔요!」를 띄웁니다.
경기가 끝나면
이긴 게임 수가 games_to_win 에 닿으면 winner 가 정해지고 그 뒤의 득점은 무시합니다. 끝난 게임의 점수(예: 11:9, 12:10)는 finished 목록에 남아 기록으로 넘어갑니다(6편).
3. 준비 화면: 경기 전에 정하는 것
/tt/play/cam 의 「경기 준비」에서 고르는 것은 이렇습니다(backend/static/tt/play/cam.js).
- 경기 길이: 3·5·7게임제(기본 5게임제).
- 별명: 12자까지. 입력 칸에 「실명 대신 별명」 안내가 붙습니다(6편).
- 먼저 서브할 쪽, 화면 왼쪽 선수, 나는 어느 쪽.
- 잘못 누름 막기(5절).
- 「카메라 켜고 시작」 또는 「카메라 없이 점수판만」.
카메라를 켜면 바로 경기가 시작되지 않고 몸풀기 상태로 들어갑니다. 몸풀기 중에 득점 칸을 누르면 점수를 넣지 않고 「경기 시작」 단추를 흔들어 알려 줍니다. 몸풀기 중에 받는 조정은 서브권 지정과 좌우 바꾸기 둘뿐입니다. 왜 이런 상태가 필요한지는 자동 판정과 얽혀 있어 3편에서 자세히 다룹니다. 손 점수판만 보면 「몸풀기 랠리를 점수로 세지 않기 위한 문」입니다.
4. 상태가 아니라 이력
문제: 한참 뒤에 알아챈 실수
손 점수판의 가장 흔한 실수는 잘못 누른 점수를 한참 뒤에 알아채는 것입니다. 휴대폰은 탁구대에서 몇 미터 떨어진 삼각대 위에 있고, 선수는 점수판을 매 랠리 보지 않습니다. 세 점쯤 지나서 「아까 그거 내 점수였는데」가 나옵니다.
점수판이 지금 상태(7:5, B 서브, A 가 왼쪽)만 저장한다면 이 실수를 고치기가 어렵습니다. 7:5 를 6:6 으로 바꾸면 서브 차례는? 그 사이 게임이 끝났다면? 상태를 손으로 고치는 순간 규칙과 어긋날 여지가 생깁니다.
해법: 이력을 저장하고 다시 돌린다
카메라 점수판의 경기 객체(backend/static/tt/play/lib/camscore.mjs 의 CamMatch)는 상태가 아니라 이력을 저장합니다. 이력의 한 칸은 「A 득점」, 「B 득점」 같은 득점이거나, 「서브권 바꾸기」 같은 조정입니다. 화면에 보이는 점수는 언제나 빈 점수판에서 이력을 처음부터 다시 돌린 결과입니다.
// camscore.mjs 에서 줄임
remove(i) { // i 번째 칸을 지우고 뒤를 다시 돌린다
const [e] = this.log.splice(i, 1);
this._replay(); // 새 Scoreboard 에 남은 칸을 처음부터 넣는다
return e;
}
이렇게 하면 「점수 조정」 시트의 이력 목록(최근 60칸)에서 아무 칸이나 지울 수 있고, 지우면 그 뒤의 서브 차례·자리 바꾸기·게임 끝이 규칙대로 저절로 다시 맞습니다. 다시 돌리는 비용은 걱정할 필요가 없습니다. 경기 하나는 많아야 수백 칸이고, 한 칸을 넣는 일은 정수 몇 개를 더하는 것뿐입니다. 이 원칙은 자동 판정의 사람 확인 설계에서도 같습니다(3편).
조정도 이력의 한 칸이다
점수 조정 시트에서 할 수 있는 것은 점수·게임 올리고 내리기, 서브권 직접 지정, 「이 게임 다시 시작(0:0)」, 선수 좌우 바꾸기, 이력 칸 지우기입니다. 이 가운데 둘은 설계가 재미있습니다.
- 서브권 직접 지정은 「첫 서버 뒤집기」다. 2절에서 봤듯 서브는 점수와 첫 서버로 계산되는 값이라 따로 덮어 둘 자리가 없습니다. 그래서 「지금 서브는 B 다」라고 고치면, 코드는 이 게임의 첫 서버를 뒤집는 조정 칸을 넣습니다. 그 뒤로도 2점마다(10:10 부터 1점마다) 규칙대로 돕니다. 지금까지의 점수는 그대로 둡니다. 서브 순서가 틀렸어도 그전 점수는 모두 인정한다는 규정 2.14.3 과 같은 동작입니다.
- 손으로 올린 게임은 기록에서 뺀다. 게임 수를 손으로 하나 올리면, 그 게임이 몇 대 몇이었는지 점수판은 모릅니다. 코드는 끝난 게임 목록에
adj표시를 단 칸(이긴 쪽 11:0)을 넣어 경기 흐름은 맞추되, 경기를 기록으로 넘길 때는 그 게임을 빼고 사람이 직접 적게 합니다. 지어낸 11:0 이 레이팅에 들어가면 안 되기 때문입니다.
되돌리기 단추는 이력의 마지막 칸을 지우는 것과 같습니다. 「다시 시작」은 확인을 받은 뒤 이력을 비우고 몸풀기로 돌아갑니다.
저장: 매 변화마다 휴대폰에
이력은 바뀔 때마다 브라우저 localStorage(ttplay:cam)에 저장됩니다. 화면을 실수로 닫거나 전화를 받고 와도 경기가 그대로 있습니다. 설정은 따로(ttplay:cam-prefs) 저장합니다. 서버에는 아무것도 가지 않습니다.
5. 잘못 누름 막기와 리모컨
득점 칸은 화면 반쪽 전체
멀리 세운 휴대폰을 정확히 누르기는 어렵습니다. 그래서 득점 칸은 작은 단추가 아니라 화면 반쪽 전체입니다. 어디를 눌러도 그쪽 선수 점수입니다. 대신 지나가다 툭 건드리는 일도 늘어나므로, 준비 화면에서 막는 방식을 고릅니다.
| 방식 | 동작 | 상수 |
|---|---|---|
| 없음 | 한 번 누르면 들어감 | — |
| 두 번 | 같은 쪽을 1.5초 안에 두 번 눌러야 들어감. 첫 번째는 「한 번 더」 표시만 | DOUBLE_MS = 1500 |
| 길게 | 0.5초 이상 누르고 있어야 들어감 | HOLD_MS = 500 |
리모컨과 셀카봉
손이 닿지 않는 휴대폰을 위해 키 입력도 받습니다.
| 동작 | 기본 키 |
|---|---|
| 왼쪽 선수 득점 | ←, PageUp, 볼륨 올리기 |
| 오른쪽 선수 득점 | →, PageDown, 볼륨 내리기 |
| 되돌리기 | Backspace, u, z |
| 경기 시작 | Enter, 재생/일시정지, s |
발표용 리모컨은 대개 ←·→ 나 PageUp·PageDown 을, 블루투스 셀카 리모컨은 대개 볼륨 올리기나 Enter 를 보냅니다(코드 주석). 문제는 안드로이드·iOS 브라우저가 볼륨 키를 페이지에 넘기지 않고 소리 크기만 바꾸는 경우가 많다는 것입니다. 그래서 「키 배우기」를 두어, 리모컨 단추를 한 번 누르면 실제로 들어오는 키를 그 동작에 걸게 했습니다. 한 키가 두 동작에 걸리면 뒤의 것을 버립니다.
「경기 시작」 키는 몸풀기 중에만 듣습니다. 경기 중에 리모컨을 잘못 눌러 경기가 처음으로 돌아가면 안 되기 때문입니다. 경기 중 다시 시작은 화면에서 확인을 받아야만 됩니다.
6. 점수 읽어 주기
득점할 때마다 점수를 소리로 읽어 줍니다(backend/static/tt/play/lib/cam-speech.mjs).
- 브라우저의 음성 합성(
speechSynthesis)을 씁니다. 서버로 보내는 것은 없습니다. - 한국어·영어·일본어를 지원하고, 속도는 보통(1.0)과 빠르게(1.35) 둘입니다.
- 실제 심판처럼 서버 쪽 점수를 먼저 읽습니다. 듀스 뒤 한 점 앞서면 「어드밴티지」를, 서브가 바뀌면 「서브 교대」를 덧붙입니다.
작은 함정 하나가 코드에 남아 있습니다. 카메라 점수판은 첫 화면을 가볍게 하려고 무거운 기능을 쓸 때 부르는데, 말하기 함수만은 늦게 부르지 않고 첫 화면 코드에 남겼습니다. iOS 사파리는 사람이 누른 그 순간에 한 번 말해야 그 뒤로 소리가 나고, 모듈을 기다렸다 말하면 이미 늦기 때문입니다.
7. 화면: 전체 화면, 점수판 크기, 꺼지지 않기
- 전체 화면(
lib/fullview.mjs): Fullscreen API 가 되면 그것으로 주소창까지 숨깁니다. 아이폰 사파리는 일반 요소에 전체 화면 요청 함수가 없어서(video 요소에만 있음), 그때는 화면을 꽉 채우는 고정 배치로 대신하고 「홈 화면에 추가」를 한 번 안내합니다. - 점수판 크기와 자리(
lib/boardview.mjs): 보통·크게·숨기기 세 모드, 크기 0.8·1·1.3배, 자리 다섯 곳(위 왼쪽·가운데·오른쪽, 아래 왼쪽·오른쪽). 화면과 녹화가 같은 설정을 읽으므로 녹화본에도 화면에서 본 그대로 점수판이 박힙니다. - 가로 고정과 화면 꺼짐 막기: 가로 방향 고정을 시도하고, Wake Lock 으로 경기 중 화면이 꺼지지 않게 합니다.
8. 녹화와 쉬는 시간 빼기
폰 안에서 녹화하고, 폰 안에서 자른다
녹화는 카메라 영상 위에 점수판을 canvas 로 합성해 휴대폰에 저장합니다(cam-rec.js, lib/cam-record.mjs). 서버로 올리지 않습니다. 인코더는 WebCodecs 가 되면 그것으로 직접 H.264 를 만들고, 안 되면 MediaRecorder 를 씁니다.
경기 녹화를 그대로 두면 절반 넘게가 공 줍기, 수건, 대화입니다. 그래서 녹화를 멈출 때 랠리만 남긴 편집본을 폰 안에서 만듭니다(기본 켜짐). 문제는 「랠리가 어디 있었나」를 어떻게 아느냐입니다.
녹화 중에 적어 두는 표시 셋
lib/breakedit.mjs 의 MarkLog 가 녹화 시간축 위에 세 가지를 적습니다.
- 손 득점 시각. 사람이 득점 칸을 누른 시각입니다. 자동 판정의 칩에 답한 것은 넣지 않습니다(2번과 겹침).
- 자동 판정 도착 시각과 랠리 길이. 자동 점수를 켰을 때만 있습니다.
- 화면 움직임. 초당 8번, 128×36 회색조로 줄인 화면에서 탁구대 둘레 띠(가로 10–90%, 세로 33–72%)의 바뀐 화소 비율을 잽니다.
남길 구간 계산 규칙
keepSpans() 의 규칙은 이렇습니다.
| 표시 | 남기는 구간 | 근거 |
|---|---|---|
| 손 득점(누른 시각 t) | [t − 10초, t] | 손으로 셀 때 아는 것은 누른 시각 하나뿐이고, 랠리는 그 앞에 끝났다. 10초는 아래 실측 |
| 자동 판정(도착 시각 a, 랠리 길이 d) | [a − d − 1.0 − 1.2, a + 1.5] | 판정은 망·처리 때문에 늦게 닿으므로 그 늦음(1.0초, 추정)만큼 더 앞을 남긴다. 앞 1.2초·뒤 1.5초는 서브 자세와 득점 반응(업로드 편집본과 같은 값) |
| 화면 움직임 | 손 득점 창의 앞머리가 움직임 한가운데면 움직임이 시작된 곳까지 최대 30초 늘리기만 | 10초보다 긴 랠리(약 9%)를 살린다. 줄이지는 않는다 — 서브 준비는 조용해서 움직임만 믿고 자르면 서브가 잘린다 |
| 공통 | 앞뒤 1초 여유, 0.5초 안으로 붙은 구간은 합침 | — |
핵심은 움직임이 구간을 새로 만들지 않는다는 점입니다. 득점이나 판정 표시가 없는 시간, 곧 몸풀기와 쉬는 시간은 아무리 움직임이 많아도 남지 않습니다. 몸풀기 랠리는 움직임으로 보면 경기와 똑같기 때문입니다.
편집본을 만들지 않는 경우도 있습니다. 표시가 하나도 없으면(손 점수도 자동 판정도 없었음), 또는 5초 미만이나 원본의 10% 미만으로만 줄어들면 만들지 않습니다. 앞에 2.5초 제목 카드를 붙이므로 오히려 길어질 수 있기 때문입니다.
실측: 왜 10초인가
사람이 랠리가 끝나고 몇 초 뒤에 누르는지(d)는 재지 못했습니다(미실측). 그래서 d 를 1·2·3초로 나눠 흉내 냈습니다. 공개 경기 영상 12편의 랠리 275개(서브 임팩트부터 종료 라벨까지)에 「종료 d초 뒤에 눌렀다」를 얹고, 서브 1.05초 전부터 종료까지가 한 구간에 온전히 들어간 비율과, 편집본 길이가 경기 구간에서 차지하는 비율을 쟀습니다(2026-10-13, docs/tt/PERFORMANCE.md 2.11).
| 누름 앞 N초 | d=1초: 온전 · 편집 길이 | d=2초: 온전 · 편집 길이 | d=3초: 온전 · 편집 길이 |
|---|---|---|---|
| 6초 | 78.5% · 42.4% | 65.8% · 42.4% | 44.0% · 42.5% |
| 8초 | 90.2% · 53.7% | 82.9% · 53.9% | 78.9% · 54.1% |
| 10초(고름) | 93.1% · 63.3% | 91.3% · 63.5% | 90.5% · 63.7% |
| 12초 | 95.3% · 70.7% | 94.2% · 70.9% | 93.1% · 71.2% |
| 15초 | 96.0% · 78.2% | 95.6% · 78.4% | 95.3% · 78.7% |
10초는 d 가 1·2·3초 어느 것이든 90% 를 넘는 가장 짧은 값입니다. 같은 랠리들의 길이는 중앙 2.2초, 90% 분위 5.9초, 95% 분위 9.6초, 99% 분위 29.3초였고, 랠리 사이 틈은 중앙 10.3초였습니다. 즉 10초 창은 대부분의 랠리를 담으면서도 랠리 사이 쉬는 시간을 대체로 잘라 냅니다. 긴 랠리 몇 개는 화면 움직임으로 늘립니다.
같은 영상 한 편(140초, 정답 랠리 7개)으로 끝까지 돌려 본 결과, 편집본은 139초 녹화를 약 95초로 줄였고 7개 중 6개 랠리가 서브 자세부터 남았습니다. 빠진 하나는 24초짜리 긴 구간으로, 중간에 선수가 화면 밖으로 나가 움직임이 끊긴 경우였습니다. 영상 한 편이라 효과의 크기는 일반화할 수 없습니다.
비용
같은 노트북 CPU 에서 크롬으로 잰 상대 비용입니다(2026-10-13, 다른 작업이 함께 돌던 때라 비율로만 읽습니다).
| 항목 | WebCodecs(하드웨어 H.264) | MediaRecorder |
|---|---|---|
| 녹화가 더하는 CPU(카메라만 켠 것 대비) | 약 10 CPU초/분 | 약 15 CPU초/분 |
| 멈춘 뒤 편집(편집본 49초) | 0.4 CPU초, 0.56초 | 22–26 CPU초, 49초 |
| 1분 저장 용량 | 16.7MB | 약 18MB |
WebCodecs 쪽 편집이 거의 공짜인 이유는 다시 인코딩하지 않기 때문입니다. 녹화할 때 1초마다 키프레임을 넣어 두면 자를 자리가 1초 안에 하나씩 생기고, 편집은 남길 구간의 프레임을 새 파일에 옮겨 담기만(remux) 하면 됩니다. MediaRecorder 쪽은 편집본을 다시 재생하며 담는 방식이라 편집본 길이만큼 시간이 듭니다.
휴대폰에서는 재지 않았습니다. 배터리, 발열, 한 시간 넘는 녹화의 메모리 모두 미실측이고, 노트북의 하드웨어 인코더와 휴대폰 인코더가 같은 비율일 근거도 없습니다.
9. 복식은 어디까지
복식 규칙은 자바스크립트 규칙 엔진에는 있습니다. 첫 서버와 첫 리시버 고르기, 네 사람의 서브 순환, 다음 게임 첫 리시버 결정(2.13.5), 마지막 게임에서 한쪽이 5점이 되면 리시브 순서 바꾸기(2.13.6)까지 구현되어 있고, 카메라 없는 「서브·점수 도우미」(/tt/play/scorekeeper)에서 쓸 수 있습니다.
그러나 나머지는 단식뿐입니다.
| 자리 | 복식 |
|---|---|
| 자바스크립트 규칙 엔진, 서브·점수 도우미 | 됨 |
| 카메라 점수판 | 안 됨(단식 고정) |
자동 판정 엔진(rules.py) |
안 됨(단식만) |
| 라이브 공유 | 안 됨(복식 경기는 보내지 않음) |
| 경기 기록·레이팅 | 안 됨 |
자동 판정 쪽은 어려움이 하나 더 있습니다. 복식은 같은 편 두 선수가 번갈아 쳐야 하는데, 3편에서 보듯 우리 심판은 「누가 쳤나」를 보지 않고 바운드 순서로만 판단합니다. 복식에서 순서를 어긴 타구를 잡으려면 사람을 구분해야 합니다.
10. 정리
- 점수 규칙은 규정집 조문과 일대일로 대응하고, 파이썬 원본과 자바스크립트 사본을 같은 사례 파일로 묶었습니다.
- 서브와 자리는 저장하지 않고 점수와 첫 서버에서 계산합니다. 그래서 경기를 이력으로 저장할 수 있고, 한참 전의 한 칸을 지워도 뒤가 규칙대로 다시 맞습니다.
- 녹화의 쉬는 시간 빼기는 「득점·판정 표시만 구간을 만든다」는 규칙 하나로 몸풀기와 쉬는 시간을 거릅니다. 10초 창은 275랠리 흉내로 골랐고, 사람이 누르는 늦음과 휴대폰 비용은 아직 미실측입니다.
티티 한마디. 점수판은 「지금 몇 대 몇」을 외우지 않아요. 「누가 몇 번째에 땄는지」를 외워요. 그래서 세 점 전 실수를 지워도 서브랑 자리가 알아서 다시 맞아요.
참고문헌
- International Table Tennis Federation. ITTF Statutes 2026 (effective 1 January 2026), Chapter 2: The Laws of Table Tennis, §§2.10–2.15. https://db.ittf.com/document
- 우리 측정: 저장소
docs/tt/PERFORMANCE.md2.11. 코드backend/static/tt/play/lib/scorekeeper.mjs·camscore.mjs·breakedit.mjs·cam-speech.mjs·fullview.mjs.
함께 읽기
- 이 시리즈 3편: 자동 판정: 몸풀기 상태가 자동 판정에서 왜 결정적인지, 사람의 손이 칩의 답이 되는 규칙.
- 이 시리즈 6편: 경기 기록과 개인정보: 끝난 경기의 게임 점수가 기록과 레이팅이 되는 길.
- 앞 시리즈 TT Score 모델 해부 5편: 이벤트와 심판: 자동 판정 엔진이 같은 규칙을 쓰는 방식.