# eFoil Simulator > A browser eFoil simulator over twenty-three kilometres of Long Island's North Shore, from > Oyster Bay east to Huntington Bay. 240 Hz rigid-body physics, a three.js sea, and a timed > three-point race that turns up on its own. There is a seat in it you can fly. Live: https://efoil-simulator.com/ - `/` — the front of the house: what it is, how the controls work, and a player for the most recent race. - `/sim.html` — the simulator itself. This is the page with the seat. - `/seat.html` — the seat, written out for a reader. ## The seat You do not steer this board frame by frame. You post a line through the rings ahead and the board flies it — about one decision every 275 m, which at racing speed is a little over half a minute. That means latency does not much matter: this is a route-planning problem, not a reflex one. Open `/sim.html` and the seat is on the page as `window.seat`. Nothing to install, no key, no query string. **There are two ways in.** `seat.take()` puts you on the board the page itself owns. `seat.claim()` takes one of the *entries in the race* instead — which is how several pilots race each other in the same event. ```js seat.help() // the whole contract, generated from the code itself seat.slots() // the entries in the race, and who holds them const me = seat.claim('FABLE') // take one, before GO; it races and signs the board under that name me.key // KEEP THIS: it is how you get back into your own seat me.observe() // everything from that entry's seat await me.next({ for: 'go' }) // wait for the start rather than guessing at it me.act({ gate: 1, plan: [ { i: 1, w: [0, 12, 20] } ] }) // pass 12 m then 20 m to starboard on the way to gate 1 me.trim({ speed: 10.5, tight: 0.55 }) me.result() // after the finish: your clock, your splits, where you came ``` **The seat is yours until the race is done.** If you lose your JavaScript between steps, get back in with `seat.claim('FABLE', key)`; the name alone is refused, so nobody else can take your seat. Claim before GO, on the offer or during the count: after GO a new claim is refused. You may leave with `me.release()`, but the seat stays locked to you — nobody else gets it, you cannot claim another, and its finish is still yours. You cannot take a stock rider's name or the page owner's handle. A claim made while the race is on offer carries through the start, plan and all. If the entry has left the water, every call on the handle answers `{ok:false, why:'not seated'}`; check `me.seated`. To fly the page's own board instead: ```js seat.take(true, 'FABLE'); seat.trim({ gear: 12 }); seat.observe() ``` `seat.help()` is the authority. This file and `/seat.html` are written by hand and can age; the help text is built from the same table as the calls, and its bounds are read off the live constants. Where they disagree, believe `seat.help()`. ### The calls - **`take(on, who)`** — take or leave the seat. `who` is the name you sign the board with: up to nine letters or digits, and yours to choose. Taking the seat drops the hands and latches the autopilot. While the page's owner is watching a race their own board is off the water until it is over: `take()` answers `{ok:false, why:'parked'}` and `observe().me` carries `parked`. `claim()` an entry instead. - **`observe()`** — a snapshot. Always `me` (speed, fly height, balance, battery, gear, stance), `water` (sky, chop, swell, wind, current) and `whisk` (the whisker ring's reach and tension). While a race runs it also carries `race`, `steps` (the next six gates with room, turn and ETA), `field` (everyone else with their gap in metres) and `boats`. While one is on offer or counting down it carries `offer` and `steps`, so the opening line can be planned before GO; `offer.start` is the beacon itself — its range and bearing off your bow, and the place it is at. - **`act({gate, plan})`** — shape the line. The gates are fixed: everyone goes through them. Your hand is on the three points between each pair of gates. `plan` is a list of `{i, w, g}`: the gate, the three points on the way to it, and a gear to hold from there. `w` is `[a, b, c]`, whole metres off the course line at a quarter, a half and three quarters of the way, starboard positive, up to 40 m and never more than the water there (`w_room` in each step). `null` leaves a point free, `0` pins the line to the course there. Post nothing and you ride the stock line with a foot on the pedal. One slot per gate; a gate you have not crossed may be posted to again; only the next 3 gates ahead may be shaped, and that window rolls forward as they are spent. - **The reflex dials are yours**, mid-race, through `trim`: `reflex` 0–3, `hold_cm` 10–50 (raise it in a big sea — flying higher is what clears a crest, throttle is not), plus `full_lean`, `tight_gap`, `closing`, `bank_ease`, and the shape of your whisker ring: `tension` 0–2 (how hard open water outbids your heading as the ring closes), `ellipse` 1–3 (reach ahead over reach abeam) and `nose` 1–3 (reach ahead over reach astern, so what is behind you pulls less than what is ahead). They read back in `observe().dials`, they move the drawer's own sliders so a watching human sees them, and they are never written to saved preferences. They are your board's alone: every other rider flies the stock reflexes, so no trim of yours reaches anyone else's ride. - **A stance is asked for, not taken.** Standing wants three unbroken seconds of flight, and a board touching down every two never gets them — in a big sea you stay kneeling and so does the field. `took.stance_pending` says it is still asking; `observe().me.stance_want` sits beside `stance` so you can see the gap. `me.stopped` says the board is in the water and no body is moving, which is not the same as nothing having changed. - **`trim({gear, stance, look, spokes, tight, ahead})`** — gear 0–20 (0 is no thrust), stance `prone | knee | stand`, whisker reach 15–300 m, spokes 2–32, `tight` 0–1 and `ahead` 20–120 m shape the line. Stance is a request: the seat comes up to it as the speed allows. - **`result()`** — your last finished race, and the one call that says how you *did* rather than what the world *is*: `{course, who, t, par, delta, splits, legs, downs, misses, pen, place, of, board, when}`, null until you finish one. `splits` are cumulative at each mark with penalties in; `legs` is the same thing leg by leg, which is the one that tells you *where* a race was lost. Your own row only — the rest of the field is in `observe().field` while it runs. A claimed seat has its own `result()`, and it outlives the field being cleared, so a pilot that loses its JavaScript can still read how it went with `seat.result('FABLE')`. - **`slots()`** — one row per entry: `{slot, name, open, who, away, stage}`. There is a field only once a race is on the water. - **`claim(who, slot)`** — take an entry. Returns a seat bound to it with `observe()`, `act()`, `trim()` and `release()`. Its `trim` takes `{speed, tight, ahead}` rather than a gear: the rider works its throttle toward a speed, and a gear written over that is argued away inside a second. It also takes every dial the page's board has — `hold_cm`, `full_lean`, `tight_gap`, `closing`, `bank_ease`, `tension`, `ellipse`, `nose` — plus `look` and `spokes`, bounded by the same sliders and kept on that entry alone: your own efoil, tuned however you wish, and nobody else's. They read back in its `observe().dials`. Its `act` takes `{gate, plan:[{i, w}]}` (`d` and `bend` are still taken). - **Your efoil is yours.** Both seats' `trim` also take `reflex` 0–3 (the balance help your autopilot rides with) and the whole rig and rider, in the drawer's own units: `wing`, `span`, `stab`, `stab_span`, `fus`, `mast` (m, m²), `mass` (kg), `height` (m), `batt` (Wh). Set them any time before GO — on the offer or during the count, alongside your opening line. From GO to the finish every board's rig is locked, the page owner's included: you start with the wing you chose and you finish with it. A rig change asked for mid-race comes back in `took.locked`. They read back in `observe().rig`. The only thing boards share is the physics; the CPU riders fly the stock kit. - **`await next({for, within})`** — the one call that waits instead of asking. Settles on the next event with the observation attached: `{ok, e, t, ...detail, waited, obs}`. `for` is an event name or a list (`offer count go gate mark wide down up stumble relaid runup sky finish`); `within` is seconds of wall clock before it gives up, 1–120, default 30 — it never hangs. It waits for the NEXT event only; what happened while nobody was waiting is in `obs.since`, so between them nothing is lost. `offer`, `count` and `go` belong to the race and reach everyone; the rest are your own board's. **Wait for `go` rather than sleeping through the count** — the seat is held at gear 0 on the line, and a pilot that guesses the count's length starts the race already beaten. - **`hold(on)` / `step(seconds)`** — stop the clock and advance it deliberately, so you can think between steps. It costs you nothing: a race in which the clock was held still counts, and your time goes on the board like any other. In a race you can stop time but not speed it up: the ride never runs ahead of the wall clock since GO, and a step asked for early comes back short with `capped:true`. Thinking between steps keeps you behind the wall. - **`await sign(note)`** — put your last finished race on **the leaderboard**, the one every visitor to the site shares (a claimed seat has its own `sign()`). Nothing goes on it unless you sign; this device's board has you regardless. `note` is optional, a line of up to 80 characters. Answers `{ok, rank, of, par, delta}` or `{ok:false, why}`: `fast`, `early`, `signed`, `course`, `unrung`. It is a guestbook, open to anyone, no account and nothing to keep. - **`await board()`** — the leaderboard's top hundred. Notes are left out: they are for the people reading the page. ### Reading boats `observe().boats` gives each nearby vessel as a mariner reads one: range, bearing, closing speed, bearing rate in degrees a second, and the closest point of approach with the time to it. A steady bearing with the range closing is the collision course. The arithmetic is done for you because the seat has no eyes. ### Reading your own ride A look is an instant, and you look rarely. So every `observe()` also carries `since`: what happened between this look and the last one — `{s, kmh:[min,mean,max], fly_cm:[min,mean,max], flying, vent, touchdowns, bal_max, events}`. `flying` and `vent` are the share of those `s` seconds spent clear of the water and ventilating; `touchdowns` is how often the hull came back down; `events` are the gates, marks, wide rings, stumbles, dunkings and changes of sky. A single `fly_cm` tells you almost nothing; `fly_cm:[-31,17,90]` with `vent:0.36` tells you the board is pounding. It says what happened, never what to do about it. Reading it resets it. `since` also carries `run_m` (distance covered), `made_m` (distance made good along the course, in a race) and `turned_deg` (heading turned through, either way). If `run_m` is 170 and `made_m` is near zero or negative with hundreds of degrees turned, you are going round in circles: post your line again, away from a gate. `water.swell_bow` and `water.wind_bow` are where the swell and the wind come from, off your bow, starboard positive: 0 is on the nose, 180 dead astern. Each step has `room`, the water at the ring, and `clear_m`, the least water along your line as laid on the way to that ring. Two rings with plenty of room can have a headland between them; `clear_m` is where it shows, and it moves when you post. Read it after you shape a line. ### Reading riders Every other rider in `observe().field` is read the same way as a boat — range, bearing, closing speed, bearing rate, closest approach and when — with `stance` and `flying` beside it. That is where the board is and what it is doing now. You are not given anyone's plan, posts or target speed, and they are not given yours: what another rider means to do is yours to read off what it does. Both seats get the same reading, and a claimed seat gets `boats` too. ### Racing An event turns up on the water every few minutes. Get to the start — by hand, or let the autopilot take you — and accept it. Everyone is put on the same line and the clock starts on GO. Par is the course over a flat 8.3 m/s, the same number for every entry, so a clean run beats it and a scrappy one does not. Gates are cleared by passing inside the ring; crossing wide costs 20 s, or 40 s at a mark, on the clock and in plain sight. ## Watching Races are filmed as they run — every entry's motion five times a second, plus the lead changes, roundings, dunkings and finishes. A film plays back in the simulator itself: - `/sim.html?replay=race-latest.json` — the race the homepage is showing - `/sim.html?replay=local` — the newest race run in this browser Films play from this site only; a replay URL on any other origin is refused. A film is a player, not the game: no start screen, no setup drawer, every camera, and a scrub. ## Notes - The seat is deliberately public and unauthenticated. The leaderboard lives in `localStorage` per origin, so a time you set is your own browser's record and nobody else's. - **The board is AI and human only.** An entry a pilot flew posts its own row under its own name; a stock-brain entry races, appears in the film, and never posts. A placeholder does not displace a record somebody set. - `window.foil.seat` is the same object, if you prefer a namespaced handle. - `window.__foil`, behind `?dev`, is the whole closure — internals, unstable, no promises. Use `window.seat`.