← Home

Tennis Lab Interactive physics laboratory

Explore tennis physics on an interactive 3D court. Simulate serves, analyse trajectories and study court geometry — all in one place.

A 3-D model of an ITF tennis court. Drag to rotate, scroll to zoom — explore singles and doubles lines, the net and full regulation dimensions before you simulate anything.

Left-click + drag Rotate view Right-click + drag Pan view Mouse wheel Zoom in / out Touch 1 finger = rotate · 2 fingers = zoom

Tennis ball physics — from gravity to the Magnus effect

A tennis ball in flight is no plain projectile. The air drags on it with a force that grows with the square of speed; spin curves the path sideways or downwards; the court surface alters bounce angle and speed. This page walks through, formula by formula, how our simulator turns four numbers — speed, angle, spin, side — into a full 3-D trajectory.

1. Coordinate system and court geometry

Before integrating any equation of motion we must agree on where the ball is. The whole court is described in a right-handed Cartesian frame whose origin sits at the server's baseline:

  • $x$ — along the court, baseline to opponent ($+x$ towards the net)
  • $y$ — lateral offset from the centre line ($+y$ on the deuce side)
  • $z$ — vertical, $z=0$ at the surface ($+z$ up)
net service line baseline x y z v 11.885 m (baseline → net) 6.40 m
Fig. 1 — Coordinate axes, key ITF dimensions and the instantaneous velocity vector $\vec v$ of the ball.

ITF prescribes precise dimensions, and the code stores them as constants. Baseline → net distance is $L_1 = 11.885\ \mathrm{m}$, net → service line $L_2 = 6.400\ \mathrm{m}$. The net is not flat — it sags to $h_\text{cen} = 0.914\ \mathrm{m}$ at centre and stretches to $h_\text{post} = 1.067\ \mathrm{m}$ at the posts. Our serve validator captures this sag with a linear interpolation:

$$ h_\text{net}(y) \;=\; h_\text{cen} + (h_\text{post} - h_\text{cen})\cdot \min\!\left(\frac{|y|}{w/2},\; 1\right), $$

where $w = 8.23\ \mathrm{m}$ is the singles court width. It's the first hint that "simple" geometry meets real physics: a passing shot down the line clears a net that is $15.3\ \mathrm{cm}$ higher than the one a centred passing shot has to cross.

2. Equation of motion and gravity

Mechanically the tennis ball is a point mass — one position $\vec r(t) = (x, y, z)$ and one velocity vector $\vec v(t) = \dot{\vec r}(t)$. The mass $m = 0.057\ \mathrm{kg}$ comes straight from the ITF rulebook (yellow felt balls weigh 56–59 g). Newton's second law gives the single governing equation:

$$ m\,\frac{d\vec v}{dt} \;=\; \sum \vec F \;=\; \vec F_\text{grav} + \vec F_\text{drag} + \vec F_\text{Mag}. $$

Three forces. Three stories. We start with the trivial one — the one that pulls the ball down by exactly the same amount on every single integration step:

$$ \vec F_\text{grav} \;=\; -m g\,\hat z,\qquad g = 9.81\ \mathrm{m\,s^{-2}}. $$

Gravity is the only constant in the model the player has no control over — and yet it's the one who eventually wins. In the absence of air the trajectory would be a clean parabola with apex

$$ z_\text{max} \;=\; z_0 + \frac{v_{z,0}^{\,2}}{2g}. $$

For a flat 200 km/h serve hit at $-8°$ the initial vertical speed is $v_{z,0} \approx -7.7\ \mathrm{m/s}$ (the ball is already heading down!), so the ballistic apex would lie below the contact point. In reality there is an apex — and it's lifted by the two forces covered in the following sections.

m v Gravity (weight m·g) Drag opposes v Magnus ⊥ to v
Fig. 2 — Free-body diagram. A point mass $m$ moves with velocity $\vec v$. Three forces:
gravity $\vec F_g = -m g\,\hat z$, drag $\vec F_\text{drag} = -\tfrac12\rho C_d A |\vec v|\,\vec v$, Magnus force $\vec F_\text{Mag}$ perpendicular to velocity.

In the code the equation is solved by an explicit Euler integrator with a fixed step $\Delta t = 3\ \mathrm{ms}$ and a maximum flight time of 4 s. The integration choice — its accuracy and why such a coarse scheme suffices — gets its own section later in this article.

3. Aerodynamic drag

Air resists the ball with a force that, across the speed range relevant to tennis (5–80 m/s, $Re \sim 10^5$), is quadratic in velocity. This is not a convenience — the Reynolds number lands squarely in the turbulent-wake regime, where the inertial term $\rho v^2$ dominates the viscous $\mu v$. The classical expression:

$$ \vec F_\text{drag} \;=\; -\,\tfrac12\,\rho\,A\,C_d\,|\vec v|\,\vec v. $$

Since $\vec v = |\vec v|\,\hat v$, the magnitude is $|\vec F_\text{drag}| = \tfrac12 \rho A C_d v^2$, opposing the direction of motion. The constants are: $\rho = 1.21\ \mathrm{kg\,m^{-3}}$ (sea-level air density), $A = \pi R^2 \approx 3.53 \cdot 10^{-3}\ \mathrm{m^2}$ (frontal area, ball radius $R = 33.5\ \mathrm{mm}$). The drag coefficient $C_d$ is the part that really matters — and it is anything but constant.

A spin-dependent drag coefficient (Stepanek–Cross)

The textbook value $C_d = 0.5$ for a smooth sphere only holds for a non-rotating ball with a glassy surface. A tennis ball wears felt and spins, both of which dramatically alter the boundary layer. From wind-tunnel data, Stepanek (1988) and Cross (2003) fitted the empirical correlation our model uses:

$$ C_d \;=\; 0.55 + \left(\frac{1}{22.5 + 4.2\,S^{2.5}}\right)^{\!0.4}, \qquad C_L \;=\; \frac{1}{2 + S}. $$

The dimensionless number $S$ is the spin ratio — the ratio of translational speed to the rim speed of the ball's spin component perpendicular to its velocity:

$$ S \;=\; \frac{|\vec v|}{R\,|\vec\omega_\perp|},\qquad \vec\omega_\perp = \vec\omega - (\vec\omega\!\cdot\!\hat v)\,\hat v. $$

With no spin at all, $|\vec\omega_\perp| \to 0$, so $S \to \infty$ and the coefficients tend to their asymptotic values $C_d \to 0.55$, $C_L \to 0$. A flat serve therefore "flies a parabola" — but a heavily damped one. Add $\sim 3000$ rpm of topspin and $C_d$ jumps by tens of percent: a spinning ball stirs more air around it and pays for it.

How big is it, in newtons?

For a flat $v = 200$ km/h $= 55.6$ m/s with $S \to \infty$ (no spin):

$$ F_\text{drag} = \tfrac12\cdot 1.21 \cdot 3.53 \cdot 10^{-3} \cdot 0.55 \cdot 55.6^2 \approx 3.63\ \mathrm{N}. $$

The ball's weight is $mg = 0.057 \cdot 9.81 \approx 0.559\ \mathrm{N}$. Drag is therefore 6.5× larger than the weight — at the start of the serve the ball decelerates much faster than gravity accelerates it downward. This is why a "ballistic" picture fails so badly for tennis: a 200 km/h flat serve arrives on the other side at only ~130 km/h.

Phase of flight$v$ [km/h]$F_\text{drag}$ [N]$F_\text{drag}/mg$
Contact2003.636.5
Crossing the net1602.334.2
After bounce1301.542.8
Drifting (e.g. lob)500.230.4

Table 1 — The quadratic law bites hard: doubling the speed quadruples the drag.

4. Magnus effect

When a spinning sphere flies through air, its trajectory curves — and curves perpendicular to both the direction of motion and the spin axis. A topspin ball drops faster than gravity alone would predict; a slice serve curves sideways; a kick serve "kicks" up off the bounce. Physics calls this the Magnus effect, and our simulation captures it via the force

$$ \vec F_\text{Mag} \;=\; \tfrac12\,\rho\,A\,C_L\,|\vec v|^2\; \frac{\vec\omega_\perp \times \hat v}{\,|\vec\omega_\perp \times \hat v|\,}. $$

The key vector is $\vec\omega_\perp$, the projection of the angular velocity $\vec\omega$ onto the plane normal to velocity:

$$ \vec\omega_\perp \;=\; \vec\omega - (\vec\omega\!\cdot\!\hat v)\,\hat v. $$

Why the perpendicular component only? Spin along the flight direction (imagine the ball rotating around its own velocity axis) is symmetric with respect to surrounding air — both sides of the ball wash past it equally. Only when the spin axis has a component across the flight does one side "move into" the air faster than the other, creating the pressure differential that drives the force.

Which way does the force point?

The cross product $\vec\omega_\perp \times \hat v$ sets the direction. For the three classic cases in tennis:

  • Topspin — $\vec\omega = (0,\omega_y,0)$ with $\omega_y > 0$. Crossing with $\hat v$ (positive $x$ component) yields a force pointing down. Topspin adds to gravity, so the ball drops faster.
  • Backspin (classic baseliner slice, lob) — $\omega_y < 0$, force points up. The ball "floats" longer and stays in the air.
  • Sidespin — $\vec\omega = (0,0,\omega_z)$. Force points sideways ($\pm y$), bending the trajectory laterally.

How big is Magnus?

Stepanek gives us $C_L = \frac{1}{2 + S}$. For a 155 km/h kick serve ($v = 43.1$ m/s) with 3000 rpm of topspin, the angular speed is $\omega = 3000 \cdot 2\pi/60 = 314.2$ rad/s. The spin ratio is

$$ S \;=\; \frac{v}{R\,\omega} \;=\; \frac{43.1}{0.0335 \cdot 314.2} \;\approx\; 4.1, $$

so $C_L \approx 1/(2+4.1) \approx 0.164$. The Magnus force is

$$ F_\text{Mag} = \tfrac12 \cdot 1.21 \cdot 3.53\!\cdot\!10^{-3} \cdot 0.164 \cdot 43.1^2 \approx 0.65\ \mathrm{N}, $$

which is ~17 % of the ball's weight added to gravity. Dramatic? Not in absolute terms — but it acts over the entire flight, and the integrated drop adds up. A kick serve lands roughly 30–40 cm deeper into the box and 20 cm lower than a flat serve at the same launch parameters.

5. Serve types and the spin vector

On court, players distinguish flat, kick and slice serves intuitively — by how the ball curves. The simulator has to translate that vocabulary into an angular velocity vector $\vec\omega$ (rad/s) that drives the Magnus force. The function _spin_to_omega() handles the dictionary:

Serve type$\omega_x$$\omega_y$ (top)$\omega_z$ (side)Geometry
Flat000No spin — pure aerodynamic drag.
Kick0$0.70\,\omega$$0.30\,s\,\omega$70 % topspin, 30 % sidespin keyed to handedness.
Slice00$s\,\omega$Pure sidespin, sign by handedness.

Tab. 2 — $\omega = \mathrm{rpm}\cdot 2\pi/60$ is the signed angular speed, $s = -1$ for right-handers, $+1$ for left-handers (mirroring the side curve).

Why 70 / 30 for the kick?

Real kick servers don't produce pure topspin. The racquet head travels a diagonal path across the ball (low-back to high-front), so the ball receives predominantly topspin but also a noticeable sidespin component. The empirical 70 % top / 30 % side ratio yields the simulator's characteristic kick: a high arc plus a strong sideways bounce into the receiver's backhand. The mix was tuned against real-match observations to give sensible landing zones.

Slice and the server's hand

Slice spins around the vertical axis ($z$). A right-hander hitting slice contacts the ball on its right side — the impulse yields negative $\omega_z$ and the sidespin curves the ball to the server's left. Lefties mirror this. Hence the handedness sign $s = \pm 1$ in the code:

w = spin_rpm * 2*math.pi / 60      # signed rad/s
s = -1.0 if handedness == "right" else 1.0
if serve_type == "slice":
    return np.array([0.0, 0.0, s * w])

Web parameterisation — rate + direction dial

The frontend Serve tab does not present three categories or two independent sliders. Instead it splits spin into a magnitude (slider, 0–5000 rpm) and a spin-axis direction set with an arrow on a circular dial. Convention (angle $\theta$ measured clockwise from 12 o'clock):

  • $\theta = 0°$ — pure topspin (axis horizontal, Magnus down),
  • $\theta = 90°$ — pure right sidespin (axis vertical, $+y$),
  • $\theta = 180°$ — backspin,
  • $\theta = 270°$ — left sidespin,
  • in between — combinations (e.g. a kick at $\theta \approx 20°$ is 94 % topspin + 34 % sidespin).

The frontend decomposes $(\text{rate}, \theta)$ into the pair $(\omega_\text{top}, \omega_\text{side})$ as

$$ \omega_\text{top} = \mathrm{rate}\cdot\cos\theta,\qquad \omega_\text{side} = \mathrm{rate}\cdot\sin\theta, $$

which is what the API receives. The function _topspin_sidespin_to_omega() then assembles $\vec\omega = (0,\,\omega_\text{top},\,\omega_\text{side})$. The schema bounds are accordingly symmetric ($\pm 5000\ \mathrm{rpm}$) on both components so the dial can sweep the full circle at maximum rate without producing out-of-range values.

6. Numerical integration

The three vector forces from the previous chapters give us a non-linear ODE system (quadratic drag + velocity-dependent Magnus). No closed-form solution exists — we have to integrate numerically. The choice is an explicit Euler scheme (more precisely, a semi-implicit variant) with a fixed step $\Delta t = 3\ \mathrm{ms}$:

$$ \begin{aligned} \vec v_{n+1} &= \vec v_n + \vec a(\vec r_n,\vec v_n)\,\Delta t,\\ \vec r_{n+1} &= \vec r_n + \vec v_{n+1}\,\Delta t. \end{aligned} $$

Notice that position uses the updated velocity — that makes it symplectic (Verlet-style) Euler, which is energy-conserving over long runs, unlike naive Euler that slowly drifts.

Why $\Delta t = 3\ \mathrm{ms}$ is enough

A serve flight lasts 0.5–1.0 s, i.e. 167–333 integration steps. At 50 m/s the ball moves $50 \cdot 0.003 = 0.15\ \mathrm{m}$ per step — narrower than the net itself. Critical events (net crossing, bounce, wall hit) are therefore captured with a spatial error below 15 cm, and they're refined further by linear interpolation between two steps:

$$ f \;=\; \frac{x_\text{target} - x_\text{prev}}{x - x_\text{prev}},\qquad z_\text{target} \;=\; z_\text{prev} + f\,(z - z_\text{prev}). $$

This gives sub-millimetre precision at crossings, so the simulation's overall trajectory error stays below ~1 cm — well below both the natural noise of a real strike and the imprecision of the aerodynamic model itself.

Vectorised batch integrator

The wall-window scan (section 8) needs thousands of trajectories at once. Instead of looping in Python we use a NumPy vectorised integrator (_batch_integrate()): identical Euler iteration, but every state variable is an array of length $N$, so a single step advances $N$ trajectories simultaneously through one BLAS-level call. For $N = 10\,201$ trajectories ($101 \times 101$ angle grid), the full sweep runs in under 0.5 s on a typical laptop.

7. Net, bounce and service-box validation

A trajectory by itself is just a curve in space. For the simulator to decide whether a serve is IN, OUT or a net cord, it has to monitor three boundary conditions on every integration step.

Net crossing

Detection: compare $x_\text{prev}$ with $x$. If $x_\text{prev} < L_1 \le x$, the ball just crossed the net plane. Linear interpolation pins down both the height $z_\text{net}$ and the lateral position $y_\text{net}$ at that instant, and we compare against the local net height — a parabolic catenary:

$$ h_\text{net}(y) \;=\; h_\text{cen} + (h_\text{post} - h_\text{cen})\, \min\!\left(\frac{|y|}{p},\;1\right)^{\!2}, $$

where the pole $p$ depends on the play mode: in singles $p = 5.029\ \mathrm{m}$ (singles-stick position), in doubles $p = 6.399\ \mathrm{m}$ (outer doubles post). The doubles net therefore sits lower than the singles net at the same $|y|$ because the parabola is stretched over a wider span — at the singles sideline ($y = 4.115\ \mathrm{m}$) the net is 0.977 m in doubles vs. 1.017 m in singles. If $z_\text{net} < h_\text{net}(y_\text{net})$, it's a net cord. The play mode is sent from the UI as play_mode; the service-box width is the same in both modes — the singles sideline always bounds a valid serve per ITF rules.

The bounce — impact detection

Every step checks whether the ball crossed the $z = 0$ plane. If it did, linear interpolation pins down the landing position $(x_\text{bounce}, y_\text{bounce})$ and the speed at impact $|\vec v|_\text{bounce}$ (converted to km/h for the stats panel).

Post-bounce — COR model and rolling

The service-box verdict uses the first-bounce coordinates only, but integration does not stop there — it continues for $T_\text{after} = 0.6\ \mathrm{s}$ past the bounce so the rendered trajectory carries the post-bounce arc (drawn dashed) and the stats panel can report the bounce apex height. The reflection itself is fixed:

$$ v_z' = -\mathrm{COR}_\text{vert}\cdot v_z,\qquad (v_x', v_y') = \mathrm{COR}_\text{horiz}\cdot(v_x, v_y). $$

The pair $(\mathrm{COR}_\text{vert}, \mathrm{COR}_\text{horiz})$ is, however, not universal — it depends on the court surface. In the simulator it lives in the SURFACE_BOUNCE table, and the active surface is chosen via the toolbar in the 3-D scene. The hard-court entry comes directly from the Tennis Warehouse University kick-serve study (twu.tennis-warehouse.com/learning_center/kickserve.php); the others are scaled per the ITF surface-pace ranking:

Surface$\mathrm{COR}_\text{vert}$$\mathrm{COR}_\text{horiz}$Character
Hard (DecoTurf)0.800.65TWU reference; medium pace.
Clay0.750.55High friction — ball loses horizontal speed, topspin "kicks" hard.
Grass0.700.75Low friction — ball skids and stays low, slice doesn't lift.
Carpet (indoor)0.820.70Indoor — fast, lively bounce.
Tartan0.800.65Outdoor synthetic — behaves like hard.

Table — In code this is the surface field on TennisServeInput. It affects the bounce only, not the airborne phase, so two serves with identical inputs but different surfaces follow identical trajectories up to first contact — the difference shows up in the post-bounce arc and the height at the receiver baseline.

After bounce the ball is assumed to roll forward, so the topspin component is overwritten with the value that matches the rolling motion regardless of the in-flight spin axis:

$$ \omega_y' = \frac{|\vec v_\text{horiz}'|}{R},\qquad \omega_z' = 0.50\,\omega_z. $$

This is why the kick "kicks" even when its in-flight spin axis is mostly sidewards — the ground itself rotates the spin axis into pure topspin, and the ball really does shoot up off the court more steeply than a flat serve at the same speed.

Service-box rules

The ITF requires the serve to land in the diagonal service box. From a deuce server ($y > 0$), that's the opponent's deuce box ($y < 0$) on the far side of the net. simulate_one_serve() walks five rules in order:

  1. The ball actually touched the ground (no truncated simulation).
  2. $x_\text{bounce} \ge L_1$ — landed past the net.
  3. $x_\text{bounce} \le L_1 + L_2$ — landed before the service line.
  4. $y_\text{bounce} \cdot y_\text{server} < 0$ — landed on the opposite side (cross-court).
  5. $|y_\text{bounce}| \le w/2$ — landed inside the singles sidelines.
  6. $z_\text{net} \ge h_\text{net}(y_\text{net})$ — didn't clip the net.

On any failure the function returns a specific fault_reason string — useful for the frontend, which tells the user exactly what went wrong ("Long", "Wide", "Wrong service box", "Net", etc.).

8. Wall window — angle-grid scan

The "Wall" training mode answers a practical question: where on the wall do I have to hit so the result is a valid serve? The player stands behind the baseline; a virtual wall sits between him and the net at distance $D$. For every possible launch angle (elevation + azimuth) we check whether the ball:

  1. clears the net at sufficient height,
  2. lands inside the diagonal service box,
  3. passes through the wall plane at $x = D$ above the ground.

If all three pass, we record the point $(y_\text{wall}, z_\text{wall})$ at which the ball pierced the wall plane. The set of all such points is the window on the wall — the patch of points you can target for the resulting serve to be IN.

The angle grid

We sweep $101 \times 101$ combinations of (elevation, azimuth) around the aimed direction. Elevation ($-15°$ to $+8°$) covers every plausible serve angle — from a steeply downward flat serve to a high kick. Azimuth is centred on the diagonal aim direction toward the target service box, with $\pm 14°$ on each side enough to span the realistic spread without wasting work on angles pointing off-court. Total: 10 201 trajectories; the vectorised integrator handles them all in one pass.

Filtering

After integration we have a stack of boolean masks: over_net (cleared the net at centre height), in_box_x, in_box_y (landed in the valid zone), hits_wall (passed the wall plane above the ground). Logical AND across these gives the final set of valid angles — typically 100–500 of the 10 201 depending on parameters.

9. Convex hull and the Shoelace area

The cloud of $(y_\text{wall}, z_\text{wall})$ points from section 8 isn't rectangular. Topspin curls the lower bound of the trajectory; sidespin tilts the entire window; the contact height shifts its vertical position. The real training "window" is therefore an irregular polygon — and we need its true area.

Convex hull

We use the classic Quickhull from SciPy (scipy.spatial.ConvexHull) to find the boundary vertices of the point set. The result is a list of vertices $(y_i, z_i)$ in counter-clockwise (CCW) order. That's enough to draw the polygon on the wall and to compute its area.

The Shoelace formula

For a polygon defined by an ordered list of vertices $(x_1, y_1), \dots, (x_n, y_n)$ the area is

$$ A \;=\; \frac{1}{2}\,\Bigl|\sum_{i=1}^{n}\bigl(x_i\,y_{i+1} - x_{i+1}\,y_i\bigr)\Bigr|. $$

The name "Shoelace" comes from the way the formula looks if you stack the vertex list as two columns and connect them crosswise — it really does resemble lacing up a shoe. It's one of the oldest geometric formulas (Gauss already used it) and we apply it here for the exact area of the wall window (as opposed to the bounding box, which typically over-estimates by 30–50 %).

10. Worked examples

Running serve.py as a script (python serve.py) executes the full pipeline for three reference serves and produces three single-serve PNG charts plus three wall-window PNG charts. The values are picked to illustrate how different spin regimes change both the trajectory and the resulting wall window:

Scenario$v$ [km/h]AngleTopspin [rpm]Sidespin [rpm]Contact height
Flat serve200$-8°$002.6 m
Kick serve155$-5°$300002.6 m
Slice serve165$-7°$$-300$15002.6 m

Tab. 3 — The three reference scenarios from the __main__ block.

What the charts tell us

  • Flat 200 km/h — steep but nearly straight. Apex is low (with the $-8°$ launch angle there may be no apex above the contact point at all). Lands close to the service line.
  • Kick 155 km/h — higher arc, lands closer to the net. 3000 rpm of topspin adds about $0.65\ \mathrm{N}$ of downward Magnus force (~17 % of weight), so the ball drops far more steeply than gravity alone would predict.
  • Slice 165 km/h — visible lateral curve (along $y$). On the top-view chart the trajectory is bent sideways by $\omega_z$.

The wall-window for the same three scenarios (wall at 6 m, deuce side, right-hander) gives:

ScenarioWindow width [m]Window height [m]BBox area [m²]Polygon area [m²]
Flat 200 km/h~0.6~0.3~0.18~0.12
Kick 155 km/h~0.9~0.5~0.45~0.32
Slice 165 km/h~1.1~0.4~0.44~0.30

Tab. 4 — Actual values vary slightly with parameters; this is an order-of-magnitude reference.

Conclusion: the tolerance of the window grows with spin. A flat serve is ruthless — a small aim error means OUT. Kick and slice give the player a 2–3× larger target area, because the curving trajectory has more degrees of freedom (a higher arc, a lateral curve) and still lands inside the valid zone.

11. References

The constants and correlations in the simulator are not made up — they come from wind-tunnel measurements and video analyses of real strokes. The main sources the model draws on:

Ball aerodynamics

  • Stepanek, A. (1988). Aerodynamics of Tennis Balls. The wind-tunnel data behind our correlations $C_d = 0.55 + (1/(22.5 + 4.2\,S^{2.5}))^{0.4}$ and $C_L = 1/(2 + S)$. Widely cited as the standard tennis-ball aerodynamic model.
  • Cross, R. (2003). Physics of overarm throwing. Extends Stepanek's data and contributes the $C_L$ behaviour at moderate spin ratios. The code combines the two.
  • Cross, R. & Lindsey, C. (2013). Measurements of drag and lift on tennis balls in flight. twu.tennis-warehouse.com/learning_center/aerodynamics2.php An interesting independent cross-check: instead of a wind tunnel, the authors extract coefficients from actual flight (speed/height differences between two on-court positions) and report $C_d \approx 0.507$ approximately constant across 34–54 mph with $C_L$ linear in $S$. That diverges from Stepanek by ~8 % in $C_d$ — the authors attribute it to a fuzz ramp mechanism (felt fibres flatten at higher speeds). It is not in conflict with our model: the underlying physics framework is identical (Newton + drag + Magnus), only the empirical constants differ, and the impact on our trajectories falls below the validation precision we already have against the kick-serve study below.

Kick serve and bounce

  • Cross, R. (TWU). The kick serve. twu.tennis-warehouse.com/learning_center/kickserve.php The source of the bounce-model constants: $\mathrm{COR}_\text{vert} = 0.80$, $\mathrm{COR}_\text{horiz} = 0.65$, rolling assumption after bounce ($\omega_y' = v_\text{horiz}'/R$). The article also reports video-measured flight times (0.520 s for a kick, 0.416 s for a flat 200 km/h serve) — our model agrees with both within ~3–10 %.

Geometry and rules

  • ITF (International Tennis Federation). Rules of Tennis. Court dimensions (11.885 m baseline–net, 6.400 m net–service line, 4.115 m singles half-width), net heights (0.914 m centre, 1.067 m post), ball parameters (mass 57 g, radius 33.5 mm). The service-box validator depends on these.