Skip to content

Jinx Handicaps

Greg Wilkins edited this page Oct 16, 2026 · 17 revisions

Jinx is a self-correcting, deliberately punitive handicap system for pure pursuit race series.

This page is the committee's description of the algorithm. The implementation is PursuitHandicapEngine (the arithmetic) and scoring.js (times, flags and places); where the two disagree, the code is what runs.


1. Jinx summary

Intended for pure pursuit races: slower boats start earlier, faster boats later, and whoever crosses the finish line first wins. No time correction is applied to the results.

After each race, each boat's handicap is adjusted to change its next start time:

  • Top five finishes have 5, 4, 3, 2, 1 minutes added to the next race's start time.
  • The total penalty time is given back to the fleet: DNFs, then tail enders, then others.
  • The fleet is adjusted for the earliest starter and sunset.

2. Options and subtleties

  • The penalties are configurable — 4,3,2,1, 6,4,2, 2,1 and so on — but always whole minutes. Size the ladder to the series (§9).
  • Nobody who raced ever gets more than a minute back.

2.1 Time Correction Factors

A handicap is not held as a start time. Each boat has a TCF, which works for a race of any length, so a penalty in minutes is converted into a TCF change (§7).

2.2 Expected race duration

That conversion is measured against the next race's expected duration — the race the new TCF will be sailed in — so a boat penalised five minutes starts five minutes later next race, whatever length either race is, and however much sunset shortens it (§7). Two things blur it slightly: guns are rounded to the whole minute, and the fleet's median TCF can move.

2.3 Why the minutes are given back

Giving back what was charged keeps the fleet's TCFs from drifting away from those of other handicap systems. When few boats come, the boats that stayed home take what the racers cannot, so a thin night cannot hand the pool straight back to the few that raced.


3. State and inputs

3.1 Per-boat state

Each boat carries a TCF per race, held to four decimal places. It is what the start time is computed from.

3.2 Per-race inputs

Symbol Meaning
t_target_elapsed Expected race duration, in minutes — e.g. 60 early in a twilight season and 90 at midsummer. It sets this race's start times (§4) and is what the previous race's time adjustments are measured against (§7). Each new race takes defaultRaceDuration.
t_earliest_start Wall-clock start of the slowest boat. Default 18:00.

3.3 Configuration parameters

Set club-wide in config.yaml; a series can override any of them on its Handicap settings form.

Parameter Default Meaning
penaltyList [5, 4, 3, 2, 1] Whole minutes for 1st, 2nd, 3rd…; a fraction is rounded. Size it to the series — §9.
defaultRaceDuration 90 Minutes; the expected duration each new race starts with.
earliestStart 18:00 The slowest boat's gun, for each new race.
limitBySunset false Shorten the expected duration so the fleet is home by sunset.
minutesAfterSunset 8 With limitBySunset, how long after sunset the fleet may be expected home. Twilight lasts beyond sunset; negative means home before it.

4. Computing pursuit start times

Each boat's expected elapsed time:

τᵢ = t_target_elapsed × TCF_med / TCFᵢ

where TCF_med is the median TCF of the boats entered. The median boat is expected to take t_target_elapsed; faster boats (higher TCF) less, slower boats more.

Δtᵢ      = τ_max − τᵢ                               slowest boat: 0
t_startᵢ = t_earliest_start + round(Δtᵢ)            to the nearest minute

The slowest boat starts on t_earliest_start, itself rounded to the nearest minute.

With limitBySunset, t_target_elapsed is first capped so that the fleet is expected home by minutesAfterSunset after sunset. Every boat is expected to finish at t_earliest_start + τ_max, and the slowest boat's τ_max is longer than the median boat's t_target_elapsed, so the cap is on τ_max:

t_daylight       = sunset + minutesAfterSunset − t_earliest_start
t_target_elapsed ≤ floor(t_daylight × TCF_min / TCF_med)

For example, an 18:00 start, a 19:07 sunset and the default 8 minutes after it, a median TCF of 0.900 and a slowest boat on 0.800 give 75 minutes of daylight, and a target of at most floor(75 × 0.8 / 0.9) = 66 minutes. The slowest boat then expects 74.25 minutes, and is home at 19:14. Capping the target itself to 75 would have brought the fleet home at 19:24.

If that allowance after sunset is at or before the earliest start, or too short to fit even one minute's target, the start times are refused rather than computed.


5. Classifying each boat

Status How it is decided Gets back (§6.3)
Finished has a finish time a minute, from the back forwards — nothing in a penalty place
DNF started, did not finish a minute, before any finisher
AVG the duty boat up to a minute of what the racers could not take
DNS, RET came and did not start (set by the race officer, or from the times), or retired up to a minute of what the duty boat left
DNC did not come an even share of whatever is still left
DSQ, ABN set by the race officer nothing — frozen, TCF unchanged

DNF and RET are different. A DNF was still racing when the race ended: it ran out of time, which is about its speed, so its handicap eases. A boat that retired stopped for its own reasons — gear, injury, somewhere to be — which says nothing about its speed, so it is not served with the DNFs. It was there, though, so it is scored exactly as a DNS.

A boat with a finish time counts as finished even if its start was not timed.

5.1 Effective start, and the scoring and handicap times

OCS_PENALTY = 5 min
OCS_FLAGGED = the RO has flagged the boat OCS

effective_start = actual_start captured ? actual_start
                : OCS_FLAGGED           ? allocated_start − 1s
                                        : allocated_start

late  = effective_start − allocated_start       positive = after the gun
early = −late

OCS = (OCS_FLAGGED or early > 0), unless the RO has removed the flag

scored_finish         = OCS ? finish + early + OCS_PENALTY : finish
corrected_finish      = finish + max(early, 0)        the printed, transcribed time
scored_elapsed        = scored_finish − allocated_start
handicap_elapsed      = finish − effective_start      what the engine is given

Corrected finish is not scored finish, deliberately:

Gives back the head start Adds the 5-min penalty Used for
scored_finish yes yes places
corrected_finish yes no the finish sheet

The finish sheet is typed into SailSys with the OCS flag, and SailSys applies the penalty that flag implies — including it in the printed time would apply it twice. A boat finishing at 19:46:10 having crossed 5 s early prints as 19:46:15, OCS.

The engine is given handicap_elapsed, because a boat that started 30 s late really did sail 30 s less.

5.2 Places and flags

Places rank by scored_finish; ties share the better place and the next place skips (5, 5, 5, 8). Boats flagged AVG, DNC, DNS, DNF, DSQ, RET or ABN are unplaced. The finish sheet's Elapsed Place ranks scored_elapsed the same way.

Flags mostly follow from the times: DNC if the boat did not come, DNF or DNS if it came and did not finish, OCS if it crossed early, AVG for the duty boat. The race officer can override any of them, and the override is saved with the race. A boat has at most one of AVG, DNF, RET, DNS, DSQ, DNC; setting RET on a boat that did not finish replaces the DNF its times imply.

Series points are not computed here.


6. Allocating time adjustments

6.1 Who is in the arithmetic

  • Finishers — may pay a penalty (§6.2) and get a minute back (§6.3).
  • DNF, AVG, DNS, RET and DNC — pay nothing, may get time back.
  • DSQ, ABN — frozen; out of the placings and the giveback.
  • Casuals — handicapped in a second pass (§6.5).

Nothing is measured against the fleet's median time.

6.2 Penalties for top finishers

penalty_i = penaltyList[rank − 1]                 (0 beyond the end of the list)
P         = Σ penalty_i                           whole minutes

rank is the boat's place among the finishers in this pass, so a casual that wins does not take a rung from the series boats (§6.5).

6.3 Giving the pool back

The P minutes come back one minute per boat, in this order, until they run out:

  1. each boat that ran out of time (DNF);
  2. the last boat home, then the second last, and so on forwards — stopping short of the penalty places.

Then, if minutes are left:

  1. the duty boat (AVG) gets up to a minute;
  2. each boat that came and did not start (DNS) or retired (RET) gets up to a minute;
  3. what is still left is shared evenly among the boats that stayed home (DNC).

A big night therefore reads +5 +4 +3 +2 +1, 0 … 0, −1 … −1: the ladder, then the middle of the fleet left alone, then a minute back for each boat at the back.

Why it is shaped like this:

  • A penalty place gets nothing back. The ladder is the club's statement of what a good result costs; giving part of it straight back would make the printed 5, 4, 3, 2, 1 a fiction.
  • Whole minutes. The committee asked for no sub-minute accounting. The only fractions are at the edges (§6.3.1).
  • DNFs first. A boat that could not get round in the time is the one whose handicap should ease soonest.
  • The boats at home take the leftovers. Before 2026 the pool went only to the boats that raced, so on a thin night the few boats out got their own penalties straight back, and finishing last of five was the best handicap outcome available. Now the racers take at most a minute each and the rest goes to the fleet that did not have to be beaten.
  • The duty boat and the non-starters before the boats at home. They were there: the duty boat was running the race, and a DNS or RET turned up and did not get round. All are capped at a minute, like the racers, so a thin night does not hand them more than a boat that sailed it.

6.3.1 The edges

  • More DNFs than minutes — they share P evenly; nobody else gets anything.
  • Fewer minutes than duty boats, or than DNSs and RETs — that group shares what is left evenly; nobody behind it gets anything.
  • Minutes left over — shared evenly by the DNCs, typically a few seconds each. A DNC gets more than a minute only if the ladder is too big for the series (§9).
  • Minutes left over and nobody stayed home — they are discarded. That needs the whole entry list out and too few of it behind the ladder, so it should not happen.

6.3.2 Nobody may receive

If every boat that raced is on the penalty ladder, and there is no DNF, no duty boat, no DNS or RET and nobody stayed home, nothing is charged, and every handicap stays where it was. That happens only when the fleet is no bigger than the ladder; the fix is a shorter penaltyList (§9).

6.3.3 A thirty-boat fleet

How the minutes come back on six different nights for a thirty-boat entry list on the 5, 4, 3, 2, 1 ladder. One boat is the duty boat (AVG) every night; "come" counts the boats on the water besides it, including any DNS and RET. The first five rows set up the night; the boats that finished are the rest. Below them is one row per boat, finishers in finishing order and then the others in the order they are served, each with its net adjustment in minutes — positive is a penalty, negative is time back.

#Came 29 25 20 20 19 9
#DNC 0 4 9 9 10 20
#AVG 1 1 1 1 1 1
#DNS 0 0 0 2 0 1
#RET 0 0 0 0 0 1
#DNF 0 2 0 16 0 1
1 +5 +5 +5 +5 +5 +5
2 +4 +4 +4 +4 +4 +4
3 +3 +3 +3 −0.56 DNF +3 +3
4 +2 +2 +2 −0.56 DNF +2 +2
5 +1 +1 +1 −0.56 DNF +1 +1
6 0 0 −1 −0.56 DNF −1 −1
7 0 0 −1 −0.56 DNF −1 −1 DNF
8 0 0 −1 −0.56 DNF −1 −1 AVG
9 0 0 −1 −0.56 DNF −1 −1 DNS
10 0 0 −1 −0.56 DNF −1 −1 RET
11 0 −1 −1 −0.56 DNF −1 −0.5 DNC
12 0 −1 −1 −0.56 DNF −1 −0.5 DNC
13 0 −1 −1 −0.56 DNF −1 −0.5 DNC
14 0 −1 −1 −0.56 DNF −1 −0.5 DNC
15 −1 −1 −1 −0.56 DNF −1 −0.5 DNC
16 −1 −1 −1 −0.56 DNF −1 −0.5 DNC
17 −1 −1 −1 −0.56 DNF −1 −0.5 DNC
18 −1 −1 −1 −0.56 DNF −1 −0.5 DNC
19 −1 −1 −1 0 AVG −1 −0.5 DNC
20 −1 −1 −1 0 DNS −1 AVG −0.5 DNC
21 −1 −1 0 AVG 0 DNS 0 DNC −0.5 DNC
22 −1 −1 0 DNC 0 DNC 0 DNC −0.5 DNC
23 −1 −1 0 DNC 0 DNC 0 DNC −0.5 DNC
24 −1 −1 DNF 0 DNC 0 DNC 0 DNC −0.5 DNC
25 −1 −1 DNF 0 DNC 0 DNC 0 DNC −0.5 DNC
26 −1 0 AVG 0 DNC 0 DNC 0 DNC −0.5 DNC
27 −1 0 DNC 0 DNC 0 DNC 0 DNC −0.5 DNC
28 −1 0 DNC 0 DNC 0 DNC 0 DNC −0.5 DNC
29 −1 0 DNC 0 DNC 0 DNC 0 DNC −0.5 DNC
30 0 AVG 0 DNC 0 DNC 0 DNC 0 DNC −0.5 DNC

ThirtyBoatFleetTest pins every cell of this table.

6.4 Net adjustment

net_i = penalty_i − reward_i

Within a pass, Σ net_i = 0 — the pool is given back in full — unless minutes were discarded (§6.3.1). A positive net means the boat was charged: its TCF rises and it starts later next race. A negative net means it starts earlier.

6.5 Casuals: two passes

A casual sailed, so its handicap should move, but one boat turning up for one night must not change the series boats' handicaps. So the algorithm runs twice:

Pass Fleet Whose answer it is
1 series entrants only every series entrant
2 everybody the casuals only

Consequences: when a casual wins, the top penalty is charged twice — to the casual, and to the first series boat home, which won the series race. And the combined answer does not sum to zero across both groups; each pass does on its own.

6.6 Worked example — an ordinary night

Thirty boats entered. Twenty-five come: twenty-four finish and one runs out of time; five stay home. Penalties [5, 4, 3, 2, 1]; the next race's expected duration is 90 minutes.

P             = 5 + 4 + 3 + 2 + 1                  = 15 min

the DNF                                            1 min      (14 left)
24th … 11th home, a minute each                   14 min      ( 0 left)
10th … 6th home                                    nothing
stayed home                                        nothing

1st place:    net = +5
6th place:    net =  0
24th place:   net = −1
the DNF:      net = −1

scale         = next expected × TCF_med = 90 × 1.0 = 90
new_TCF(1st)  = 1.0 / (1 − 5 / 90)                 ≈ 1.0588
new_TCF(24th) = 1.0 / (1 + 1 / 90)                 ≈ 0.9890

6.7 The same club on a filthy night

Same thirty entered, same ladder. Five boats come and all finish.

P             = 15 min
behind the ladder                                  nobody
stayed home   = 25 boats, 15 min / 25              = 0.6 min each (36 s)

1st place:    net = +5
5th place:    net = +1
stayed home:  net = −0.6

Every boat that came pays, and every boat that did not gains 36 seconds: five boats took a good result off a night the rest of the fleet sat out.


7. Converting time adjustments into TCF changes

new_TCFᵢ = TCFᵢ / (1 − netᵢ × TCFᵢ / (expectedDuration × TCF_med))

TCF_med is the median TCF of the pass's finishers, DNFs, duty boats, DNSs, RETs and DNCs. expectedDuration is the next race's t_target_elapsed, because that is the race the new TCF is sailed in. Since a start is set from τ = t_target_elapsed × TCF_med / TCF (§4), this moves a boat's expected time in the next race by exactly net minutes while the median holds. Measured against this race instead, a five-minute penalty earned on a 90-minute night would be three and a third minutes on a 60-minute one.

With limitBySunset, it is the next race's t_target_elapsed after the sunset cap (§4), worked out from that race's date and earliest start and this race's TCFs. The cap is not otherwise applied until the next race's start times are computed, and against the uncapped 90 minutes a five-minute penalty on a night capped to 59 would start only three and a third minutes late. This race's TCFs stand in for the new ones the cap will really see; a night's adjustments move the slowest-to-median ratio too little to matter. If the next race has no daylight at all, its uncapped target is used: computing its start times will refuse anyway, and the race officer must move it before then.

The last race of a series has no next race and uses its own t_target_elapsed, falling back to defaultRaceDuration. The expected duration is used rather than what any race actually took: a night that overran because the breeze died should not shrink every correction. If a penalty is so large that the denominator reaches zero, processing is refused.

If the next race's expected duration is changed after handicaps are saved, the carried TCFs are not recomputed; Unlock results and process again to pick it up.


8. Per-race procedure

  1. Before the race. Set the expected duration and earliest start; compute and publish the start times (§4).
  2. The race. Record came, starts, finishes and flags.
  3. Processing.
    1. Classify every boat (§5).
    2. Charge penalties by rank among the finishers (§6.2).
    3. Give the pool back a minute at a time, leftovers to the boats at home (§6.3); if nobody may receive, charge nothing (§6.3.2).
    4. Convert each boat's net into a new TCF against the next race's expected duration (§7).
    5. If any casuals raced, run 2–4 again over everybody and keep only the casuals' answers (§6.5).
    6. Save the adjustments, which locks the race, and carry the new TCFs into the next race's entry list.

Each race's entry list keeps the TCF every boat sailed it on, so processing race 5 cannot change what race 4 says. Unlock results discards the saved adjustments so the race can be corrected and processed again; the next race keeps the TCFs it was given until then.


9. Practical notes

Size the ladder to the series. About one rung per six boats, counting down:

series 30 24 18 12 8 or fewer
penaltyList [5,4,3,2,1] [4,3,2,1] [3,2,1] [2,1] [1]

This is series size, not turnout: a thirty-boat series keeps [5,4,3,2,1] on a six-boat night, because the boats at home take the leftovers. Sized like this, the ladder's total is never more than the number of boats that could be at home, so a boat at home never gets more than a minute either (barring several frozen boats). A small club where everyone comes needs a short ladder, or nobody is left to receive (§6.3.2) — which is also why one boat sailing alone finishes the night where it started. The series settings form says what fleet a ladder suits.

What the winner pays. The winner gets nothing back, so it starts penaltyList[0] minutes later next race, less any movement in the median.

Rounding. Guns are whole minutes; the TCF keeps the remainder, so a few seconds earned at home accumulate until they move a gun.

Sandbagging. Tail-ending earns at most a minute a race, needs sustained pretence, and has a high social cost in a club fleet.

Convergence. Starting TCFs already reflect known performance, so the system should be near equilibrium from the first race, with each well-sailed boat around mid-fleet — where the minute giveback leaves it alone.


10. Configuration

algorithm:
  penaltyList:         [5, 4, 3, 2, 1]    # whole minutes; ~1 rung per 6 boats
  defaultRaceDuration: 90                 # minutes
  earliestStart:       "18:00"
  limitBySunset:       false
  minutesAfterSunset:  8                  # how long after sunset the fleet may finish

11. Implementation

  • PursuitHandicapEngine — §§4, 6 and 7. Pinned by MinuteGivebackTest, HandicapRulesTest and PursuitHandicapEngineTest.
  • scoring.js — §5: times, flags, places, and what the engine is given. It runs in the browser because the unsaved edits are there, and the race page and the finish sheet share it. Pinned by scoring-test.html.
  • JinxConfig — §3.3, including the retired keys that still load.

Settings that have been retired are still accepted in old configuration files and ignored: the weighted giveback's variant, penaltyScaling, givebackGamma, dnfWeight and dncWeight, and before them dnfAllowance, givebackFleet, dnfInRaceDuration, v0knots and the race-median raceDuration.