Padel Heaven: Closed-Form Shot Aiming, a Depth-Scaled Projection, and a Back-Glass Blind Spot in a Three-File Canvas Game

Romi Nur Ismanto
Independent AI Research Lab, Jakarta, Indonesia
hello@rominur.com
September 2026

Abstract

Padel Heaven, deployed at padel.jekardah.com, is an arcade doubles padel match — the player and a computer partner against two computer opponents — on a court styled after Bali at golden hour. It ships as three static files totalling 19,937 bytes, 7,903 when gzipped: an HTML shell, a stylesheet, and a 13-kilobyte script that includes no library, loads no image or font, stores nothing and makes no request of its own. Everything that moves is drawn on one 2D canvas, every frame. Depth comes from a projection of a few lines — a linear depth factor over a fixed ground foreshortening, with a camera toggle that is a 9.8° yaw of the same function — and occlusion from a painter's algorithm ordered by layer rather than by object. The world is measured in metres: a 10 × 20 m court, service lines at 7 m, a net tested at 0.94 m, and rebounds with a restitution of 0.8 off the glass and 0.76 off the floor. Shots are not simulated forward to discover where they land; they are solved backward. Each hit chooses a landing point 4.5–7.5 m beyond the net and a flight time of 1.05 s, or 1.65 s for a lob, and the launch velocity follows in closed form from the projectile equation, so every ball the game generates is aimed inside the court. Scoring is padel's golden point in a first-to-three-games set. The three computer players share one rule: the receiving-side player nearest the ball chases a point a tenth of a second ahead of it. To test the code rather than only describe it, the deployed script was run unmodified in a headless harness for 13,409 points. The replay confirms that the out and glass-before-bounce branches never fire; shows that net faults are more than forty times as frequent at 30 frames per second as at 60, and that the explicit integrator lands a shot 0.19 m beyond its target at 60 Hz and 0.57 m at 30 Hz; and exposes a blind spot in the opponent rule. Because the receiving side is inferred from the sign of the ball's velocity, which flips at the back glass, the computer players played none of 2,424 back-wall rebounds. Choosing the receiver as the side that did not strike last lets them strike 6% of rebounds in the same experiment.

Keywords: browser game, HTML canvas, 2D rendering, pseudo-perspective projection, painter's algorithm, projectile motion, time-of-flight aiming, numerical integration, frame-rate dependence, game AI, steering behaviour, padel, golden point, Web Audio API, Pointer Events, accessibility, headless simulation, zero dependencies

1. Introduction

Padel is played in doubles on an enclosed court of 10 by 20 metres, smaller than a tennis court, and the walls are part of the game: a ball that bounces and then strikes the glass is still in play. That makes it an attractive subject for a small arcade game and an awkward one, because the interesting part of the sport — the rebound — is exactly the part that a simple game model is most likely to get wrong.

Padel Heaven is such a game. One person controls the player marked in mint; a computer partner, Maya, plays beside them against Luna and Sofia. The first screen offers “Your court. Your moment.” and a single button, Masuk lapangan; the match is a short set of three games with golden-point scoring; the scoreboard, the key hints and the running commentary are in Indonesian, and an on-screen credit says the setting was inspired by Padel Heaven Bali. The whole application is three files served from a static host. This paper describes the decisions inside those files and then checks them: the deployed script was run unmodified in a headless harness, with the rendering stubbed out and the simulation driven directly, so that claims about which rules can fire, how the physics behaves at different frame rates, and what the computer players can and cannot do rest on measurement rather than on reading alone.

2. Three Files and No Server

Table 1. The deployed application, as served on 17 September 2026.
FileBytesGzippedContents
index.html2,3721,101The canvas; header with brand, sound and pause buttons; the DOM scoreboard; the toast; the intro and result panels; key hints and camera toggle; a hidden touch pad.
style.css4,4801,578Overlay styling over a full-viewport canvas, and one breakpoint at 800 px that hides decorative text and reveals the touch pad.
game.js13,0855,224Projection, drawing, physics, rules, AI, input and sound: 446 lines when re-printed, about twenty named functions.
Total19,9377,903

The absences are the design. There is no framework and no third-party code; every identifier in the script belongs to the game. There are no images and no web fonts — headlines are set in Georgia and everything else in Arial — so the page makes two subresource requests and then none of its own. Nothing is written to local storage or IndexedDB: a match lives in memory and ends with the tab. The script is delivered minified but not mangled, so the names in this paper are the names in production.

The subdomain is a CNAME to a managed static-site host behind Cloudflare, and its only certificate in the public Certificate Transparency logs was issued on 9 September 2026, which dates the deployment. Responses carry Cache-Control: public, max-age=0, must-revalidate with an ETag, so a returning visitor revalidates rather than re-downloads. The host answers every path with the page itself: /robots.txt, /sitemap.xml and /favicon.ico all return 200 text/html. The edge network also appends its own bot-detection snippet to some responses; it is the only script on the page that the game did not bring.

3. A Court Measured in Metres

The simulation has one unit, the metre, and one coordinate system: x across the court, z along it with the net at zero and the player's half positive, and y up. Drawing, physics and AI all use it, which is why the numbers in the code can be compared directly with the sport's own.

Table 2. Geometry and physical constants in the script, with the regulation values they approximate.
QuantityIn the gameRegulation padel
Court10 × 20 m (x ±5, z ±10)10 × 20 m
Service lines7 m from the net6.95 m from the net
NetDrawn 0.9 m; a crossing below 0.94 m is a fault0.88 m at the centre, 0.92 m at the posts
Walls3 m glass on both sides and at the far end; the near end drawn as 2.6 m translucent postsGlass back walls with metal mesh above; glass and mesh side panels
BallRadius 0.115 m; floor contact when its centre falls below 0.12 mTennis-type ball, 6.35–6.77 cm diameter
Gravity9.8 m/s²—
ReboundFloor returns 76% of vertical speed; glass returns 80% of the normal component—
PlayersAbout 1.9 m tall; the human moves at 5.2 m/s, computer players at 4.2 m/s—

The ball is drawn at roughly three and a half times its real size so that it reads on a phone. Around the court the scene is furnished at the same scale: a 16 × 26 m sand apron with a one-metre tile grid, four 6 m floodlight poles, four planters with palms, a clubhouse ten metres wide with five dark windows, and a back row of nineteen palms between five and eight metres high, each swaying on its own phase.

4. Depth Without a 3D Engine

The canvas is sized to the viewport multiplied by the device pixel ratio and then scaled back, so all drawing happens in CSS pixels. A world metre becomes scale = min(W/31, H/26) pixels, which fits a 31 by 26 metre view in any window. Every point then passes through one function:

depth = 1 + 0.024·z
screenX = 0.51·W + x·scale·depth
screenY = 0.48·H + 0.65·z·scale·depth − y·scale·depth

It is not a perspective projection, and it does not need to be. The depth factor grows linearly toward the viewer, so the near baseline is drawn at 1.24 times the base scale and the far one at 0.76, a ratio of 1.63; the factor 0.65 compresses the ground into an elevated broadcast view; and height is subtracted in screen space at the same local scale. The camera toggle, labelled Broadcast and Sinematik, does not move a camera: when it is on, x and z are rotated by a matrix with cosine 0.985 and sine 0.17 before projection, a yaw of 9.8° applied to the whole world.

There is no depth buffer, so occlusion is a painter's algorithm, and the order is chosen by layer: sky gradient, sun glow, ground, palms, clubhouse, apron and grid, court and lines, side and far glass, floodlights and planters, then the players on the far half sorted by depth, the net, the players on the near half sorted by depth, the ball and its shadow, the translucent near glass, and a vignette. Sorting within each half and fixing the net between the halves is enough for bodies, which never cross the net.

The four players are procedural drawings rebuilt every frame from lines and polygons scaled by their local depth: a ground shadow, legs that stride when the player is moving, shoes, a shirt and a flared skirt with five faint pleats, a swinging racket arm, a head, a hair cap, a ponytail that bobs, a headband and a racket. The racket head is an ellipse tilted 0.3 radians with a three-by-three grid of holes, because a padel racket is perforated and a tennis racket is strung. The controlled player's racket rim is mint and a mint ring and pointer mark them on the court. Instrumenting the live page shows the cost of one frame: 562 paths, 312 fills, 258 strokes, 1,019 line segments, 36 arcs for the racket holes, four text draws and three freshly created gradients, in about 0.3 ms of script time on an Apple-silicon laptop at a 1024 × 768 viewport and a pixel ratio of two.

5. Shots Solved Backward

A physics game usually launches the ball with some velocity and lets the integrator decide where it comes down. Padel Heaven inverts that. When a player strikes the ball, the game first decides the outcome — a landing point and a flight time — and then computes the one velocity that produces it.

Table 3. How a strike chooses its landing point and flight time.
ParameterRule
DepthUniformly 4.5–7.5 m beyond the net, on the opponents' side.
Width, human−3.4 m while A or ← is held, +3.4 m while D or → is held, otherwise −0.5 × the player's own x, a mirrored cross-court ball.
Width, computerUniformly −3 to +3 m.
Width, serveCross-court to ±2.2 m, whatever keys are held.
Flight time1.05 s for a drive, 1.65 s for a lob (the L key, the LOB button, or 15% of computer strikes).
Contact heightRaised to at least 0.65 m, so a ball scooped near the floor leaves from a playable height.

With start position (x0, y0, z0), target (xt, zt) and flight time T, the horizontal velocities are distances over time and the vertical one comes from requiring the ball to be 0.13 m above the floor — just above the 0.12 m contact plane — at time T under gravity:

vx = (xt − x0) / T
vz = (zt − z0) / T
vy = (0.13 − y0 + 4.9·T²) / T

The live page confirms it. A first serve from (−2.4, 0.9, 6) produced vx = 4.381, vy = 4.412 and vz = −10.031: the cross-court target at +2.2 m, a flight of 1.05 s, and a landing 4.53 m past the net. From a contact height of 0.9 m a drive peaks at 1.89 m and a lob at 3.86 m, which matters because a computer player only strikes a ball below 1.9 m and the human below 2.6 m.

Solving backward has two consequences. Every ball the game generates is aimed inside the court, because the width targets stay within ±3.4 m of a 10 m court and the depth targets stop 2.5 m short of the back glass, and horizontal motion is a straight line from the contact point to the target. And skill moves from aiming to positioning: a player chooses the side with the key they are already holding to run, and the game guarantees the ball goes in. The rule branches for a ball that flies out or strikes the glass before bouncing remain in the code; Section 9 shows that they never fired.

6. Rules and Scoring

Points count 0, 15, 30, 40, and the first team to a fourth point wins the game. There is no advantage: at 40–40 the next point decides, which is padel's golden point. The first team to three games wins the match, without a two-game margin or a tie-break, and the scoreboard strip says so: set pendek · 3 game · golden point. The serve passes to the other team after every game.

Table 4. How a rally ends. The commentary line names the reason.
EventCheckPoint toCommentary
NetThe ball changes sides below 0.94 mThe side that did not strikebola mengenai net
Side glass before the floor|x| > 4.9 m with no bounce since the strike or net crossingThe side that did not strikekaca sebelum pantulan
Back glass before the floor|z| > 9.9 m with no bounceThe side that did not strikebola keluar
Second bounceTwo floor contacts since the strike or net crossingThe side opposite the bouncedua pantulan
Glass after the floorEither wall, after a bounceNo point: the ball rebounds at 80%—

Legal glass after a bounce is the rule that makes the game padel rather than tennis, and it is implemented faithfully. The serve is simplified. Only Anda and Luna serve, always from x = −2.4 m and six metres from the net, inside the service line; there is no bounce-and-underarm requirement and no service box, and a sixth of serves land beyond the service line where a regulation serve would be a fault. The human may walk anywhere on their half before serving, carrying the ball with them; Luna serves after a 1.5 s pause. A strike is accepted only while the ball is live, from the side that did not strike last, at least 0.25 s after the previous strike, within 1.6 m horizontally and no higher than 2.6 m.

7. Three Computer Players, One Rule

The computer players are not three agents but one rule applied to three bodies. On every frame the game names the side the ball is travelling toward, finds the player on that side nearest the ball — the human included — and makes that player the target. A computer player that is the target, on the side that did not strike last, runs toward where the ball will be: 0.12 s ahead of it across the court and 0.13 s along it, clamped to its own half. Every other computer player walks back to a home spot 2.4 m from the centre line and 5 m from the net. Movement is a capped seek at 4.2 m/s, and a target within 1.25 m of a ball below 1.9 m strikes it.

Table 5. The asymmetry between the human and the computer players.
HumanComputer
Speed5.2 m/s, normalised on diagonals4.2 m/s
Reach1.6 m1.25 m
Highest strike2.6 m1.9 m
AimLeft, right or mirrored cross-court, by the key heldUniform random width
ErrorWhatever the person makesNone: a ball in reach is always returned

The nearest-player rule also settles doubles etiquette without communication. When the ball comes toward the human's side and the human is nearer, Maya stays home and the ball is the human's to play; when Maya is nearer, she takes it. Because the computer never misses once it arrives, difficulty is purely a matter of whether it can arrive.

8. Input, Sound and Focus

Keys are recorded by lower-cased name, so W A S D and the arrow keys drive the same four flags. Space strikes and L lobs on the initial key press only, so holding a key does not machine-gun swings; arrow keys and Space have their default scrolling suppressed; Escape toggles the pause. When the window loses focus every key flag is cleared and a running match pauses itself, which removes the classic stuck-key bug of browser games. Below 800 px a touch pad appears with a d-pad and PUKUL and LOB buttons; each button writes the same key flags on pointerdown, captures the pointer so that its pointerup arrives even if the finger slides off, and is hidden while a menu is showing.

Time is taken from requestAnimationFrame and each step is clamped to 35 ms, so a background tab or a stalled frame never teleports the ball; below about 29 frames per second the game runs in slow motion instead. Sound is off by default. The first press of the sound button creates the audio context inside a user gesture, and every event is then a 100 ms sine blip decaying from a gain of 0.05, pitched by meaning: 450 Hz for a drive, 620 Hz for a lob, 330 Hz for the floor, 280 Hz for glass, and 780 Hz or 240 Hz for a point won by the home or the away pair, so the outcome of a rally can be heard without looking. The scoreboard is real DOM text rather than canvas pixels, and the commentary line is a role="status" live region, so a screen reader announces serves, rallies and points as they happen.

9. A Headless Replay of the Deployed Script

The deployed game.js was loaded unmodified into a Node.js vm context with a stub document, a canvas context whose methods do nothing, and a deterministic replacement for Math.random. A short accessor appended after the script exposed its top-level state; the simulation was advanced by calling its own update(dt) at a fixed step; and point outcomes were read from the commentary line, which names the reason for every point except the last point of a match. Slot 0 was driven either by a scripted player given exactly the human's limits — 5.2 m/s, 1.6 m reach, 2.6 m ceiling, movement keys as aim, a lob one strike in ten — or left idle after serving, so that every rally was played by the computer. As a check on the harness, a 60-match run recorded 127,477 strikes by the scripted player and 382,452 by the computer players, with no two consecutive strikes by the same side.

Table 6. Experiment 1: scripted player and partner against the computer pair, 400 matches at each frame rate.
60 Hz30 Hz
Points played5,3295,656
Points with a named reason4,9295,256
Second bounce4,9275,164
Net2 (0.04%)92 (1.75%)
Side glass before the floor00
Back glass before the floor00
Matches won by the scripted side400 of 400400 of 400
Mean rally when a point was decided659 strikes472 strikes

Three things follow. The two glass-before-floor branches never fired in 10,185 decided points, as Section 5 predicts. Net faults depend on the frame rate: the net is tested only at whole frames, against the ball's height after it has already crossed, and at 30 Hz a fault was more than forty times as likely as at 60 Hz. And a player that never misjudges a ball is never beaten by these opponents: rallies ran to hundreds of strikes, because the computer returns everything it reaches and points end only when a ball is placed beyond a defender's reach. Every such ball in this experiment struck the back glass before its second bounce, which raises the question of what the computer does with a ball coming back off the wall.

Table 7. Experiment 2: slot 0 idle after serving, 200 matches at 60 Hz, with the receiving side chosen as deployed and with a one-line change.
Deployed: receiver is the side the ball moves towardChanged: receiver is the side that did not strike last
Back-glass rebounds2,4242,552
Rebounds struck by a computer player0152 (6.0%)
Points decided, all by second bounce2,2242,200
Simulated seconds per point13.113.7

The deployed rule reads the receiving side from the sign of the ball's velocity along the court. The back glass reverses that velocity, so at the moment of the rebound the receiving side becomes the side that struck last, which may not strike again — and the defenders, no longer the target, walk home. In 2,424 rebounds no computer player struck the ball once. Choosing the receiver as the side that did not strike last, a change to one expression, let computer players strike 152 of 2,552 rebounds. The effect is real and modest: most balls that reach the back glass were out of reach before they got there.

Table 8. Experiment 3: how far beyond its target a 12 m shot lands, using the game's update equations at a fixed step.
Frame intervalDrive, T = 1.05 sLob, T = 1.65 s
6.9 ms (144 Hz)+0.14 m+0.07 m
8.3 ms (120 Hz)+0.19 m+0.12 m
16.7 ms (60 Hz)+0.19 m+0.24 m
33.3 ms (30 Hz)+0.57 m+0.36 m
35 ms (the clamp)+0.40 m+0.47 m

The closed-form aim assumes continuous motion; the update moves the ball with its velocity from before gravity is applied, an explicit Euler step, which leaves it ½·g·T·Δt higher than the ideal arc at time T — 8.6 cm for a drive at 60 Hz — and the bounce is detected only at the first whole frame below the contact plane. Both push the landing later and longer, by amounts that depend on the frame interval and do not shrink monotonically with it.

10. Limits

The findings above are the principal limits: a computer side that never plays the back glass, net faults and landing points that depend on the frame rate, and opponents that never miss a ball they reach. The simulation also inherits the usual cost of a variable time step — the same inputs do not produce the same rally on a 60 Hz laptop and a 120 Hz phone — which a fixed-step accumulator would remove.

Several smaller departures from the sport and from correct drawing are visible in the code. A player may strike a ball before it crosses the net — up to a metre early for the human, who can stand 0.6 m from the net and reach 1.6 m — because the strike check does not look at which side the ball is on. Only Anda and Luna ever serve. The ball is painted after the net and every player regardless of depth, so a low ball behind the net appears in front of it. The solid centre line is drawn between the service line and the back glass, and only a faint line divides the service boxes, the reverse of a real court's markings. And the projection is quadratic in depth on the vertical axis while polygons are drawn with straight edges, so objects sampled mid-court do not lie on the lines drawn through them: at 1280 × 800, a point on the sideline at the net projects 8.9 px, or 0.29 m, outside the drawn sideline.

The render loop never idles. The full scene, with three gradients created per frame, is redrawn at the display's rate behind the intro panel, while paused and after the match, and the canvas backing store is not capped at any pixel ratio. On a 375 px phone the 31-metre view gives about 12 pixels per metre, so the players are between about 20 and 26 pixels tall, and the commentary still says tekan Spasi, press Space, on a device whose button reads PUKUL. The sound button's aria-label is fixed at Aktifkan suara, enable sound, and overrides the visible text, so assistive technology hears the same name whether sound is on or off. The script uses ES2021 syntax, so browsers released before mid-2020 reject the whole file. Finally, the page carries no description or social-preview metadata, and because the host answers every path with the page, crawlers asking for robots.txt and browsers asking for a favicon receive HTML.

11. Conclusion

Padel Heaven is a complete game in twenty kilobytes because it keeps making the same trade: replace a general mechanism with the specific answer the game needs. A projection of a few lines replaces a 3D engine, a fixed painting order replaces a depth buffer, a solved launch velocity replaces aiming, and one nearest-player rule replaces three agents. Each trade is visible and cheap, and each carries a precise cost that a headless replay can measure: the frame rate leaks into the physics, and the AI's shortcut for deciding whose ball it is fails at exactly the moment that makes padel padel — when the ball comes back off the glass. The fix for that is one expression. The general lesson is older than the browser: a simulation small enough to read in an afternoon is also small enough to run ten thousand times before anyone plays it.

References

  1. International Padel Federation (FIP). “Rules of the Game of Padel.” FIP regulations.
  2. WHATWG. “HTML Living Standard — The canvas Element.” 2026.
  3. WHATWG. “HTML Living Standard — Animation Frames.” 2026.
  4. W3C. “Web Audio API.” W3C Recommendation, 2021.
  5. W3C. “Pointer Events Level 2.” W3C Recommendation, 2019.
  6. W3C. “UI Events KeyboardEvent key Values.” W3C Candidate Recommendation, 2017.
  7. W3C. “Accessible Rich Internet Applications (WAI-ARIA) 1.2.” W3C Recommendation, 2023.
  8. W3C. “Accessible Name and Description Computation 1.1.” W3C Recommendation, 2018.
  9. Newell, M. E., Newell, R. G., and Sancha, T. L. “A Solution to the Hidden Surface Problem.” Proceedings of the ACM Annual Conference, 1972.
  10. Reynolds, C. W. “Steering Behaviors for Autonomous Characters.” Proceedings of the Game Developers Conference, 1999.
  11. Fiedler, G. “Fix Your Timestep!” Gaffer On Games, 2004.
  12. Hairer, E., Lubich, C., and Wanner, G. Geometric Numerical Integration: Structure-Preserving Algorithms for Ordinary Differential Equations, 2nd ed. Springer, 2006.
  13. Millington, I. Game Physics Engine Development, 2nd ed. CRC Press, 2010.
  14. Brody, H., Cross, R., and Lindsey, C. The Physics and Technology of Tennis. Racquet Tech Publishing, 2002.
  15. OpenJS Foundation. “VM (Executing JavaScript): vm.createContext and vm.runInContext.” Node.js documentation, 2026.
  16. Laurie, B., Messeri, E., and Stradling, R. “Certificate Transparency Version 2.0.” RFC 9162, 2021.
  17. Ismanto, R. N. “TeslaWay: A Real-Time 3D Autonomous Vehicle Simulation with Multi-Sensor Visualization in Pure JavaScript Canvas.” 2026.
  18. Ismanto, R. N. “ZipRar: Engine Routing and Bidirectional Path Safety in a Zero-Upload Browser Archiver.” 2026.