Status: Draft (2026-10-26), reflects actual measurements. Rules were read from the repository code as of 2026-10-25 (
ttscore/,backend/static/tt/play/), and the numbers are values we measured directly from public match footage (dates and conditions vary by table). Accuracy for real ping-pong club footage recorded with a phone is unmeasured as labeled video is not yet available. Writing convention: Measured data is the default; unmeasured items are marked as "Unmeasured".
Table of Contents
- 1. Automatic Rulings Are "Suggestions," Not Scoreboards
- 2. From Ball Coordinates to Events
- 3. Referee: Ignoring "Who Hit It"
- 4. Grouping Rallies: Dead Balls, Winners, and Resynchronization
- "Needs Review": Four Categories
- 6. How Warm-ups/Practice Are Separated from Matches
- 7. Chips: Division of Labor Between Humans and Machines
- 8. Empirical Measurement: Current Accuracy
- 9. Conclusion
- References
- Further Reading
1. Automatic Rulings Are "Suggestions," Not Scoreboards
Let us clarify the position first. The true score of a camera-based scoreboard is the history entered and corrected by humans, as seen in Part 2. Automatic rulings merely submit suggestionsto that history one by one—such as "Current rally: Player A scores, reason: out, confidence: 0.83"—and only confirmed suggestions become score entries in the history (backend/static/tt/play/lib/camauto.mjs Introduction, "The Scoreboard Is One"). Therefore, even if an automatic ruling is wrong, there is no need to reconcile it separately from the human-corrected history.
This post follows the journey from the moment ball coordinates come in until chips turn into scores.
- Ball coordinates → Events(Bounce, Net, Out, End)
- Events → Referee State Machine → Rally Winner
- Rally grouping, dead ball, resynchronization
- "Needs Review" decisions
- Chips and automatic reflection, division of labor between humans and machines
- And in between, mechanisms separating warm-ups and practice from matches
How ball coordinates are generated is covered in Part 4, and Part 5of the previous series covers the history of this engine rising from 66.9% to 93.1%. This post provides the full picture of the rules operating currently.
2. From Ball Coordinates to Events
The worker holds ball coordinates for only the last 2.5 seconds (EventFeed, KEEP_S=2.5), filling short gaps (3 samples or fewer) and re-extracting events every time new samples arrive (ttscore/events.py in detect_events). Because it operates in real time and cannot see the future, it continuously re-reads the recent segment.
Bounce: Meeting All Five Conditions
A bounce is emitted only when all of the following conditions are met:
| Condition | Description | Why |
|---|---|---|
| V-shape | Screen y increases (descending) then decreases (ascending) | Basic shape of a bouncing ball |
| Inside table boundary | Inside the table top rectangle. 8px margin per edge, end edges extend up to 0.15m outside the plane | At the bounce moment, the ball center is higher than the contact point by the radius (20mm), so mapping to the plane shifts it |
| Interval | 0.12 seconds or more from the previous bounce | To prevent counting the same bounce twice |
| Horizontal direction stability | Discard if both forward/backward horizontal movements are 2px or more with opposite directions | Balls bouncing on the table keep moving, while balls hitting a racket reverse direction. Racket hits also form a V-shape |
| Discard V-shape near net | Discard V-shape within net x ±2% and net hit ±2 frames | Vibration of a ball caught in the net |
If recorded from an end view (camera behind the endline), one more condition is checked: longitudinal velocity must be 0.3m/s or greater. This is because end-view screens mix height and distance into screen y, easily producing false V-shapes.
Bounce Side (L/R) in Plan View
Which player's side a bounce lands on is determined by table plane coordinates, not the left and right of the screen. A screen-to-plane (2.74m × 1.525m) homography is created using the four corners of the table (Part 4), and the point where the ball makes contact is mapped to the plane to determine the side using the sign of the longitudinal coordinate u (ttscore/table.py in ball_side). This ensures the formula remains the same whether filmed from the side or the end. If the rectangle is distorted to the point where it cannot be read as a rectangle (likely due to incorrect corners), it falls back to the old rule: left and right based on the net x reference line. This is because plane transformation of a distorted rectangle tilts the net line and flips sides.
Planar projection is accurate only at the exact moment the ball contacts the table . Mapping an airborne ball to the plane yields the intersection point of the line passing through the ball from the camera and the plane; the higher the ball, the further it is displaced. Therefore, planar coordinates are used exclusively for bounce sides, while airborne segments are evaluated separately.
Horizontal Velocity: The Key to Rulings
Longitudinal horizontal velocity is recorded with every bounce: vxright after the bounce and vx_inright before it, with the unit being table lengths/second. Because it is divided by table length rather than pixels, it is independent of camera distance and resolution. This single number distinguishes serves, returns, and false bounces in the referee section below.
Nets, Outs, and Ends
- Net: A net hit is recognized if the horizontal velocity ratio of the 6 surrounding frames drops below 0.7 (only when forward velocity is 0.3 or higher) while the ball crosses net x ±4%. Emitted only in side-view screens.
- Out: Triggered only after a bounce. An out occurs when the ball goes beyond the table end and falls below the top surface strip, with the side being the crossed end.
- End: A rally is considered ended if no new bounce occurs for 1.5 seconds after the last bounce, or if the ball is not visible for 1 second . The 1.5-second threshold comes from empirical measurements showing that 98% of bounce intervals in actual rallies are within 1.5 seconds (12 public matches, 2026-10-11).
Confidence
Every event is assigned a confidence score. conf = min(검출 신뢰도, 공백 신뢰도) , and gap confidence is 1 − 놓친 프레임 / (fps × 0.25초) . If the ball was lost for more than 0.25 seconds prior, it is 0, meaning it cannot be trusted because a bounce might have been missed in between. Detection confidence is also tracked separately (det_conf), used in the first bounce of a serve.
3. Referee: Ignoring "Who Hit It"
The core concept of the referee (ttscore/referee.py in RallyReferee) is stated in the very first line of the introduction: Who hit the ball is inferred from the bounce order rather than directly observed from video. If a ball bounces on side S, the player on side S must hit it next so that it lands once on the opposite side. Failing to do so (bouncing on the same side again, going out without bouncing, or ending without incident) results in the player on side S losing the point. The serve is the only exception, requiring one bounce on the server's side followed by one on the receiver's side.
Racket contact is the hardest event to find in video. Balls are obscured by rackets, motion-blurred, and blended into players' bodies. This design scores points without relying on that event. The trade-off is clear: if a single bounce is missed or a false bounce slips in, "whose turn it is to hit" breaks down entirely. Therefore, most of the rules below serve as mechanisms to filter out false bounces and balls outside of rallies.
There are four states: serve0(Wait for Serve) → serve1(Serve in Progress) or direct(Direct) → rally(Rally) → done(End).
Wait for Serve: Accepting Only the Start of a Serve
Even between rallies, the ball moves a lot. Players go fetch balls, pass them to opponents, bounce them on their own side a few times before serving, and hand-held balls shake. If these are captured as events, nonexistent points are created. In fact, in the first run where an entire match was fed into the engine, 20 out of 49 rulings were not rallies (13 ball passes, 5 pre-serve self-bounces, etc., 2026-10-11, docs/tt/PERFORMANCE.md 2.6).
Therefore, in the Wait for Serve state, only balls that look like the start of a serve are accepted.
# ttscore/referee.py _start() 에서 줄임
f = self._forward(ev, self.server, serve=True) # 서버 쪽에서 네트 쪽으로 가는 가로 속도(바운드 앞뒤 중 큰 쪽)
if ev.side == self.server:
if f is not None and f < SERVE_MIN_VX: # 0.7 테이블 길이/초
return False # 서브가 아니다 — 버리고 계속 기다린다
self.phase = "serve1"
return True
if f is not None and f >= TOWARD_NET_VX: # 리시버 쪽에, 서버 쪽에서 날아온 방향으로 떨어진 공
self.phase, self.expect = "direct", self.server
self.conf = min(self.conf, AMBIGUOUS_CONF) # 0.5 — 서버 쪽 바운드를 못 봤으니 확인 필요
return True
return False
There are two conditions: it must land on the server's side known to the scoreboard, and move toward the net at 0.7 table lengths/second or more . Other balls are discarded without penalty to confidence. The justification for the 0.7 figure is empirical (12 public matches, 2026-10-11).
- The first bounces of all 271 actual serves had net-bound velocities of 1.07 or greater (taking the larger of pre- and post-bounce velocities).
- Across 5 consecutive matches starting from 0:0, out of 55 non-rally server-side balls, those that looked like serves (pre-match practice serves, unlabeled serves) had velocities of 0.61 or below 4 times. Returned balls yielded negative values, and self-bounces hovered around 0.
Balls starting "like a serve" on the side opposite the scoreboard's server are not accepted. When the receiver tosses the ball back to the server, it bounces from the receiver's side toward the net and lands on the server's side. It looks identical to a serve but moves in the reverse direction. Accepting serves from both sides increased false rulings in 5 matches from 3 to 16.
There is one exception: a ball landing on the receiver's side in the direction flying from the server's side(moving away from the net at 0.7 or faster). This could be a serve where the server-side bounce was missed, or a failed serve that skipped the server's side. This is accepted as a direct(Direct) state, lowering confidence to 0.5 and sending it for review. First bounces of serves are frequently missed due to being blocked by tosses and rackets. Therefore, if another bounce occurs on the receiver's side or it ends directly after a direct event, depending on the switch (TTSCORE_DIRECT_MISS, enabled), it is judged as an unreturned ball by the receiver (server point) . In actual video experiments (Director E27), 48 out of 60 "another bounce" rulings and 36 out of 49 "end" rulings were server points.
Serve in Progress: To the Receiver's Side Within 1 Second
serve1 In
- , the system waits for a bounce on the receiver's side. A normal serve requires the receiver-side bounce to arrive within 1.0 second of the server's bounce (actual maximum was 0.55s, with margin for 30fps and slow serves). Exceeding this counts as a serve fault (receiver point).
- If another bounce occurs on the server's side while the ball continues toward the net (0.7+), it is treated as a false bounce captured while crossing over the net, and the serve tracking continues (5 actual cases). If it stops or reverses, it is a serve fault where the ball hit the net and fell back onto the server's side (2 actual cases, horizontal velocity near 0).
- If an out or end occurs without a bounce, it is a serve fault (
serve_out).
Let: Postponing Until the Next Event
If the ball touches the net during a serve (net) If it bounced on the receiver's side, it is a let candidate. Instead of declaring a let immediately, it is deferred until the next event.
- If the next bound is on the server's side, it means the ball returned by the receiver has dropped. Since the net detection was incorrect or the player continued playing without calling a let, the let is withdrawn, the rally continues, and it is marked as needs review (0.5).
- Otherwise, it is a let. There is no score(
winner_side=None).
Initially, lets were called immediately, but in the real-time pipeline, it happened twice that the "dead ball" rules following a let call (Section 4) discarded all subsequent bounds of the actual ongoing rally, causing the rally score to disappear (2026-10-13). The deferred version fixed this issue, and the offline rally-level results remained unchanged.
Rally: Flipping the expected side
In a rally, only the "side where the next bound should land" (expect) is tracked.
# referee.py feed() 의 랠리 부분에서 줄임
f = self._forward(ev, ev.side) # 바운드 뒤 가로 속도
if f is not None and f >= TOWARD_NET_VX: # 네트 쪽으로 가는 「바운드」 는 가짜 — 버린다
return None
if ev.side == self.expect: # 기대한 쪽: 정상. 다음은 반대쪽
self.expect = flip(self.expect)
return None
if ev.t - prev_bounce_t < DOUBLE_BOUNCE_SURE_S: # 0.25초 안의 같은 쪽 두 바운드 — 미룬다
self.pending = ev
return None
return self._call(flip(ev.side), "double_bounce", ev) # 같은 쪽에 또: 그 쪽 선수가 진다
- Bounds heading toward the net are fake. During a rally, real bounds on the S side are opponent-hit balls heading toward the S side end, meaning they move away from the net. A "bound" heading 0.7 or more toward the net is a misread of the ball passing over the net or a shot (56 out of 1,094 during actual rallies). This single rule increased the number of actual rally winners from 235 to 241.
- Two-bounce. If the ball bounces twice on the same side, it means the player on that side failed to return it.
- Fast two-bounces are deferred. If two bounces on the same side occur within 0.25 seconds, it is impossible to tell whether it was genuinely a short ball or if a shot immediately following a bounce was misread as a bounce. In actual measurements, 16 out of 39 such calls (41%) were incorrect (error rate for other calls was 8%). Therefore, the confidence is lowered to 0.5 and deferred until the next event. If the next bound is on the opposite side (the return lands), the second bounce was fake, so it is discarded and the rally continues. With this rule, all 9 actual rallies whose decisions changed went from incorrect to correct.
Out and End: The player on the last bouncing side failed to receive it
- Out: Outs that cross the end line of the side that just bounced are ignored. This is because it is a ball that the player on that side is going to receive below the table height (chop, mid-distance drive). If they fail to receive it, the end is determined.
- Other outs or ends mean "the player on the side that bounced last failed to return it." That player loses, and the reason is
outorno_return.
Judgment Confidence
The confidence of a single judgment is the minimumvalue of the event confidences within that rally. However, the first bounce of a serve is counted using the detection confidence excluding gaps (det_conf). This is because the gap before the serve's first bounce is caused by occlusion from the toss and racket, and there are no bounds to be missed in between. Before excluding this, 70% of the serve's first bounces had a confidence of 0, which was the main cause of "86% needs review" (PERFORMANCE 2.6).
4. Grouping Rallies: Dead ball, Winner, Resynchronization
A single referee handles a single rally. The task of chaining rallies together to form a scoreboard is performed by ttscore/analyze.py of LiveScorer .
- Discard before the start of the match. All events before the "match start" time (
start_t) are discarded. This is the topic of Section 6. - 2-second dead ball. Once a judgment is made, events are discarded for the following 2 seconds (continuously extended if events follow) or until an end arrives. When a judgment is made on a bounce or out, the ball is still alive and moving. These include serves continuing to bounce, balls rolling after a double bounce, and return shots after a let. Reading them as serves for the next rally would create non-existent scores.
- From side to player. The referee determines the winner by L/R side. The scoreboard (
rules.Scoreboard) reflects the side change, updates who is currently standing on that side, and awards the point. The server side for the next rally's referee is newly determined by the scoreboard's serve turn. - Resynchronization signal. If the scoreboard is off by one point, the serve turn is misaligned, and the real serve is discarded as a "ball starting from the server's opposite side." Therefore, the engine separately measures the actual served sidefor each rally. This is the S of the last "serve pattern pair" (S-side bound heading 0.7 or more toward the net → opposite-side bound within 1.0 second) from after the end of the previous rally up to the start of the current rally + 0.5 seconds. If this differs from the scoreboard server side even once , a signal is generated. It does not change the score, but simply asks a human, "Is the serve correct?" In actual measurements, pairs within ±0.8 seconds of the labeled serve matched the labeled server in 266 out of 267 cases. The reason it was not set to "if misaligned twice in a row" is that when the scoreboard is off by one point, the serve changes every two points, so the server side mismatches every other rally, meaning two consecutive mismatches almost never occur (out of 64 mismatched rallies, the two-in-a-row rule caught only 10, whereas the single-occurrence rule caught 60 with 2 false alarms, PERFORMANCE 2.7).
5. "Needs Review": Four Branches
Every judgment can be marked with a "needs review" flag. This leaves the judgment as-is while asking a human to take a look. There are four ways this flag is attached.
| Branch | Condition | Reason (why) |
|---|---|---|
| ① Calibration Regression | "Probability that the judgment is incorrect" calculated by the learned logistic regression > 0.0442 (Calibration v2). When calibration is turned off, the old rule applies: "confidence < 0.6" | cal + top two feature bundles that pushed it the most (weak ball, missed ball, long rally, unclear end, unobserved serve, mismatched serve side, judgment type) |
| ② End-line Band | Landing position is within ±0.08m of the end line boundary | edge |
| ③ Signal Discrepancy / Weak Ball | If ball models disagree, or if the ratio of frames where the ball is visible in the rally section is less than 0.5, confidence is reduced to 0.59 | conflict etc. |
| ④ Table | Rally is re-locating the table or spans a missing section | table |
Judgments accompanied by a resynchronization signal (resync) are also prompted separately in the chip.
Why a single confidence score is not enough
The initial rule was "needs review if rally confidence < 0.6." When tested on 2026-10-14 to see if this confidence actually distinguishes errors, it failed to distinguish them(Ledger E20, live pipeline as-is, public test 7 games × 11 degradation conditions, 597 judgments). The accuracy in the 0.0–0.1 confidence range was 0.86, and in the 0.9–1.0 range it was also 0.86, with an AUC of 0.51 (coin flip) for distinguishing errors. This is because, in the 30fps live pipeline, even a single missed view of the ball drops the minimum value below 0.6.
Therefore, a small logistic regression was trained using 19 values already known to the engine at the moment of judgment (ttscore/reviewcal.py). These include confidence, minimum detection confidence, average bound confidence, number of bounds and events, number of weak and gap events, rally length, whether the end is an "end", whether a serve pattern pair was observed, whether that side differs from the scoreboard, and the reason for the judgment. The calculation is a single dot product per judgment, incurring zero real-time cost. The principles and training are covered in Part 4.
| Judgment | Condition | Incorrect without review (†) | Needs Review | Error Discrimination AUC |
|---|---|---|---|---|
| Confidence < 0.6 | Test 7 games × 11 conditions (597 judgments, E20) | 26 | 395 | 0.51 |
| Calibration v1 | Same | 12 | 430 | — |
| Calibration v2 (Production) | Test 7 games × 11 conditions (661 judgments, reflecting label corrections, E25) | 20 → 8 | 450 → 497 | 0.71 → 0.73 |
The part before 「→」 in the v2 row is v1 under the same condition. †The v2 row includes ghost judgments that passed without review, so it cannot be directly compared with the top two rows. Calibration selected thresholds to slightly increase needs-review while drastically reducing false answers that humans blindly accept . Thresholds were determined via 5-fold cross-validation on training games as "minimum missed errors among thresholds where needs-review is at or below the current level."
The reason for attaching reasons also emerged from actual measurements. Before calibration, reasons were empty, so the chip could not tell what to review (Ledger E19-2). Now, for every judgment determined to need review by the regression, up to two human-readable feature bundles "that pushed most toward being incorrect" are sent along.
End-line Band
Whether a ball hit the end line (edge) or fell just outside is the hardest judgment to make with a single camera. If the landing spot is within ±0.08m of the end-line boundary, the winner is kept as-is and sent for review (TTSCORE_EDGE_REVIEW, Ledger E34).
6. How Warm-ups, Practice, and Matches are Distinguished
The Engine Cannot Distinguish Practice Serves
Table tennis matches begin with a warm-up rally. Players exchange hits for a few minutes and try out practice serves. The problem is that practice serves look identical to real serves. They bounce once on the server's side, once on the receiver's side, and the net-crossing speed is the same as the real ones.
There is a measured case. In one public test game (test_4), a pre-match practice serve was captured at 2.05 seconds from the start. It went from the server side toward the net at 4.2 table lengths/second, and the ball crossed the net. Under the serve recognition rules in Section 3, there is no way to distinguish it from a real serve. The engine counted this as the first point, and that single point misaligned the serve turn until the end of the match, resulting in 0 out of 21 rallies with correct post-rally scores for this game (PERFORMANCE 2.6).
Methods to distinguish practice using only video were also tested: "Discard before the first complete serve of the scoreboard's first server side" (N1) and "Discard before the first serve pattern pair on either side" (N2). Neither improved results (compared to engine without signal: 5-game score correctness 24 → 24, winners 41 → 40, Part 12 total no-judgment 18 → 21). In test_4, the real first serve was a missed serve hitting the net and thus was not a "complete serve," and both rules discarded even the first real point. Since there was only one practice serve case, rules could not be determined from data alone. Therefore, it was not included.
The conclusion is simple: Humans inform when the match starts. This is easy for players, but nearly impossible for machines.
Path of the "Match Start" Signal
The camera scoreboard's warm-up state (Part 2) is the gateway to receive this signal.
- Screen. When the camera is turned on, it is in the warm-up state (
CamMatch.startedAt === null). Tapping the score box does not add points, but shakes the "Match Start" button. The remote control's start key only works during warm-up. - Start. When "Match Start" is pressed,
startMatch()records the match start time in the history and triggers att:match-startevent to generate{type:'score', on:true, start, server_side}. - Time Synchronization. The automated score screen (
cam-auto.js) receives it, callsscoreOn(), and converts the start time recorded by the device clock (Date.now) into a frame clock(where the moment of connection with the worker is 0) (startOffset). This is because workers do not know the device clock and only know timestamps attached to frames. - Worker. Via web relay, the worker's session calls
scorer.start(server_side, t)and creates a newLiveScorer. Previous judgments and sections are forgotten, and all events prior to this time are discarded. - Double Lock. The browser does not send this signal if the match has not started, and the worker does not make judgments before receiving the signal (only ball positions and bound indicators are sent). The browser side also does not generate proposals for judgments arriving during warm-up or after the match ends (
autoProposalis null, code comment "Warm-up rallies are not scores").
The server side also follows along. Every time the scoreboard changes (scoring, undo, human correcting the service turn), the current server side is sent to the worker, and the engine uses it to align the server side for the next rally. The server side corrected by a human wins.
Measurement: Effect of a Single Signal
This is a value measured by simulating that a person pressed the "Start Match" signal just before the first serve across 5 public matches (49 rallies) from 0:0-starting labels matching the rules (2026-10-11, PERFORMANCE 2.7).
| Metric (5 matches, 49 rallies) | No Signal | With Signal |
|---|---|---|
| Correct Post-Rally Score (Score/Game) | 27 / 49 | 36 / 49 |
| Correct Winner | 44 / 49 | 45 / 49 |
| Non-Rally Judgment (Fake Score) | 3 | 2 |
One test_4 improved from 0/21 to 9/21. The results were identical even when the signal was pressed 0.3, 1, 2, or 3 seconds before the first serve, but 5 seconds before(before the practice serve) had no effect. Therefore, the screen instructs users to press it "right after warm-up and just before the first serve."
"Practice-like Balls" During a Match
Even after a match starts, balls continue to move between rallies. The mechanisms in Sections 3 and 4 filter these out.
- Filtering Pre-Serve Wait: Discards passes, self-bounces, balls held in hand, and balls on the floor. In the same 5 matches, fake judgments dropped from 30 to 5, and correct post-rally scores improved from 8/49 to 24/49 (PERFORMANCE 2.6, before/after serve recognition rules).
- Dead Ball 2 Seconds: Discards bouncing balls immediately after a judgment.
- Table Zone: Balls missed or recovered off the table are excluded from judgments, and rallies spanning those zones are sent to Needs Review.
Remaining Limitations
- Balls shaped like real serves still score. If players replay a let during a match or practice serves between rallies, the engine cannot distinguish them from real serves. When only the serve recognition rule was applied, 3 out of the 5 remaining fake judgments in the 5 matches were such balls (practice serves, unlabeled serves presumed to be lets).
- If "Start Match" Is Not Pressed Warm-up rallies become scored. The manual scoreboard indicates the scoring section is active by shaking the start button during warm-ups, but it is useless if a person presses start too early (before practice serves).
- Break Times Between Gameshave no separate "Pause" signal. Balls during this time rely solely on the pre-serve wait filtering.
"Practice" Features Unrelated to the Scoreboard
Two similarly named, easily confused features are not connected to the scoreboard or automatic judgment.
- Reaction Speed Rally(
/tt/play/reaction-rally): A solo reaction game where onscreen balls approach one of four zones: forehand/backhand × short/long. It does not use camera judgments. - Practice Routine(
/tt/practice): Select a skill level and duration (15–180 minutes) to generate a practice plan structured around warm-up, technique, game, and wrap-up. Records are saved as practice history and remain separate from ratings (Part 6).
7. Chips: Division of Labor Between Humans and Machines
From Judgment to Chip
When an operator submits a judgment, the browser converts it relative to the left player at that moment and displays it as a chip. This is because operators refer to L/R from the raw video, screens can be mirrored, and positions swap every game. Lets and no-judgments are not suggested.
Chips feature four buttons: "Confirm", "(Left Player) Scores", "(Right Player) Scores", and "Void".
Auto-Commit When There Is No Response
No one will tap a distant phone after every single rally. Therefore, unresponded items are auto-committed (backend/static/tt/play/lib/scorestatus.mjs).
| Judgment | Timeout Without Response | Rationale |
|---|---|---|
| Certain | 4 seconds | — |
| Needs Review | 9 seconds | Ample time for a human to look at the chip and respond (approx. 2.5 seconds + 3 seconds for "Hold on"), fitting within the actual inter-judgment intervals in video (3.8–17.4 seconds). |
This rule has a backstory. When public match footage was streamed to operations staff on 2026-10-13, the camera scoreboard received four judgments over 50 seconds yet remained at 0:0. Most judgments were marked Needs Review, and the screen withheld them until a human responded, discarding them if a new judgment arrived. Furthermore, it provided no explanation for why they were dropped. Now, Needs Review items are also committed after 9 seconds, and if a new judgment arrives before the 9-second mark, the preceding judgment is committed immediately.
Chip queues are capped at two. When a third judgment arrives, the oldest is auto-committed, and any displayed chip remains visible for at least 2.5 seconds.
Handling Responses
| Response | Recorded History Column | Revision Log |
|---|---|---|
| "Confirm" | Score (Source: auto) | confirm |
| Auto-commit after timeout | Score (Source: auto) | None (not human-verified) |
| Same player as judgment | Score (Source: auto) | confirm |
| Different player from judgment | Score (Source: tap) | flip |
| "Void" | Not added | void |
Manual Input Overrides Chip Responses
This is the most critical rule: if you tap the score column while a chip is displayed, that manual action becomes the response to the chip(handOverride). If it is the same player, it counts as confirm; if a different player, it counts as a correction. Without this rule, operators would habitually tap manual scores while automatic judgments commit 4 seconds later, resulting in 2 points per single rally.
In summary, roles are divided as follows.
| Human | Machine | |
|---|---|---|
| When the match starts | Determines("Start Match") | Discards everything before it |
| Who serves | Can correct; correcting updates the winner | Adopts from scoreboard, asks "Is the serve correct?" if mismatched |
| Rally winner | Responds to chips or uses manual tap (manual action is the response) | Suggests. Commits after 4/9 seconds if no response |
| Ambiguous judgments | Reviews | Appends Needs Review status and reasons |
| Miscalculated scores | Erases from history (Part 2) | — |
Records of human responses to chips (revision logs) may be retained on the server. Video footage is omitted, and only data consented to for training purposes is exported. Where these records go and how they have been used is covered in Part 6.
8. Empirical Measurements: Current Accuracy Status
All rows are measured from public match videos (7 OpenTTGames test matches). Different matches were used for training and threshold tuning, with test matches evaluated last. However, since the rule engine itself was refined while reviewing test matches between 2026-10-11 and 12, test scores for the rule baseline are optimistic (Director's data leakage warning).
| Measurement | Conditions | Result |
|---|---|---|
| Operational Workflow Baseline Table | 7 test matches × 11 degradation conditions, 638 labels, production-identical pipeline + bounce filtering, calibration v2, direct-pass rules (2026-10-16, Director E25/E27) | Confirm 508, Incorrect without review 8, Needs Review 497, Phantom (scored with no ball present) 51, Judgment latency p90 1.32s |
| Camera Scoreboard End-to-End | Streaming 851 seconds of 7 test matches as mock camera 30fps into actual browser/operator code, 58 labeled rallies (2026-10-17, Director E35) | Confirm 47, Incorrect 7, No judgment 4, Phantom 9, Incorrect without review 0, Needs Review 53/65 |
| Public Footage by Condition | 8 shooting conditions across 27 clips (2026-10-14–16, PERFORMANCE 2.16–2.17) | Vertical smartphone footage yielded 5% ball-visible frames and 0 judgments |
| Actual Table Tennis Hall Shot on Smartphone | — | Unmeasured(0 labeled videos) |
These two lines summarize the current characteristics: incorrect judgments without review are rare, but Needs Review frequency is high. In the end-to-end camera scoreboard test, 53 out of 65 judgments were marked Needs Review. The 9-second auto-commit bears this burden on behalf of humans, and our next improvement goal is to "reduce Needs Review items without increasing unreviewed errors."
9. Conclusion
- Automatic judgments are suggestions, not the scoreboard itself. The true score consists solely of human input and correction history.
- Umpires do not watch rackets; instead, they distinguish serves, passes, and fake bounces using bounce sequences and a single metric: horizontal velocity of 0.7 .
- Practice serves look identical to real ones, preventing the engine from separating them. Consequently, humans press "Start Match," and that signal flows through screen → frame clock → operator → engine to discard everything preceding it. This single signal improved correct match scoring from 27/49 to 36/49.
- "Needs Review" could not reliably separate errors using confidence alone (AUC 0.51), so it was replaced with learned calibration. Unresponded chips auto-commit after 4/9 seconds, with human manual actions overriding chip responses.
A word from TT. Warm-up serves and real serves look completely identical to me. Therefore, please be sure to press "Start Match" immediately before the first serve. That single action is more important than ten points.
References
- International Table Tennis Federation. ITTF Statutes 2026, Chapter 2: The Laws of Table Tennis, §§2.10–2.15. https://db.ittf.com/document
- R. Voeikov, N. Falaleev, R. Baikulov. "TTNet: Real-time Temporal and Spatial Video Analysis of Table Tennis." CVPR Workshops, 2020. arXiv:2004.09927. A representative prior study where neural networks directly detect bounces and nets; our approach differs in extracting events via ball trajectory rules.
- Extended OpenTTGames (Rally End Reason & Winner Labels). arXiv:2512.19327. Rally winner ground truth for this article.
- Our Measurements: Repository
docs/tt/PERFORMANCE.md2.6, 2.7, 2.16, 2.17, Research Logs E20, E25, E27, E34, E35. Codettscore/events.py·referee.py·analyze.py·reviewcal.py,backend/static/tt/play/lib/camauto.mjs·scorestatus.mjs.
Further Reading
- This Series, Part 2: Manual Scoreboards and Match Rule Engines: "History, Not State," and warm-up state.
- This Series, Part 4: Model Principles and Papers: How bounce filtering 1D CNN and Needs Review calibration regression were trained.
- Previous Series, TT Score Model Anatomy Part 5: Events and Umpires: What was changed and executed per match from 66.9% to 93.1%.
- Reading Vision Models Parts 2 & 10: Precision Event Detection: How to find events through learning.