Shogi has kifu and chess has PGN: one move per line, readable by anyone, replayable by software, and you can diff two of them. Curling, as far as I could find, has no public notation that does the same job.
World Curling publishes every shot of the Olympics and the World Championships as "Shot by Shot". Each shot has its type and the statistician's rating, and there is even a diagram of the stones. But the format is PDF. That is fine for a person reading it, but a machine has to parse it every time, and the stone positions exist only as images. The research simulator Digital Curling has a log format, but it is not built to take records of real games.
The notation actually came first. Years ago I started on a puzzle game about curling and began working out a format for writing its games down, then left it half-finished. In August 2026 I was working with a Hokkaido Curling Tour livestream on in the background, remembered it, and dug out the unfinished spec. I rewrote the physics while I was at it, and that turned into a curling strategy board, CurlFlux. Once I needed to replay real games in the browser, I had to settle the notation too. The result is PCN (Portable Curling Notation).
This article walks through the spec from top to bottom, with the reasons behind each design decision. It is long, but it is written so that after reading it you know all of PCN. The app itself is introduced in a separate article (in Japanese).
It is still a draft, and that is the reason for this article. I am a programmer who watches a lot of curling, with no playing experience. So there must still be things that happen in real games and cannot be written in this format, and people who have played are far better placed to find them than I am. If you read something and think "you couldn't write this", please tell me.
The spec is public (the Japanese text is normative; there is an English edition):
https://github.com/shinagaki/pcn/blob/main/spec/PCN_SPEC.en.md
Summary for busy readers
-
Plain text, one shot per line. The header uses PGN's
[Key "Value"]; the body repeatsEndlines, shot lines andScorelines. Everything after;is a comment -
The shot codes are taken directly from World Curling's statisticians' material. Shot type (Task) is one letter, rotation (Handle) is
>or<, and the rating (Points) is 0 to 4 - Four layers by depth of information. Line score only → shot type and rating → coordinates of every stone → motion at release. Write only the layers you have
-
Only up to layer ③ (coordinates) carries across implementations. Layer ④ (motion) depends on the physics engine, so it is written together with an
Enginetag, and a different engine ignores it and rebuilds from the coordinates -
Outcomes as coordinates, reasons as comments. Infractions, games cut short, measurements, time-outs: everything that happens in a game can be written with these two. How the game ended (
Termination) and the result (Result) are separate axes - Rule defaults are inferred from the era and the format. Games from before 1993, mixed doubles and wheelchair all fit the same format
- A finishing stage for the data (
Curation), so automated processing does not overwrite records that were adjusted by hand
The big picture
First, a whole file. End 1 of Japan vs Poland at the 2026 World Men's Championship.
[PCN "1.0"]
[Event "LGT World Men's Curling Championship 2026"]
[Site "Ogden, UT, USA"]
[Date "2026.04.02"]
[Stage "Round Robin Session 18"]
[Sheet "A"]
[Red "JPN - Japan"]
[Yellow "POL - Poland"]
[RedPlayers "KOIZUMI S; USUI S; YAMAGUCHI T; YANAGISAWA R"]
[YellowPlayers "LOBAZA B; CIEMINSKI M; DOMIN K; STYCH K"]
[Ends "10"]
[LSFE "Red"]
[Result "5-0"]
[Termination "Concede"]
End 1 hammer=R
1 Y F>4
2 R D>4
3 Y D>2
4 R D<4
5 Y D<2
6 R T<3
7 Y S<2
8 R H>3
9 Y H>4
10 R T<2
11 Y D<3
12 R H<4
13 Y P>1
14 R H>3
15 Y S<2
16 R D<4
Score 3-0
End 2 hammer=Y
1 R F>4
...
Score 2-0
A file consists of a header (tags) and a body (a sequence of ends). Blank lines can go anywhere, and everything from ; to the end of the line is a comment. Case is significant.
The shot line 13 Y P>1 reads like this:
-
13: sequence number within the end -
Y: thrown by Yellow. Usually this can be left out (see below) -
P: Promotion Take-out, a shot that hits one of your own stones or a guard and drives it in -
>: clockwise rotation. For a right-hander that is an in-turn, and the stone curls to the right -
1: the statistician's rating 1 (25%). Close to a miss
This game was 5-0 after two ends, and Poland conceded. [Termination "Concede"] says so.
Five design principles
These are at the top of the spec. Every detailed rule after this follows from them.
1. Four layers by depth of information
| Layer | Content | Sources that exist today |
|---|---|---|
| ① Line score | Points per end, the team with the hammer in end 1 (LSFE), last stone draw | World Curling results database, national association result pages |
| ② Description | Thrower, shot type, rotation, 0–4 rating | World Curling / CURLIT Shot by Shot (PDF, Game Centre) |
| ③ Positions | Coordinates of every stone after each shot | Diagrams in Results Books (images), Live Scores SVG, coaching apps |
| ④ Motion | Position, velocity and spin at release | Simulators (Digital Curling and others), measuring equipment |
① Line score → ② Description → ③ Positions → ④ Motion
Score lines Task / Handle / Points @ lines v= and Engine
③: every implementation can follow the same board
④: exact replay with the same engine
You write only the layers you have, in the same file. The first World Championship in 1959 has nothing but a line score, so it becomes a layer-① file (the scores here are made up):
[PCN "1.0"]
[Event "Scotch Cup 1959"]
[Date "1959.??.??"]
[Red "Canada"]
[Yellow "Scotland"]
[Ends "12"]
[LSFE "Red"]
[Result "7-4"]
End 1 hammer=R
Score 1-0
End 2
Score 0-2
End 3
Score 2-0
...
Even without a single shot line, this is valid PCN. At the other end, a practice session recorded in a simulator has everything up to layer ④.
With layer ④, a deterministic physics engine can replay the game exactly. How to present a record without layer ④ is left to the implementation. It may show the layer-③ positions as the board, or it may generate throws that fit layer ③ or are consistent with layer ② and animate them. When a generated throw is written back into the record, est marks it as an estimate.
2. Contain existing records rather than replace them
When creating a new format, the thing I most wanted to avoid was adding "yet another proprietary format". The design lets public records (line scores, Shot by Shot, position diagrams, coaching-app CSV, simulator logs) each go straight into one of the layers ①–④. Converting a World Curling PDF gives a layer ②③ PCN; throwing stones in CurlFlux gives one with layer ④ too.
3. Line-oriented and appendable
One shot per line. grep, diff and spreadsheets all work. The file reads from top to bottom, so adding a line per throw gives you a live record. An end without a Score line is "in progress".
4. Match World Curling practice
The shot types and ratings use the definitions and one-letter codes from Curling Statistics: How to Score, the manual for World Curling and CURLIT statisticians. The classification statisticians assign at every game becomes the notation as it is. The only thing I changed is how rotation is written (see below).
5. Implicit values only where derivable
Throwing order (who throws first) and the thrower follow from the rules, so they may be omitted. Only exceptions (a skip throwing the lead's stones, a throwing-order violation) are written explicitly. Conversely, nothing that cannot be derived is omitted.
Header: tags
One [Key "Value"] per line. Inside a value, " is written \" and \ is written \\. Order is free, but putting PCN first is recommended. The rule is that unknown tags are preserved and ignored, and the same goes for unknown key=value pairs and comments in the body. That way information added by future extensions or other processors is not lost on a round trip.
Only three are required: PCN (the spec version, "1.0") and Red / Yellow (team names). Everything else is optional. Let's go through them by purpose.
Identifying the game
Event, Site (city, country), Venue (the building), Date (YYYY.MM.DD, unknown parts as ??), Time, Stage (round or session, "Round Robin Session 18" or "Semi-final", either is fine), Sheet.
Time is venue local time. World Curling records are written in local time, so PCN follows them, and the time zone is inferred from Site. Only if you want UTC do you add TZ ("+09:00").
Teams
Teams are identified by stone colour, Red and Yellow, as World Curling does. Even if the actual stones are blue or green, Red/Yellow are used nominally, and the display colour goes in RedColor / YellowColor (#rrggbb). Records of domestic events in Japan often have blue stones, so this separation was necessary.
RedPlayers / YellowPlayers list the players in throwing order, separated by ;: lead; second; third; fourth. By default the third is the vice-skip and the fourth is the skip. For teams where that is not the case, you can add (S) (skip) or (V) (vice-skip) after a name. A team playing with three writes three names; the first two then throw three stones each and the third throws two, as in World Curling rule R3(c)(i). Mixed doubles has two names.
There are also RedShort / YellowShort (an abbreviation such as JPN), RedCoach, RedReserve (alternates) and RedSub (substitutions during the game, free text such as "E6 TANAKA for SUZUKI Y"). RedSub is display metadata. Who threw after a substitution is written with orderR= / orderY= on the end line (the delivery rotation from that end on, see below) or with p= on a shot line. Alternates are referred to by the numbers that follow Players (with four Players, the first alternate is 5).
Format and rules
-
Format:Team(default) /MixedDoubles/Wheelchair -
Ends: scheduled ends. Default10; mixed doubles8. Write it explicitly for 8-end games -
Stones: stones per team per end. Default8, mixed doubles5 -
FGZ: the number of stones protected by the free guard zone rule,0/3/4/5 -
NoTick:true/false -
ThinkingTime: thinking time ("38:00"for 10 ends,"30:00"for 8,"22:00"for mixed doubles)
FGZ and NoTick may be omitted. If they are, the rules of the time are inferred from Date and Format. The table of defaults comes in a later section.
Wheelchair plays like the four-player game (the difference is that there is no sweeping), so the body is written the same way. It is, however, exempt from the no-tick rule, so it does affect the rule defaults.
Ice and sheet
-
SheetWidth: sheet width in metres. The default4.75is World Curling's maximum. Existing facilities may be as narrow as4.42, and narrow club sheets can be 14 ft 2 in,4.28 -
Ice: ice conditions. Three values, as in"hh=13.8 curl=1.41 turns=5"-
hh: hog-to-hog time (seconds) of a draw that stops on the tee -
curl: for a draw to the tee released straight with the centre line as its aim, how far to the side of the aim line it ends up when it stops (m) -
turns: the standard number of turns, i.e. how many times a stone rotates from release until it stops.5if omitted
-
Ice exists for the portability of layer ④. A player calibrates its own friction and curl to match hh and curl. Even if the physics models differ, it gives a way to get close to "the same ice". Engine-specific multipliers (friction= curlx=) may be added alongside.
I defined curl as "the offset from the aim line" to match how players and ice technicians talk when they say "this ice curls four and a half feet". turns is there because some physics models curl differently depending on the number of turns. Two and a half to three and a half turns used to be normal, but top players now throw around five, so the default is 5. curl is written as the value for a draw thrown with that number of turns.
Result and termination
-
LSFE: the team with the hammer (last stone) in end 1.Red/Yellow -
LSD: last stone draw."Red 286.3; Yellow 199.6"(cm; outside the house = 199.6) -
Result: final score"Red-Yellow". In progress or unknown:"*" -
Termination: how the game closed.Normal/Concede/Stopped/Forfeit -
Timeout: time-outs, listed as "team, end, before which of that team's stones" (counted per team, not the shot number within the end), as in"Y E6 before 5; R EE before 3"
Termination was a hard part of the design, so it gets its own section later.
Source and quality
-
Engine: identifier of the physics engine that replays the layer-④ motion, asname/version. Required when motion is written -
Source: source (URL etc.) -
Annotator: who recorded it -
Curation: the finishing stage of the data
Curation came out of an accident in my own implementation. Its value is a single ordered ladder: generated < adjusted < verified < reenacted. Motion generated automatically from positions and left as is: generated. Positions or throws adjusted by hand after that: adjusted. Every throw that matters for the outcome checked against video: verified. Fully reproduced with keyframes: reenacted. If absent, it counts as generated.
The rule is that a processor about to overwrite a file at adjusted or above with an automatic regeneration should refuse (or at least warn). I once did exactly that: a record I had spent hours adjusting while watching the video got overwritten by an automated step. The lesson was that the format itself needs a safety valve to stop machines from erasing human work.
Body: end lines
End <n> [hammer=R|Y] [key=value ...]
...shot lines...
Score <red>-<yellow>
n starts at 1. Extra ends beyond the schedule simply continue the numbering (11, 12 …); showing them as "EE" is up to the player.
hammer= may be omitted. It is then derived from the previous end (the team that did not score gets the hammer next; a blank keeps it) and from LSFE. If an explicit value conflicts with the derived one, the explicit value wins and a warning is shown. This doubles as a way to catch mistakes in records.
The other attributes are powerplay=R|Y (the team that used its mixed doubles power play) and clockR= / clockY= (thinking time left at the end of the end).
Things that change mid-game: ice and delivery rotation
Two things change during a game, and both became end attributes that apply "from that end on".
Change of ice, hh= / curl=. For things like heavy ice early on while the pebble is fresh, or ice getting faster after scraping or as the temperature changes. Scraping happens between ends, and measurements are taken end by end, so an end attribute fits exactly. If only one is written, the other keeps its previous value.
End 6 hammer=R hh=14.3 curl=1.40
Change of delivery rotation, orderR= / orderY=. This is for bringing in an alternate. The player numbers are listed with commas.
End 5 hammer=Y orderY=5,2,3,4
This means "from end 5, the first alternate (5) throws lead". With one number per player, each player throws the number of stones divided by the number of players. Mixed doubles takes two numbers: the first throws stones 1 and 5, the second stones 2 to 4.
It is an end attribute because that is how the rules work. The delivery rotation may change only when an alternate is brought in, that happens at the start of an end, and the replaced player cannot return to the game (WCF R3). A record where someone else threw a single stone is written with p= on the shot line.
Score is the score of that end. A blank is Score 0-0. An end not played to completion, because of a concession for example, is marked with X: Score X (neither team threw) or something like Score 2-X. Leaving out the Score line itself means "not yet decided", which is the state of an end in progress in a live record.
Scenarios (cutting out a position)
A strategy board is often used as "think about the next shot from this position". You cut out just the last three stones of end 10, without the throws that came before. For that, the starting end can take two attributes.
End 10 hammer=Y thrown=7-6 score=4-5
Setup
@ R0.10,0.30* R-0.60,1.90 Y-0.40,1.20 Y0.35,-0.50 Y0.02,3.40
thrown=7-6 says that at the start of the end the first team has thrown 7 stones and the second team 6 (the next shot is the hammer team's 7th). score=4-5 is the cumulative score at the start of the end. It is a different thing from the Score line in the body (the result of the end). A processor that reads these starts counting from this end number, score, hammer and stone count. It is a tool only for cutting out positions; a normal full record does not need it.
Body: shot lines
<number> [R|Y] <shot> [key=value ...] [; comment]
Number and side
The number is the sequence number within the end: 1–16 in the four-player game, 1–10 in mixed doubles. The side R / Y may be omitted; if it is, the team without the hammer throws first, then they alternate. You write it only for records where the throwing order went wrong (a throwing-order violation, for example). When a line without a side follows a line with an explicit side, it is read as thrown by "the team that has thrown fewer stones".
Shot codes
The order is <Task><Handle><Points><annotation>, as in D>4, T<2, P>1!?.
Task (shot type) is the one-letter code from the statisticians' manual.
| Code | Name | Class |
|---|---|---|
D |
Draw | slow |
F |
Front (placed short of the house; includes centre / corner guards) | slow |
G |
Guard (protecting a specific stone) | slow |
R |
Raise / Tap Back | slow |
W |
Wick / Split / Soft-peeling | slow |
Z |
Freeze | slow |
T |
Take-out | fast |
H |
Hit and Roll | fast |
C |
Clearing (peel) | fast |
S |
Double Take-out | fast |
P |
Promotion Take-out (run-back) | fast |
- |
Through (intentional) | |
X |
Not considered (burned stone etc.) | |
? |
Unspecified (no task assigned) |
The last three involve a small trick. In the statisticians' manual, - (Through) is a Task whose rating is automatically "not considered", and X (Not considered) is really a Points value. PCN keeps both as Task codes so the syntax is uniform: - takes neither Handle nor Points, and X takes no Points.
? (Unspecified) is a PCN addition. I added it for throws that have a position diagram but no Shot by Shot classification. PCN requires a Task, and this code avoids forcing an unknown throw into a particular type. The spec says a player may build the throw without a type hint and fill in the type from the resulting board (the position is authoritative). Chess-style annotations also have ?, but that one comes after the Task, so the position tells them apart.
Handle (rotation) is > or <.
| Code | Meaning |
|---|---|
> |
Clockwise (seen from above). In-turn for a right-hander. The stone curls right |
< |
Counter-clockwise. Out-turn for a right-hander. The stone curls left |
The statisticians' manual writes In-turn / Out-turn (I / O), but those words flip meaning with the thrower's handedness. A left-hander's in-turn is the same rotation as a right-hander's out-turn. PCN ignores handedness and records only the direction the stone rotates (as World Curling does). I chose > and < because you can see at a glance which way the stone curls. If unknown, leave it out.

Two stones aimed at the same button. The direction of the symbol matches the direction the stone curls (the paths were actually solved with the physics engine)
Points (rating) is 0 to 4, corresponding to World Curling's 0 / 25 / 50 / 75 / 100%. It is assigned following the statisticians' guidelines. For a take-out, for example: 4 = opponent removed and shooter stays, 3 = stays but rolls, 2 = both out, 0 = the opponent stays. Some older scoring used 5 or 6 as the maximum; PCN does not represent those and rounds them to the 4-point scale on import (4 and above become 4).
World Curling statistics record hog-line and FGZ violations as "Player's Fault", separately from the Task. In PCN you write the intended Task as it is and give the reason in an x= field (see below).
Annotations are ! (good), !! (brilliant), ? (dubious), ?? (bad), !? and ?!, placed after Points. Points is the statistician's objective rating; the annotation is the writer's subjective opinion. I borrowed this from chess notation.
Additional fields
| Field | Form | Meaning |
|---|---|---|
p= |
1– |
Thrower: which entry of the Players tag (alternates continue the numbering). If omitted, the end's delivery rotation (orderR= / orderY=; otherwise the regular order, two stones each) |
t= |
seconds | Hog-to-hog time (hog line to hog line), e.g. t=12.7
|
i= |
seconds | Split time (delivery-end back line to hog line, 8.230 m), e.g. i=3.7
|
clock= |
MM:SS |
Thinking time left after the shot |
v= |
x,y,vx,vy,w |
Layer-④ motion |
est |
flag |
v= is not a measurement but an estimate generated from layers ② / ③ |
p= is for a single stone thrown outside the regular order, such as a skip throwing the lead's stone. If the order changed because of a substitution, orderR= / orderY= on the end line is shorter to write. t= is exactly the hog-to-hog time shown on TV broadcasts, and it is the primary information about how fast the ice is.
Body: board lines
The @ line
Right after a shot line, a line starting with @ gives the coordinates of every stone after that shot.
3 Y T<4
@ Y-0.02,3.61 Y0.31,-0.55
- Items are
<R|Y><x>,<y>, separated by spaces, in any order - Only stones in play are written. Removed stones are not
- If there are no stones, the line is just
@ - A stone with a trailing
*is the final position of the stone thrown in that shot, when it stayed in play. At most one per position. Do not mark a position where the thrown stone went out of play
* corresponds to the stone drawn with a thick black rim in World Curling's diagrams. With it, there is no ambiguity about which stone was thrown when a throw is rebuilt from the positions. Where a hit swaps stones around, without this mark there can be two solutions.

How the @ line maps to the board. Only the one stone thrown in this shot gets the ``*
There is one more important rule. @ means "the board after that shot, as settled before the next shot is thrown". Stones repositioned by an umpire, and corrections made from video or photos, belong in this board. Setup is never written in the middle of an end.
The Setup line
To start from an arbitrary position, write Setup and a board line right after End. Mixed doubles pre-positioned stones, practice problems and tactical study all use it.
End 1 hammer=Y
Setup
@ R0.00,3.50 Y0.00,0.30
1 R D<4
In mixed doubles two stones are placed at the start of each end (rule R17(f)). The stone of the team with the hammer goes in the house, the other one is a centre guard. In this example yellow has the hammer, so the yellow stone is in the house and the red stone is the guard.
Coordinates and motion
Coordinate system
- Unit: metres. Two decimals (1 cm) is standard, up to four if needed
- Origin: the tee (centre of the button) of the house in play
-
+xis to the thrower's right,+yis toward the thrower (the hack). Guards havey > 0, the back of the house hasy < 0 - Reference dimensions (World Curling): 12-foot circle radius 1.829, 8-foot 1.219, 4-foot 0.610, button 0.152, hog line
y = 6.401, back liney = −1.829, sheet width 4.75 (|x| ≤ 2.375), stone radius 0.1455

The coordinate system. The coordinates alone tell you where a stone is relative to the rings
I put the origin at the tee because then you can see how a stone relates to the rings just by looking at its coordinates. Y0.31,-0.55 reads as "31 cm right of the tee, 55 cm behind it, inside the 4-foot". Digital Curling, which is used in research, has its origin at the hack with y pointing toward the house, so the conversion is x_pcn = x_dc, y_pcn = 38.405 − y_dc (38.405 m from the hack to the tee).
Motion v=x,y,vx,vy,w
| Element | Unit | Meaning |
|---|---|---|
x,y |
m | Stone centre at the start of the record (release) |
vx,vy |
m/s | Velocity vector |
w |
rad/s | Angular velocity, positive = counter-clockwise seen from above (a > stone is negative) |
4 R D<3 t=14.2 v=0.3541,30.8,0.1012,-2.1746,1.2495 est
@ R0.45,0.98* R0.08,0.26 Y0.08,2.69 Y0.05,0.58
y=30.8 is the release point. vy is negative because the stone travels toward the tee (−y). w is positive, which agrees with the Handle < (counter-clockwise).
The portability trade-off: layer ③ is the common language
This is the part of the design I thought about most. With layer-④ motion, the same physics engine reproduces the stones' movement exactly. But with a different physics model, the same release stops somewhere else. The friction model and the curl formula differ from one implementation to the next.
So the spec says that v= is always written together with an Engine tag, and a player with a different engine ignores v= and rebuilds the throw from the layer-③ coordinates.
v= present ──┬─ Engine matches ──→ replay the physics as is (exact)
└─ Engine differs ──→ do not use v= ──┐
├──→ fit the throw from @
@ only ─────────────────────────────────────────────┘
② only ──→ generate a throw consistent with Task, Handle, Points and the board
There are seven guidelines for implementers of the playback side:
-
v=present andEnginematches yours → replay the physics as is -
v=present butEnginediffers → do not usev=. If there is anIcetag, calibrate your engine to it first, then solve from@as in 3 -
@present → fit a throw from the difference between consecutive boards: the stopping point for draws, the contact outcome for hits. The resultingv=may be saved withest - Layer ② only → generate a throw consistent with Task, Handle, Points and the board. The lower the Points, the farther from the target.
-passes without touching anything;Xleaves the board unchanged - Show generated or fitted throws as estimates in the UI (with
≈, for example) - Do not apply rule-violation judgement when replaying a recorded game. The ruling of the day is already reflected in the record, and re-judging with estimated physics causes false positives
- When reading a live record, treat a final end without
Scoreas in progress, and read it on the assumption that lines may be appended
In other words, layer ③ is "the common language in which every implementation follows the same board", and layer ④ is "exact reproduction and bit-level verification within one engine". Almost all of the CurlFlux library is layer ④ with est, which means: open it in CurlFlux and you see motion close to the video; open it in another implementation and you get the same boards from layer ③.
Writing irregularities
Games have infractions, games cut short, and umpires repositioning stones. If you start handling these case by case with dedicated codes, the spec grows without limit. PCN has just two principles.
Outcomes are expressed by the @ board (which can represent any position). Reasons are expressed by a ; comment or an x= field.
Violations
Add x=<code> and write the board after the ruling in @.
| Code | Meaning | Ruling (reflected in @) |
|---|---|---|
x=fgz |
Protected-stone (free guard zone) violation | Moved stones returned, shooter removed |
x=notick |
No-tick violation | The non-offending team's choice (restore / leave) in @
|
x=hog |
Hog-line violation (late release), or a stone that did not reach the hog line | Shooter removed |
x=burn |
Burned stone (touched while moving) | The opponent's choice (leave / restore / remove) in @. Details in a comment |
x=reposition |
Stones repositioned by an umpire | The repositioned board in @
|
x=wrong |
Wrong stone or wrong order | Result of the ruling in @. Details in a comment |
5 R T<0 x=fgz ; opponent's centre guard removed → restored, shooter removed
@ Y0.05,3.42 R-0.21,0.88
The Task is the intended one (a take-out) as it is, the rating is 0, the reason is x=fgz, and the outcome is @. When the FGZ rule is in effect the player can detect and restore this automatically, so the restoration happens even without @, but if you are keeping a record, writing it is the safe choice.
How the game ended
The Termination tag has only four values, describing how the game closed.
| Value | Meaning | Winner |
|---|---|---|
Normal |
All scheduled ends played (default) | Total score |
Concede |
One team conceded | The team that did not concede |
Stopped |
Cut short before the scheduled ends | Total score at the stop. A tie is a draw |
Forfeit |
Decided by something other than play (no-show, disqualification, out of thinking time) | The Result tag |
The reason is not part of the value; it goes in a comment on the final end's line. There is no end to the possible reasons: an event's time limit for games, a facility failure, the weather, an opponent not turning up. Assigning a code to each would never finish. A reader decides how to handle the game from Termination and simply shows the reason as a comment.
[Ends "8"]
[Result "5-4"]
[Termination "Stopped"]
…
End 7 ; game reached the event time limit (2.5 hours); last end
…
Score 1-0
Some finer decisions are in here too. Losing on time is Forfeit, not Stopped (because it is an ending that decides a loser). A game that ends tied under Stopped is a draw, and Termination has no value for draws, because how the game ended and the result are separate axes. Ends that were not played are Score X, and Result carries the deciding score.
Everything else
-
Measurements: the outcome shows in
@andScore, so nothing extra is needed. Add a comment if you like -
Time-outs: write
; time-outon the shot line where it was taken, or list them for the whole game in theTimeouttag -
Power play:
End … powerplay=R|Y -
Substitutions and ice changes:
End … orderR=/orderY=andEnd … hh=/curl=(from that end on) -
Blank end:
Score 0-0(hammer kept) -
Extra ends: just continue
End nbeyond the scheduled ends -
In progress: leave out the
Scoreline
Inferring the rules from era and format
When there are no FGZ / NoTick tags, the rules of the time are inferred from Date and Format. The reference is when World Curling (international play) adopted each rule.
Four-player and wheelchair
| Period | FGZ |
NoTick |
Basis |
|---|---|---|---|
| to June 1993 | 0 | false | Before FGZ (prototype: the 1990-91 Moncton Rule, trialled at the 1992 Olympics) |
| from July 1993 | 4 | false | Four-rock FGZ in international play from the 1993-94 season |
| from July 2018 | 5 | false | Decided at the September 2017 Congress, five-rock from 2018-19 |
| from July 2022 | 5 | true | No-tick from the 2022-23 season (not wheelchair) |
Canadian national championships alone used three rocks from 1993-94 to 2001-02. That differs from the international basis, so it is not a default; you write FGZ "3" explicitly.
Mixed doubles uses FGZ 3 and NoTick false regardless of era.
What is protected depends on the format
The same FGZ tag protects different things in the four-player game and in mixed doubles. I only realised the two needed separate descriptions when I re-read the rules while writing the spec.
- Four-player and wheelchair (R6): before the 6th stone is delivered, putting an opponent's stone that is in the FGZ (between the tee line and the hog line at the playing end, excluding the house) out of play is a violation. Your own stones may be removed, and stones in the house are not covered
- Mixed doubles (R17(e)): before the 4th stone is delivered, putting any stone already delivered or pre-positioned, own or opponent's, out of play is a violation. The location is not limited to the FGZ
- No-tick (R7): before the 6th stone is delivered, moving an opponent's stone that is in the FGZ and touching the centre line off the line, or out of the FGZ, is a violation. The ruling is the non-offending team's choice. It does not apply to mixed doubles or wheelchair
PCN records the throw as it happened, and the player does the judging. When replaying a recorded game, however, the recommendation is not to apply the judgement and to use the @ boards as they are. The ruling of the day is already in the record, so re-judging with estimated physics causes false positives.
Engine extension line: Anim
Some stone movements cannot be reproduced by physics. An unnatural path transcribed from video, for example a scene where two stones met at an unexpected angle and rolled away. For movements like these, the spec reserves a line for hand-authored animation.
9 R S>3
Anim R0 dur=2.4 0:0.10,30.8 0.5:0.30,4.0 0.8:0.55,0.8 1:0.45,-0.1
@ Y-0.02,3.5 R0.45,-0.1
-
<stone>is<R|Y><number>.0is the stone thrown in this shot;1and up count existing stones of that colour in the order of the previous board line - Time is normalised from 0 to 1 (0 = start of the throw, 1 = stop). The real duration is
dur=(seconds) - Interpolation between keyframes is fixed as centripetal Catmull-Rom (α = 0.5), so that every implementation draws the same curve
- Stones without an
Animline stay still (no physics). Physics and animation are not mixed within one throw - A stone that is not in the post-shot
@is removed att=1
A shot with Anim is replayed from it in preference to v=, and @ is its final board. Implementations that do not support it ignore the line and can still replay from @ or v=. This is the first instance of the rule that future extensions are identified by their leading word, and lines with an unknown leading word are preserved and ignored.
For programs: PCN-JSON and EBNF
The spec defines a JSON representation that maps one-to-one to the text.
{
"pcn": "1.0",
"tags": { "Event": "...", "Red": "...", "Yellow": "..." },
"ends": [
{
"no": 1, "hammer": "R",
"setup": null,
"shots": [
{ "no": 1, "side": "Y", "task": "F", "handle": ">", "points": 4,
"player": 1, "hogToHog": null, "board": [["Y", 0.05, 3.42]],
"v": null, "est": false, "nag": "", "comment": "" }
],
"score": { "red": 3, "yellow": 0 }
}
]
}
handle is ">" / "<" / null, points is an integer or null, and X in score is null.
The grammar fits on one screen in EBNF.
file = { line } ;
line = tag | end | score | setup | shot | board | comment | empty ;
tag = "[" key WS string "]" ;
end = "End" WS int { WS kv } ;
score = "Score" WS ( "X" | num "-" num ) ;
setup = "Setup" ;
shot = int [ WS side ] WS shotspec { WS ( kv | "est" ) } ;
board = "@" { WS stone } ;
stone = side coord "," coord [ "*" ] ;
side = "R" | "Y" ;
shotspec = task [ handle ] [ points ] { nag } | "-" | "X" ;
task = "D"|"F"|"G"|"R"|"W"|"Z"|"T"|"H"|"C"|"S"|"P"|"?" ;
handle = ">" | "<" ;
points = "0"|"1"|"2"|"3"|"4" ;
nag = "!" | "?" ;
kv = key "=" value ;
comment = ";" { any } ;
The derivation rules are written down too. The throwing side comes from hammer; the hammer comes from LSFE and each Score; each player throws Stones divided by the number of players; the percentage is Points × 25; a team's percentage is Σpoints ÷ (4 × rated shots) (- and X are not in the denominator). The final score is the sum of the Score lines, and if it disagrees with the Result tag, Result wins with a warning.
What I learned from implementing it
Writing a spec and actually pushing hundreds of games through it turned out to be different jobs. Here are the things I tripped over, in order.
Some records' scores do not match their boards. In the first public dataset I imported, the end score disagreed with the score counted from the final positions in 13% of ends. Concessions were not recorded either. In the end I removed that data from the library, and made the validation command check that "the score counted from the positions matches the Score line". I now accept records from only two sources: World Curling / CURLIT records, and records transcribed from video.
@ always wins. When a record has @, the player snaps to the @ positions even if the physics lands slightly off. I used to keep the physics result when it was within a tolerance, but then every read-and-write round trip moved stones by 1 to 8 cm. It stopped once I decided to "always keep the recorded positions". The spec's definition, "@ is the board as settled before the next shot", was made explicit because of this.
The trap of trusting v= because Engine matches. A player replays v= as is when the [Engine] tag matches its own. If you fix a physics constant and forget about this, old releases get replayed under the new physics, and the positions drift without any warning. In my measurements the error doubled. At first I kept the Engine version fixed, because it is an identifier carried in distributed files, and relied on procedure: whenever the physics changed, I rebuilt every record from layer ③. Since the end of September, CurlFlux bumps the version (curlflux/1 → curlflux/2) whenever a physics change alters where the same v= stops. Older files then fall back to their positions instead of replaying wrongly, and the library is rebuilt from layer ③ in the same pass.
The diagrams are images. The position diagrams in Results Books are images embedded in the PDF, and the stone coordinates are extracted by image processing. It takes about 4 seconds per game (about 90 before I vectorised the code). Recent Live Scores, on the other hand, serve the boards as SVG, which gives the numbers directly. Converting the same game from both and comparing them, the positions agreed to within 2–3 cm. That comparison is also how I found the step needed to remove the "ghost stones" in the PDFs (stones drawn transparently that are already out of play).
Some throws have no type. I explained above why I added ?. It is a compromise between a design that requires a Task and not wanting to guess at what is unknown.
Machines erase human work. The Curation ladder and the rule against overwriting were added after the accident where an adjusted record was overwritten by automated processing. If the format carries a mark saying "a person has worked on this file", tools can see it and stop.
Published tools
The spec, grammar, JSON schema and examples are in the public repository (github.com/shinagaki/pcn). The validation and conversion tools belong to the reference implementation, CurlFlux, so here I only list how they are used.
node tools/pcn.mjs validate <file.pcn> # grammar, derivation rules, score check
node tools/pcn.mjs replay <file.pcn> # replay with physics, error against layer-③ positions
node tools/pcn.mjs export <in> <out.pcn> # write out normalised
node tools/pcn.mjs json <in> <out.json> # to PCN-JSON
python tools/wcf-pdf-to-pcn.py <pdf> <out.pcn> # World Curling Shot by Shot PDF → PCN (layers ② ③)
python tools/curlit-sse-to-pcn.py … # Live Scores JSON/SVG → PCN
python tools/curlr-to-pcn.py … # CurlR (a CC0 dataset) → PCN, for verification
In the browser, CurlFlux has "Open PCN", which replays your own files as they are. Positions (scenarios) built on the strategy board can be saved as PCN.
Try it
The bronze medal game of the 2018 PyeongChang Olympics, before the last stone of the final end. This record was converted from the Shot by Shot PDF and its positions adjusted against the video, so its Curation is verified.
https://curlflux.creco.net/?replay=og2018-women-bronze-gbr-jpn&lang=en#s=168
The PCN of this game is in the public repository as examples/og2018-women-bronze-gbr-jpn.pcn. You can see [Ice "hh=13.8 curl=1.66 …"] and [Curation "verified"] in the header, and on each shot line t= (hog-to-hog time from the broadcast), v= (with est) and @ (with the * mark).
Status and what comes next
The spec is a v1.0 draft (6th revision, September 2026). The [PCN "1.0"] value in files names the format family it follows and is not revised while the document is a draft; 1.0 is frozen once the content is settled. It is an unofficial document with no affiliation to World Curling or CURLIT, and where interpretations of the rules differ, the originals prevail (The Rules of Curling, July 2024, and Curling Statistics: How to Score, 2025).
Having built it, I think the value of a notation is decided less by the spec than by "how many records flow into it". Right now there are about 660 games from World Curling's public records and from video (15 of them with every shot checked against video). I have also stored more than 900 games of this season's Live Scores, ready to convert to PCN (publishing them requires the rights holders' permission, so for now they are used only for verification and statistics). It becomes a real "common language" only once coaching apps and simulators can write PCN.
What would help most right now is for people to break it. Take a game you know well (one with an unusual ending, one with a measurement, one with an infraction, one you scored yourself), write it in PCN, and if something will not go in, that is a hole in the spec. I want to know before 1.0 is frozen, so please tell me in a GitHub Issue or on X (@creco). Pointing out where I have misread the rules helps just as much.
The spec lives in a repository separate from the app. A notation that belongs to one particular program cannot work as a notation, so the canonical text is on GitHub.
- Repository (spec, grammar, JSON schema, examples): https://github.com/shinagaki/pcn
- Spec (English): https://github.com/shinagaki/pcn/blob/main/spec/PCN_SPEC.en.md
- Spec (Japanese, normative): https://github.com/shinagaki/pcn/blob/main/spec/PCN_SPEC.md
- CurlFlux: https://curlflux.creco.net/?lang=en
Top comments (0)