PageSourceSearch

https://curling.io/assets/js/4ea2042b.4a07bcbe.js

js curling.io collected 2026-10-02 14:51:53 UTC 56,413 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[7055],{2349:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>s,toc:()=>d});var s=t(97465),i=t(74848),a=t(28453);const r={slug:"optimizing-curling-draw-schedules",title:"Optimizing Curling Draw Schedules",authors:["dave"],tags:["v3","competition-management","architecture","rust","javascript","sneak-peek"]},o=void 0,l={authorsImageUrls:[void 0]},d=[{value:"The Draw Scheduling Tool",id:"the-draw-scheduling-tool",level:2},{value:"See the Draw Scheduling Workflow",id:"see-the-draw-scheduling-workflow",level:2},{value:"For Drawmasters: How We Built the Schedule Catalog",id:"for-drawmasters-how-we-built-the-schedule-catalog",level:2},{value:"Scheduling Objective",id:"scheduling-objective",level:3},{value:"How Good Is AI for Draw Schedule Generation?",id:"how-good-is-ai-for-draw-schedule-generation",level:3},{value:"Search Envelope",id:"search-envelope",level:3},{value:"Research Harness",id:"research-harness",level:3},{value:"Evaluated Algorithms",id:"evaluated-algorithms",level:3},{value:"Local Search",id:"local-search",level:4},{value:"Simulated Annealing",id:"simulated-annealing",level:4},{value:"Reconstruction and Portfolios",id:"reconstruction-and-portfolios",level:4},{value:"Exact Methods and Constructions",id:"exact-methods-and-constructions",level:3},{value:"Coverage and Proof Status",id:"coverage-and-proof-status",level:3},{value:"Offline Rust and Browser JavaScript",id:"offline-rust-and-browser-javascript",level:3},{value:"Catalog Distribution and Runtime Selection",id:"catalog-distribution-and-runtime-selection",level:3},{value:"Findings",id:"findings",level:3},{value:"Glossary",id:"glossary",level:2}];function c(e){const n={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",h4:"h4",img:"img",li:"li",ol:"ol",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.R)(),...e.components},{Details:s}=n;return s||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.p,{children:(0,i.jsxs)(n.em,{children:["This post is part of our Curling IO v3\n",(0,i.jsx)(n.a,{href:"/blog/tags/sneak-peek",children:"sneak peek series"}),", where we explore some of the new\nfeatures available in the upcoming version."]})}),"\n",(0,i.jsxs)(n.p,{children:["Curling IO v3 includes a new draw scheduling screen for event games. The\nschedule is a grid of draws and club resources, with unassigned games kept in a\nqueue beside it. You can drag and drop games, lock specific placements, and use\n",(0,i.jsx)(n.strong,{children:"Allocate"})," and ",(0,i.jsx)(n.strong,{children:"Optimize"})," around those locks."]}),"\n",(0,i.jsx)(n.p,{children:"Unlike a separate schedule template generator, this editor works with the\nevent's actual teams, stages, games, resources, and draw times. Saving the\nschedule updates the event directly."}),"\n",(0,i.jsxs)(n.p,{children:["You can try most of the scheduling interface now at\n",(0,i.jsx)(n.a,{href:"https://curlingschedules.com",children:"CurlingSchedules.com"}),". It uses generic teams and\nbrowser-local saves instead of an event's actual games, but the grid,\ndrag-and-drop editing, locks, catalog schedules, fairness inspection, and\noptimization are available today."]}),"\n","\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"A league draw schedule in the Curling IO v3 event editor",src:t(66899).A+"",width:"1280",height:"720"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:"The Curling IO v3 schedule editor keeps the game queue, event controls, draw\ntimes, resources, and scheduled games in one view."})}),"\n",(0,i.jsx)(n.h2,{id:"the-draw-scheduling-tool",children:"The Draw Scheduling Tool"}),"\n",(0,i.jsx)(n.p,{children:"Version 2 generates draw schedule templates and uses dropdowns to place games.\nIt does not have this editor, the full canonical catalog, game locks, or local\noptimization around manual changes."}),"\n",(0,i.jsx)(n.p,{children:"The v3 editor supports:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"drag and drop games between the schedule and game queue"}),"\n",(0,i.jsx)(n.li,{children:"edit draw times, add draws as games are placed, or delete a draw and return\nits games to the queue"}),"\n",(0,i.jsxs)(n.li,{children:["lock a game so ",(0,i.jsx)(n.strong,{children:"Allocate"})," and ",(0,i.jsx)(n.strong,{children:"Optimize"})," leave it where you put it"]}),"\n",(0,i.jsx)(n.li,{children:"allocate round-robin, ad hoc, and bracket games under their different\nordering rules, including queued games when requested"}),"\n",(0,i.jsx)(n.li,{children:"fill unused sheets with games from the next logical round when compact draws\nmatter more than keeping every round in a fresh row"}),"\n",(0,i.jsx)(n.li,{children:"inspect exactly which games produce the Max, Total, and Back-to-back fairness\nnumbers"}),"\n",(0,i.jsx)(n.li,{children:"add an ad hoc game to a specific round robin, so it remains part of that\nstage's standings and scoring"}),"\n",(0,i.jsx)(n.li,{children:"import CSV assignments for existing games without recreating the event's\ncompetition structure"}),"\n",(0,i.jsx)(n.li,{children:"save the current arrangement as a club draw schedule template, or deliberately\napply a matching template"}),"\n",(0,i.jsx)(n.li,{children:"start from a validated canonical schedule, then optimize event-specific\nchanges without moving locked games"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"These are curling-specific scheduling rules. The allocator distinguishes\nround-robin, ad hoc, and bracket games; understands logical rounds that may span\nseveral draws; accounts for selected resources and fixed placements; and scores\nhow often teams return to the same sheet, including consecutive draws. It puts\nad hoc games after the affected teams' round-robin games, and unlocked bracket\ngames after round-robin and ad hoc play. A locked game is exempt from those\nplacement rules."}),"\n",(0,i.jsxs)(n.p,{children:["When an event contains one complete round robin and no conflicting locks,\n",(0,i.jsx)(n.strong,{children:"Allocate"})," checks our schedule catalog before doing any browser-side search.\nMultiple iterations repeat the matching catalog layout as a starting point. A\nmatching club draw schedule template is presented separately and is applied\nonly when the drawmaster chooses it. Once locks, ad hoc games, multiple stages,\nor manual placements change the problem, the browser optimizer works from the\nactual event schedule."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"A locked game and an ad hoc game in the Curling IO v3 event editor",src:t(17073).A+"",width:"1280",height:"720"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:"The blue lock marks a placement that automatic allocation must preserve. The\nqueue contains an ad hoc second meeting between A and B, ready to be placed."})}),"\n",(0,i.jsx)(n.h2,{id:"see-the-draw-scheduling-workflow",children:"See the Draw Scheduling Workflow"}),"\n",(0,i.jsx)(n.p,{children:"This tutorial shows how to configure, allocate, edit, inspect, optimize, save,\nand reuse an event draw schedule."}),"\n",(0,i.jsx)("div",{className:"text--center videoWrapper",children:(0,i.jsx)("iframe",{width:"100%",src:"https://www.youtube.com/embed/wigR-bzj004?si=8tE3gk-KWvb7ZpKT",title:"Build and Manage a Curling Draw Schedule in Curling IO",frameBorder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;
1 web-share",referrerPolicy:"strict-origin-when-cross-origin",allowFullScreen:!0})}),"\n",(0,i.jsx)(n.h2,{id:"for-drawmasters-how-we-built-the-schedule-catalog",children:"For Drawmasters: How We Built the Schedule Catalog"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:"The rest of this article gets technical. It covers how we scored schedules,\ntested algorithms, used exact solvers, and distinguished best-found schedules\nfrom proven optima. You've been warned!"})}),"\n",(0,i.jsx)(n.p,{children:"The v3 scheduling system has two computational parts. An offline Rust\napplication searches for, validates, and in some cases proves optimal base\nschedules. A JavaScript optimizer handles event-specific changes in the\nbrowser, including locked games, ad hoc games, and competition stages sharing\nthe same resources."}),"\n",(0,i.jsx)(n.p,{children:"The offline work produced a versioned catalog covering all 736 combinations\nfrom 2 through 24 teams and 1 through 16 sheets, under both supported\ndraw-packing modes. Curling IO v3 loads this catalog directly when allocating\nan event schedule."}),"\n",(0,i.jsx)(n.p,{children:"The catalog currently contains 449 layouts. Of those, 419 are proven optimal\nand 30 are the best result found so far. That includes 448 distinct\nrequirements in the practical catalog envelope and one historical outlier\nretained for compatibility."}),"\n",(0,i.jsx)(n.h3,{id:"scheduling-objective",children:"Scheduling Objective"}),"\n",(0,i.jsx)(n.p,{children:"We score sheet fairness as an ordered tuple:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Maximum repeat visits:"})," the largest number of extra visits one team makes\nto one sheet."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Total repeat visits:"})," every visit by every team after its first visit to a\nsheet."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Back-to-back repeat visits:"})," cases where a team plays on the same sheet in\nadjacent draws."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Lower is better. We compare the first number first, then the second, then the\nthird. Runtime never compensates for a worse schedule."}),"\n",(0,i.jsxs)(n.p,{children:["An eight-team, four-sheet result of ",(0,i.jsx)(n.code,{children:"(1, 24, 0)"})," means no team visits one sheet\nmore than twice, there are 24 repeat visits in total, and nobody returns to the\nsame sheet in consecutive draws."]}),"\n",(0,i.jsxs)(n.p,{children:["Those 24 repeats are unavoidable. Eight teams make 56 team-sheet visits across\nseven draws, but there are only 32 unique team-and-sheet combinations. Even a\nperfectly balanced schedule has at least ",(0,i.jsx)(n.code,{children:"56 - 32 = 24"})," repeat visits. Reaching\nthat lower bound with a maximum of one extra visit and no consecutive repeats\nproves that there is nothing left to improve under our scoring rules."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"The fairness inspector showing where a team repeats on one sheet",src:t(43499).A+"",width:"1280",height:"720"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:"The stats are inspectable in the schedule editor. Selecting one highlights the\ngames and teams that produced it."})}),"\n",(0,i.jsx)(n.p,{children:"We chose an ordered tuple instead of a weighted score because weights hide\ntradeoffs. Is reducing one team's sixth visit to Sheet A worth creating three\nnew consecutive repeats elsewhere? A scalar score can answer that only after\nsomeone invents a conversion rate. The tuple makes the priority explicit."}),"\n",(0,i.jsx)(n.h3,{id:"how-good-is-ai-for-draw-schedule-generation",children:"How Good Is AI for Draw Schedule Generation?"}),"\n",(0,i.jsx)(n.p,{children:"We tested whether current general-purpose AI models could produce these\nschedules from a normal drawmaster request. This was a direct, one-shot model\ntest, not an agent with access to our validator or a repair loop."}),"\n",(0,i.jsx)(n.p,{children:"The prompt took some work. Early versions that sounded completely natural\noften returned missing or duplicate matchups, especially with an odd number of\nteams. We refined it to approximate the clarifications a drawmaster might give\nin a short conversation. The final version stated the expected number of\ngames, asked for one Markdown table, and asked the model to check its work. It\ndid not provide the matchup inventory, minimum draw count, fairness tuple,\ncached schedule, circle method, or any other scheduling algorithm."}),"\n",(0,i.jsxs)(s,{children:[(0,i.jsx)("summary",{children:"Read the exact 8-team, 4-sheet prompt"}),(0,i.jsx)(n.p,{children:"Create a curling draw schedule for a single round robin with 8 teams and 4\nsheets. Label the teams 1 through 8 and the sheets A through D."}),(0,i.jsx)(n.p,{children:"The schedule must contain exactly 28 games, one for each unique team pairing. A\nteam cannot play more than once in the same draw."}),(0,i.jsx)(n.p,{children:"Try to spread each team's games across the sheets, especially avoiding the same\nsheet in consecutive draws. Correct pairings matter more than perfect sheet\nbalance."}),(0,i.jsxs)(n.p,{children:["Before answering, count the games and check for missing or duplicate matchups.\nReturn one completed Markdown table, not a separate table for each draw. Use one\nrow per draw and columns named Draw, Sheet A through Sheet D. Write each game as\n",(0,i.jsx)(n.code,{children:"1 vs 2"}),", leave unused sheet cells blank, and include a Bye column when needed."]})]}),"\n",(0,i.jsx)(n.p,{children:"Only the team count, sheet count, labels, and expected game count changed for\nthe other fixtures."}),"\n",(0,i.jsx)(n.p,{children:"We sent that frozen prompt to OpenAI and Anthropic through their first-party\nAPIs. We added Gemini in a separate pass through OpenRouter, pinned to Google\nAI Studio with provider fallback disabled. Each model received the same five\nfixtures once: 8 teams on 4 sheets, 8 on 2, 9 on 4, 11 on 5, and 14 on 7.\nEvery request started with a fresh one-message context, used the provider's\ndefault reasoning behaviour, and had a hard five-minute timeout. We kept every\nresponse, including timeouts."}),"\n",(0,i.jsx)(n.p,{children:"The familiar 8-team, 4-sheet layout has been published for years and may appear\nin model training data. We kept it as a calibration case, not proof that a\nmodel derived the answer. We also kept informal consumer-c
1hat experiments out\nof this table because those products may add hidden instructions, model\nrouting, and reasoning settings."}),"\n",(0,i.jsx)(n.p,{children:"The results were parsed and checked independently. Harmless formatting could\nbe normalized, but the evaluator could not add or remove a game, change a\npairing, or improve a sheet assignment. A schedule had to contain every\nmatchup exactly once, keep each team to one game per draw, and pass the same\nfairness calculation used by Drawmaster. Invalid schedules did not receive a\nfairness score."}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"API and model"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Valid results"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Median response time"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Output tokens"}),(0,i.jsx)(n.th,{style:{textAlign:"right"},children:"Calculated cost"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["OpenAI ",(0,i.jsx)(n.code,{children:"gpt-5.6-sol"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"5/5"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"50.7 seconds"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"21,079"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"$0.64"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Anthropic ",(0,i.jsx)(n.code,{children:"claude-opus-5"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"5/5"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"108.3 seconds"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"59,070"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"$1.48"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsxs)(n.td,{children:["Gemini ",(0,i.jsx)(n.code,{children:"google/gemini-3-flash-preview"})]}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"2/5"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"3.7 seconds"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"3,338"}),(0,i.jsx)(n.td,{style:{textAlign:"right"},children:"$0.01"})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"Reasoning tokens are included in the output totals, not added a second time.\nOne pass over five fixtures is useful baseline evidence, but it is not a\nstatistically strong ranking of the models."}),"\n",(0,i.jsx)(n.p,{children:"OpenAI and Claude returned a valid, minimum-draw schedule for every fixture.\nOpenAI matched or beat Claude on all five. Sheet fairness was a different\nresult:"}),"\n",(0,i.jsx)(n.p,{children:"Gemini finished every request in 5.1 seconds or less, but three of its five\nschedules were invalid. The 8\xd74 and 14\xd77 answers repeated matchups despite\nclaiming in their own prose that every pairing had been verified. The 9\xd74\nanswer repeated two matchups and scheduled one team twice in its final draw.\nIts two valid schedules used the minimum number of draws, but neither matched\nthe catalog's fairness score."}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Fixture"}),(0,i.jsx)(n.th,{children:"Current catalog"}),(0,i.jsx)(n.th,{children:"Best AI result"}),(0,i.jsx)(n.th,{children:"Comparison"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"8 teams, 4 sheets"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(1, 24, 0)"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(2, 24, 0)"})}),(0,i.jsx)(n.td,{children:"Curling IO is better"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"8 teams, 2 sheets"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(3, 40, 0)"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(3, 40, 0)"})}),(0,i.jsx)(n.td,{children:"Equal, proven optimal"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"9 teams, 4 sheets"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(1, 36, 0)"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(2, 36, 0)"})}),(0,i.jsx)(n.td,{children:"Curling IO is better"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"11 teams, 5 sheets"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(1, 55, 0)"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(2, 55, 0)"})}),(0,i.jsx)(n.td,{children:"Curling IO is better"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"14 teams, 7 sheets"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(1, 84, 0)"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"(2, 84, 0)"})}),(0,i.jsx)(n.td,{children:"Curling IO is better"})]})]})]}),"\n",(0,i.jsx)(n.p,{children:"Our simple neutral-start Rust search matched OpenAI's best result on all five\nfixtures after a few seconds of combined local work per fixture. The much\nheavier offline process then produced the current catalog, which is better on\nfour of the five fixtures and equal on the fifth."}),"\n",(0,i.jsx)(n.p,{children:"AI is still useful here as another offline candidate generator. It can find a\nschedule our existing methods missed. We just treat its answer the same as any\nother untrusted candidate: parse it, validate every matchup and conflict,\ncalculate the score ourselves, and prove optimality when we can. For custom\nevent schedules, the browser optimizer works around locks and extra games under\na 100 ms deadline instead of asking a general model to rebuild the schedule\nfrom prose."}),"\n",(0,i.jsx)(n.h3,{id:"search-envelope",children:"Search Envelope"}),"\n",(0,i.jsx)(n.p,{children:"Our practical envelope contains:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"2 through 24 teams"}),"\n",(0,i.jsx)(n.li,{children:"1 through 16 selected sheets"}),"\n",(0,i.jsx)(n.li,{children:"one complete round robin"}),"\n",(0,i.jsx)(n.li,{children:"one iteration"}),"\n",(0,i.jsx)(n.li,{children:"no locked, ad hoc, or bracket games"}),"\n",(0,i.jsx)(n.li,{children:"two behaviours when one logical round spans multiple draws: start each round\nin a fresh draw, or fill blank sheets with games from the next round"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["That is ",(0,i.jsx)(n.code,{children:"23 \xd7 16 \xd7 2 = 736"})," lookup keys."]}),"\n",(0,i.jsx)(n.p,{children:"The two packing settings cannot al
1ways produce different layouts. If all games\nin a round fit in one draw, for example, there are no blank sheets for the next\nround to fill. Aliasing those equivalent cases reduces the envelope to 448\ndistinct layout requirements. The catalog stores 449 layouts because it also\nretains one historically used shape outside the envelope, 26 teams on 7\nsheets."}),"\n",(0,i.jsx)(n.p,{children:"This boundary was not arbitrary. We analyzed Curling IO data. Among thousands of generated, single-iteration schedules where sheets were scarce, 98% had at most 20 teams and 8 sheets. The five most common shapes were 8\xd74, 12\xd76, 6\xd73, 10\xd75, and 9\xd74. Together they accounted for 63% of that cohort."}),"\n",(0,i.jsx)(n.p,{children:"We extended sheet coverage to 16 because surplus-sheet layouts are cheap to\nstore and useful to support. We later expanded the team boundary to 24. The\n42-team outliers in the historical data still did not justify expanding the\ninteractive target that far."}),"\n",(0,i.jsx)(n.h3,{id:"research-harness",children:"Research Harness"}),"\n",(0,i.jsx)(n.p,{children:"The original CurlingSchedules implementation already had a circular\nround-robin generator, a greedy sheet allocator, local cleanup moves, and a\nsmall exact-match cache. We had also done a lot of experimentation, including a\nsimulated annealing branch."}),"\n",(0,i.jsx)(n.p,{children:"The first issue was measurement. Some historical runs used different weights,\nsome used an older definition of maximum repeats, and some tests did not assert\nwhat their names claimed. Results using different objective functions could\nnot be compared directly."}),"\n",(0,i.jsx)(n.p,{children:"We built an application in Rust as a research harness with:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"one reference schedule model and independent validator"}),"\n",(0,i.jsx)(n.li,{children:"deterministic fixtures and seeds"}),"\n",(0,i.jsx)(n.li,{children:"swappable search strategies"}),"\n",(0,i.jsx)(n.li,{children:"paired comparisons under equal budgets"}),"\n",(0,i.jsx)(n.li,{children:"text, JSON, and CSV reports"}),"\n",(0,i.jsx)(n.li,{children:"production-frequency weighting from Curling IO data"}),"\n",(0,i.jsx)(n.li,{children:"resumable, atomic checkpoints for long-running searches"}),"\n",(0,i.jsx)(n.li,{children:"a versioned catalog with the method, seed, effort, runtime, lower bound, and\nproof status attached to every layout"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.img,{alt:"The search and validation workflow used to add a schedule to the canonical catalog",src:t(49474).A+"",width:"1200",height:"560"})}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.em,{children:"Generated schedules pass through heuristic search, exact methods, and an\nindependent validator before entering the versioned catalog."})}),"\n",(0,i.jsx)(n.p,{children:"Cached layouts were comparison targets, never search inputs. Each algorithm had\nto produce its result from the same generated starting conditions."}),"\n",(0,i.jsx)(n.p,{children:"Rust was a good fit for this work for many of the same reasons we chose it for\nthe Curling IO v3 backend. Native execution let us evaluate large numbers of\ncandidates, and the type system made it difficult to mix an invalid partial\nschedule into a supposedly valid result. The same harness could run bounded\ncomparisons or resume long exact searches. This was a small research project we\nknew we would change constantly, but correctness still matters."}),"\n",(0,i.jsx)(n.h3,{id:"evaluated-algorithms",children:"Evaluated Algorithms"}),"\n",(0,i.jsx)(n.p,{children:"No single algorithm won across every fixture. The final offline process uses\nseveral methods with different costs and strengths."}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Approach"}),(0,i.jsx)(n.th,{children:"What it was good at"}),(0,i.jsx)(n.th,{children:"What we learned"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Greedy generation"}),(0,i.jsx)(n.td,{children:"Producing a valid deterministic baseline quickly"}),(0,i.jsx)(n.td,{children:"Local sheet choices are myopic, and more random rebuilds do not guarantee a better basin"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Single, double, triple, and quadruple swaps"}),(0,i.jsx)(n.td,{children:"Cheap local improvements"}),(0,i.jsx)(n.td,{children:"Two and three moves can escape some local optima, but deeper is not automatically better"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Targeted moves"}),(0,i.jsx)(n.td,{children:"Spending work on teams and sheets blocking the next fairness reduction"}),(0,i.jsx)(n.td,{children:"Reusing scoring ledgers improved both quality and speed"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Tabu and plateau walking"}),(0,i.jsx)(n.td,{children:"Moving through equal-scoring states"}),(0,i.jsx)(n.td,{children:"Strict improvement-only search gets stuck too early"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Greedy reconstruction"}),(0,i.jsx)(n.td,{children:"Entering a different part of the search space"}),(0,i.jsx)(n.td,{children:"Independent restarts were more useful than repeatedly polishing one schedule"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Elite pools and populations"}),(0,i.jsx)(n.td,{children:"Preserving several strong, different candidates"}),(0,i.jsx)(n.td,{children:"More diversity does not automatically become better schedules"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Simulated annealing"}),(0,i.jsx)(n.td,{children:"Occasionally improving an unresolved fixture"}),(0,i.jsx)(n.td,{children:"It was not a competitive general strategy under our measured budgets"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Ruin-and-recreate, crossover, rotations, and sheet-column moves"}),(0,i.jsx)(n.td,{children:"Adding specific neighbourhoods to a portfolio"}),(0,i.jsx)(n.td,{children:"Most were fixture-sensitive, and several were useful only as negative controls"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Constraint solving and branch-and-bound"}),(0,i.jsx)(n.td,{children:"Proving a target reachable or impossible"}),(0,i.jsx)(n.td,{children:"Exact methods work best after the problem is decomposed correctly"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Combinatorial constructions"}),(0,i.jsx)(n.td,{children:"Solving whole families directly"}),(0,i.jsx)(n.td,{children:"Sometimes the right answer is a design, not more search"})]})]})]}),"\n",(0,i.jsx)(n.h4,{id:"local-search",children:"Local Search"}),"\n",(0,i.jsx)(n.p,{children:"The simplest optimizer tries one legal game swap and keeps it only if the tuple\nscore improves. Our depth-two version tries a second swap even when the first\nmove is awful, then keeps both only if their combined result beats the untouched\nschedule. Across 20 neutral eight-team starts, depth two beat single-swap search\n16 times and reached the known optimum twice."}),"\n",(0,i.jsx)(n.p,{children:"Depth three improved the odds again, but it did not dominate depth two. Across\n1,000 paired starts it won 394, tied 310, and lost 296. Depth four then lost\nmore often than it won against depth three. Bigger neighbourhoods created new\npaths, but they also spent more of a fixed budget wandering through unhelpful\nones."}),"\n",(0,i.jsx)(n.p,{children:"Targeting helped more consistently. We aimed most source-game choices at the\nteam-sheet assignments blocking the next maximum-repeat reduction. Then we\nadded tabu memory so the search could walk across previously unseen\nequal-fairness states instead of bouncing between the same arrangements."}),"\n",(0,i.jsxs)(n.p,{children:["Still, the classic 8\xd74 case exposed the limit of local moves. One exact check\nproved that a particular matchup grouping could do n
1o better than ",(0,i.jsx)(n.code,{children:"(1,24,5)"}),".\nThe global optimum was ",(0,i.jsx)(n.code,{children:"(1,24,0)"}),". No amount of sheet shuffling inside that\ngrouping could close the gap because the pairings had to be regrouped across\ndraws."]}),"\n",(0,i.jsx)(n.p,{children:"This is a limit of local search. Some improvements require changing which games\nshare a draw, not only changing their sheet assignments."}),"\n",(0,i.jsx)(n.h4,{id:"simulated-annealing",children:"Simulated Annealing"}),"\n",(0,i.jsx)(n.p,{children:"We tried simulated annealing twice, first in the old Gleam implementation and\nagain as a deliberately smaller Rust experiment. The larger historical version\nhad temperature schedules, reheating, restarts, adaptive move weights, sheet\ncycles, draw rotations, targeted moves, and several phases. It retained the\nbest candidate, so returning a hot degraded state did not explain its\nperformance."}),"\n",(0,i.jsx)(n.p,{children:"Considering the effort we put into understanding and implementing simulated\nannealing, the results were disappointing. The smaller Rust version made the\ncomparison easier to trust. It remained competitive on 8\xd74 but lost across the\nbroader fixture suite. Annealing added some portfolio diversity on one shape,\nbut the evidence did not support using it as a general strategy."}),"\n",(0,i.jsx)(n.p,{children:"We retained the negative result in the research record. Further temperature\ntuning was lower priority than approaches that improved more fixtures under\nthe same evaluation budget."}),"\n",(0,i.jsx)(n.h4,{id:"reconstruction-and-portfolios",children:"Reconstruction and Portfolios"}),"\n",(0,i.jsx)(n.p,{children:"Greedy reconstruction keeps the draws and competition rules but rebuilds sheet\nassignments from a new seeded ordering. Five independent reconstructed starts\nbeat five ordinary starts under the same total evaluation budget. It did not\nsolve every fixture, but it entered basins that swap-only search never saw."}),"\n",(0,i.jsx)(n.p,{children:"Our offline portfolio eventually kept independent reconstruction and population\nworkers, plus a separate lane that exploited the best validated candidate found\nso far. We tried forcing every worker back to the global best, but that reduced\ndiversity and did not help. Sharing became useful only when it was additional\nwork rather than a replacement for each worker's private search."}),"\n",(0,i.jsx)(n.p,{children:"This is why we added checkpoints to the offline runner for each worker's\nprivate state as well as the global best. A long search can stop and resume\nwith both the global best and the independent state of each worker intact."}),"\n",(0,i.jsx)(n.h3,{id:"exact-methods-and-constructions",children:"Exact Methods and Constructions"}),"\n",(0,i.jsx)(n.p,{children:"Heuristic search gave us strong candidates, but proving optimality required\nexact methods."}),"\n",(0,i.jsx)(n.p,{children:"We added exact methods in layers instead of handing the entire problem to one\nsolver:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Fixed-draw sheet assignment"})," keeps matchups and draw membership fixed,\nthen solves only which sheet hosts each game."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Logical-round models"})," can repartition games across the draws belonging to\none round while preserving round boundaries."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Packed factorization search"})," chooses both the matchup grouping and sheet\nassignment for smaller full-draw schedules."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Bounded repair queries"})," ask whether a known target can be reached within\na radius of a strong incumbent."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Combinatorial constructions"})," directly generate known design families such\nas partitioned balanced tournament designs."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"The fixed-draw constraint solver brought several common layouts to their global\nlower bounds. A later exact pass promoted 64 of 74 previously unresolved\nhigh-sheet layouts to proven optimal, about 86%. Packed branch-and-bound also\nproved the 10\xd75 lower bound from generated input without reading the cached\nschedule, and handled small odd-team schedules using near-perfect matchings."}),"\n",(0,i.jsxs)(n.p,{children:["The exact results also identified limits in the search formulation. A 16\xd78\nround robin is a partitioned balanced tournament design of side eight. A\npublished starter-adder construction produced the global ",(0,i.jsx)(n.code,{children:"(1,112,0)"})," lower\nbound directly. Local search had stalled because a strong heuristic schedule\nneeded at least 41 game-sheet placements changed under its existing draw\ncomposition."]}),"\n",(0,i.jsxs)(n.p,{children:["For 19 teams on 9 sheets, bounded repair could not reach ",(0,i.jsx)(n.code,{children:"(1,171,17)"})," within 10\nchanged cells. We removed redundant variables and encoded the lower-bound\ncondition directly: every team uses every sheet exactly twice. Z3 found\n",(0,i.jsx)(n.code,{children:"(1,171,0)"})," on the existing cyclic matchup structure. The solution differed\nfrom the starting sheet labels in 151 of 171 game cells, about 88%. The local\nrepair radius had excluded the relevant solutions."]}),"\n",(0,i.jsx)(n.p,{children:"We then ran an exact round-order finishing pass. It treats each complete logical\nrou
1nd as one block and finds the best path through those blocks without changing\nmatchups, sheets, or the games grouped into each draw. That improved 28 catalog\nentries and proved 23 of them optimal. The pass deliberately rejects packed\nschedules whose draws cross round boundaries rather than guessing which games\nbelong together."}),"\n",(0,i.jsxs)(n.p,{children:["A short constraint sweep produced another 51 proofs. Forty-nine finished in\nunder one second on the development machine. The largest remaining\nback-to-back gap was 17 teams on 8 sheets, which improved from ",(0,i.jsx)(n.code,{children:"(1,136,16)"})," to\nthe global ",(0,i.jsx)(n.code,{children:"(1,136,0)"})," lower bound after about 25 seconds. Both 13-team,\n4-sheet packing modes remained unresolved after a one-minute attempt."]}),"\n",(0,i.jsxs)(n.p,{children:["Finally, we generalized the full-round enumerator to allow surplus sheets. It\nexhausted six small search spaces, proved all six optima, and improved the\n6-team, 5-sheet catalog entry from ",(0,i.jsx)(n.code,{children:"(1,3,0)"})," to ",(0,i.jsx)(n.code,{children:"(1,2,0)"}),". These proofs matter\nbecause the cheap generic lower bound is not attainable for every shape."]}),"\n",(0,i.jsx)(n.p,{children:"Seventeen layouts were still marked best-found at that point. Graph-degree\nparity and a single-sheet transition bound proved 12 of them without changing\ntheir schedules. Cyclic edge-difference constructions then closed the 9-team\nand 13-team two-sheet layouts. Complete edge-colouring, draw-pairing, and draw\nordering searches closed the 6-team and 7-team two-sheet layouts."}),"\n",(0,i.jsxs)(n.p,{children:["The last case was 9 teams on 8 sheets. Searching complete schedules carried too\nmuch symmetry, so we split it into smaller exact problems. A pseudo-Boolean\nmodel assigned the 36 games to sheets first. An exact factorization then formed\nnine legal draws, and a Hamiltonian path search ordered them without\nback-to-back repeats. The result improved from ",(0,i.jsx)(n.code,{children:"(1,5,0)"})," to the global lower\nbound of ",(0,i.jsx)(n.code,{children:"(1,4,0)"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["We later expanded the catalog through 24 teams. Published partitioned balanced\ntournament designs gave exact 22-team, 11-sheet and 24-team, 12-sheet\nschedules. Splitting the two balanced halves of those designs also proved nine\nsurplus-sheet layouts. Exact draw ordering and the existing odd-team split\nconstruction proved another 14 layouts. The remaining new shapes still receive\nvalidated cached schedules, but keep ",(0,i.jsx)(n.strong,{children:"Optimize"})," available while we work\nthrough them offline."]}),"\n",(0,i.jsx)(n.h3,{id:"coverage-and-proof-status",children:"Coverage and Proof Status"}),"\n",(0,i.jsx)(n.p,{children:"Catalog revision 23 resolves every team-and-sheet shape in the practical\n2-through-24-team and 1-through-16-sheet envelope. Of the 449 stored layouts,\n419 are proven optimal and 30 retain validated best-found results."}),"\n",(0,i.jsx)(n.p,{children:"Of the 419 proven layouts, 392 reach the generic global lower bound. The other\n27 need a tighter proof: 16 use a graph-degree parity bound, one uses a\nsingle-sheet transition bound, and ten use exhaustive search. The odd\nfive-team, two-sheet layout is one example. Exact enumeration proved that four\nback-to-back repeats cannot be removed. Small surplus-sheet layouts provide\nothers because games sharing a draw still need different sheets."}),"\n",(0,i.jsx)(n.p,{children:"Every lookup in the defined envelope has a validated cached result. The 30\nbest-found layouts remain in the improvement queue. Proven layouts stay in the\nregression suite, but receive no more offline search budget unless the catalog\nenvelope or the scoring rules change."}),"\n",(0,i.jsx)(n.h3,{id:"offline-rust-and-browser-javascript",children:"Offline Rust and Browser JavaScript"}),"\n",(0,i.jsx)(n.p,{children:"Precomputing ordinary layouts changes the role of the browser optimizer. It is\nused for schedules that no longer match the canonical inputs."}),"\n",(0,i.jsx)(n.p,{children:"A real event may have:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"multiple round-robin pools sharing sheets"}),"\n",(0,i.jsx)(n.li,{children:"multiple iterations, where combining canonical layouts creates new repeat\npatterns"}),"\n",(0,i.jsx)(n.li,{children:"locked games that Allocate and Optimize must not move"}),"\n",(0,i.jsx)(n.li,{children:"ad hoc filler games for unbalanced round robins"}),"\n",(0,i.jsx)(n.li,{children:"bracket games that have to follow their predecessors"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Those combinations are too event-specific to cache globally. They still need\nimmediate and reversible optimization in the editor."}),"\n",(0,i.jsx)(n.p,{children:"The in-browser optimizer runs in a cancellable Web Worker with a hard 100 ms\ndeadline today. It always retains the input and the best valid result found, so\nrunning longer can fail to improve a schedule but cannot return a worse one."}),"\n",(0,i.jsx)(n.p,{children:"Its current dispatcher uses three broad policies:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"ordinary:"})," an unlocked schedule that needs general local improvement"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"constrained repair:"})," locks or fixed placements make stability important"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"bracket-safe:"}
1)," game ordering and fixed draw boundaries matter more than a\nwider reconstruction"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"It spends 70% of the deadline on depth-two search, then gives the remainder to\nthe selected repair lane. The bracket-safe path stays with depth two for the\nwhole deadline. Across 700 paired benchmark cases, this three-policy dispatcher\nbeat the older five-route selector 22 times, tied 666, and lost 12."}),"\n",(0,i.jsx)(n.p,{children:"The paired result supported using the simpler dispatcher. It nearly always\nreturns the same or a better answer, runs off the main thread, and selects a\npolicy from visible schedule features."}),"\n",(0,i.jsx)(n.p,{children:"The browser and Rust implementations share the schedule vocabulary, validator\nexpectations, fairness tuple, fixtures, and evidence. The offline Rust budget is\neffectively unconstrained, so it can use populations, exact solvers, parallel\nworkers, and long-running checkpoints. The browser budget is tightly constrained\nbecause longer optimization would noticeably affect the editor's responsiveness."}),"\n",(0,i.jsx)(n.h3,{id:"catalog-distribution-and-runtime-selection",children:"Catalog Distribution and Runtime Selection"}),"\n",(0,i.jsx)(n.p,{children:"Drawmaster writes a versioned JSON catalog. Each entry contains indexed teams,\ndraws, and sheets plus its fairness tuple, lower bound, producing method, proof\nstatus, command, and independent validation evidence."}),"\n",(0,i.jsxs)(n.p,{children:["The catalog is owned by our shared Curling scheduling package and bundled into\nthe Curling IO v3 event schedule builder. The free\n",(0,i.jsx)(n.a,{href:"https://curlingschedules.com/",children:"CurlingSchedules.com"})," editor also consumes the\nsame artifact, but its interface and local-storage persistence remain separate\nfrom Curling IO. An 8\xd74 lookup resolves to the same canonical schedule in both\nplaces."]}),"\n",(0,i.jsx)(n.p,{children:"The runtime order is:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsx)(n.li,{children:"Look for an exact canonical layout."}),"\n",(0,i.jsx)(n.li,{children:"Apply it immediately when it maps safely to the teams and selected sheets."}),"\n",(0,i.jsx)(n.li,{children:"Repeat or compose catalog layouts for iterations and multiple pools."}),"\n",(0,i.jsx)(n.li,{children:"Repair around locks, extra games, bracket games, and manual placements."}),"\n",(0,i.jsx)(n.li,{children:"Generate directly only when no catalog starting point can be used."}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["An exact, single-iteration layout marked proven optimal disables ",(0,i.jsx)(n.strong,{children:"Optimize"}),".\nThere is nothing useful for the button to do. The catalog format can still hold\na best-found layout if the envelope expands before its optimum is proved. In\nthat case, Optimize remains available. Repeated, composed, or repaired\nschedules also remain optimizable because the original one-iteration proof no\nlonger covers the combined result."]}),"\n",(0,i.jsx)(n.p,{children:"Club draw schedule templates are separate. A drawmaster can deliberately apply\na recurring local arrangement instead of the global default, and v3 warns\nbefore that template moves games already placed in the schedule. Templates\nexpress a club's preference. The canonical catalog expresses the best general\nresult we can validate for a numerical shape."}),"\n",(0,i.jsx)(n.h3,{id:"findings",children:"Findings"}),"\n",(0,i.jsx)(n.p,{children:"The research produced the following working conclusions:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Complete validated coverage took priority over spending more search time on\none already well-understood layout"}),"\n",(0,i.jsx)(n.li,{children:"No heuristic dominated every team-and-sheet shape. The amount of variance\nbetween shapes was larger than we expected"}),"\n",(0,i.jsx)(n.li,{children:"More search depth did not guarantee better results under a fixed budget"}),"\n",(0,i.jsx)(n.li,{children:"Equal-score plateau movement mattered, but it could not repair a bad matchup\nfactorization"}),"\n",(0,i.jsx)(n.li,{children:"Reconstruction and independent portfolios found different basins more\
1nreliably than one long local walk"}),"\n",(0,i.jsx)(n.li,{children:"Simulated annealing did not perform well enough to become a general strategy"}),"\n",(0,i.jsx)(n.li,{children:"Exact solvers became practical after we split the problem into the right\nlayers"}),"\n",(0,i.jsx)(n.li,{children:"Published combinatorial designs supplied direct solutions for applicable\nschedule families"}),"\n",(0,i.jsx)(n.li,{children:"Proof status belongs beside every cached answer"}),"\n",(0,i.jsx)(n.li,{children:"The best production optimizer is often a cache lookup followed by a small,\ndomain-specific repair"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"The current catalog covers the v3 target envelope, and every stored layout is\nproven optimal. The remaining research is event-specific repair around locks,\nad hoc games, bracket games, and shared resources, plus any future expansion of\nthe catalog envelope."}),"\n",(0,i.jsx)(n.p,{children:"Curling IO v3 maps each abstract catalog layout onto real event teams,\nresources, draws, and games. From there, the drawmaster can apply club\ntemplates, import assignments, add ad hoc games, and make manual changes while\npreserving any placements they choose to lock."}),"\n",(0,i.jsx)(n.h2,{id:"glossary",children:"Glossary"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Fairness tuple:"})," The ordered ",(0,i.jsx)(n.code,{children:"(Max, Total, Back-to-back)"})," score used to\ncompare schedules. Lower is better, and the numbers are compared from left\nto right."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Heuristic:"})," A method designed to find a strong schedule quickly without\nproving that it is the best possible schedule."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Local search:"})," Improving a schedule by making nearby changes to its\ncurrent arrangement."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Swap depth:"})," The number of moves evaluated together before deciding\nwhether to keep them. A depth-two search can keep two moves whose combined\nresult improves the original schedule."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Plateau:"})," A group of different schedules with the same score."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Plateau walking:"})," Moving through equal-scoring schedules in search of a\nposition from which an improvement becomes possible."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Tabu search:"})," Keeping a short memory of recent moves or schedules so the\nsearch does not repeatedly cycle through them."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Simulated annealing:"})," A search that sometimes accepts a worse move to\nescape a local optimum, with that willingness usually decreasing over time."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Basin:"})," A region of the search space whose nearby moves tend to lead to\nthe same local optimum."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Portfolio:"})," Several search strategies or workers run independently, with\nthe best validated result retained."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Exact method or solver:"})," A method that can establish whether a target is\nreachable or prove that no better valid schedule exists."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Branch-and-bound:"})," An exact method that stops exploring a branch when its\nmathematical bound proves it cannot beat the best result already found."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Lower bound:"})," A score that no valid schedule can beat. Reaching an\napplicable lower bound proves optimality."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Proven optimal:"})," A validated schedule for which no better fairness tuple\ncan exist."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Best-found:"})," The strongest validated schedule found so far, without proof\nthat a better one does not exist."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Checkpoint:"})," Saved search state that lets a long-running process resume\nwithout starting over."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},17073:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/locked-and-extra-games-1480948d28ae5b65d9dac7a7347ed3b5.png"},28453:(e,n,t)=>{t.d(n,{R:()=>r,x:()=>o});var s=t(96540);const i={},a=s.createContext(i);function r(e){const n=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),s.createElement(a.Provider,{value:n},e.children)}},43499:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/repeat-highlights-31d5b6c8e316e492979c7d54f8b5f7cb.png"},49474:(e,n,t)=>{t.d(n,{A:()=>s});const s="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU2MCIgdmlld0JveD0iMCAwIDEyMDAgNTYwIiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0idGl0bGUgZGVzY3JpcHRpb24iPgogIDx0aXRsZSBpZD0idGl0bGUiPkhvdyBhIGN1cmxpbmcgc2NoZWR1bGUgZW50ZXJzIHRoZSBjYW5vbmljYWwgY2F0YWxvZzwvdGl0bGU+CiAgPGRlc2MgaWQ9ImRlc2NyaXB0aW9uIj5HZW5lcmF0ZWQgc2NoZWR1bGVzIHBhc3MgdGhyb3VnaCBoZXVyaXN0aWMgc2VhcmNoIGFuZCBleGFjdCBtZXRob2RzLCB0aGVuIGluZGVwZW5kZW50IHZhbGlkYXRpb24sIGJlZm9yZSB0aGV5IGVudGVyIHRoZSB2ZXJzaW9uZWQgY2F0YWxvZyB1c2VkIGJ5IEN1cmxpbmcgSU8uPC9kZXNjPgogIDxkZWZzPgogICAgPGZpbHRlciBpZD0ic2hhZG93IiB4PSItMjAlIiB5PSItMjAlIiB3aWR0aD0iMTQwJSIgaGVpZ2h0PSIxNDAlIj4KICAgICAgPGZlRHJvcFNoYWRvdyBkeD0iMCIgZHk9IjIiIHN0ZERldmlhdGlvbj0iMyIgZmxvb2QtY29sb3I9IiMwZjE3MmEiIGZsb29kLW9wYWNpdHk9Ii4xMiIvPgogICAgPC9maWx0ZXI+CiAgPC9kZWZzPgoKICA8cmVjdCB3aWR0aD0iMTIwMCIgaGVpZ2h0PSI1NjAiIHJ4PSIyMCIgZmlsbD0iI2Y4ZmFmYyIvPgogIDx0ZXh0IHg9IjUyIiB5PSI2MiIgZmlsbD0iIzBmMTcyYSIgZm9udC1mYW1pbHk9IkludGVyLCB1aS1zYW5zLXNlcmlmLCBzeXN0ZW0tdWksIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMzAiIGZvbnQtd2VpZ2h0PSI3MDAiPkhvdyBhIHNjaGVkdWxlIGVudGVycyB0aGUgY2F0YWxvZzwvdGV4dD4KICA8dGV4dCB4PSI1MiIgeT0iOTQiIGZpbGw9IiM0NzU1NjkiIGZvbnQtZmFtaWx5PSJJbnRlciwgdWktc2Fucy1zZXJpZiwgc3lzdGVtLXVpLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjE3Ij5TZWFyY2ggYW5kIHByb29mIHVzZSB0aGUgc2FtZSBzY2hlZHVsZSBtb2RlbCwgc2NvcmluZyB0dXBsZSwgYW5kIHZhbGlkYXRvci48L3RleHQ+CgogID
1xnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzY0NzQ4YiIgc3Ryb2tlLXdpZHRoPSIzIj4KICAgIDxwYXRoIGQ9Ik0yNzQgMjM0aDI0Ii8+CiAgICA8cGF0aCBkPSJNNTQ2IDIzNGgyNCIvPgogICAgPHBhdGggZD0iTTgxOCAyMzRoMjQiLz4KICAgIDxwYXRoIGQ9Ik05NzQgMzE4djE4Ii8+CiAgPC9nPgogIDxnIGZpbGw9IiM2NDc0OGIiPgogICAgPHBhdGggZD0iTTI5OCAyMjhsMTAgNi0xMCA2eiIvPgogICAgPHBhdGggZD0iTTU3MCAyMjhsMTAgNi0xMCA2eiIvPgogICAgPHBhdGggZD0iTTg0MiAyMjhsMTAgNi0xMCA2eiIvPgogICAgPHBhdGggZD0iTTk2OCAzMzZoMTJsLTYgMTB6Ii8+CiAgPC9nPgoKICA8ZyBmaWx0ZXI9InVybCgjc2hhZG93KSIgZm9udC1mYW1pbHk9IkludGVyLCB1aS1zYW5zLXNlcmlmLCBzeXN0ZW0tdWksIHNhbnMtc2VyaWYiPgogICAgPGc+CiAgICAgIDxyZWN0IHg9IjQyIiB5PSIxNTAiIHdpZHRoPSIyMzIiIGhlaWdodD0iMTY4IiByeD0iMTQiIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2NiZDVlMSIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICAgIDxjaXJjbGUgY3g9IjczIiBjeT0iMTgxIiByPSIxNCIgZmlsbD0iI2RiZWFmZSIvPgogICAgICA8dGV4dCB4PSI3MyIgeT0iMTg3IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjMWQ0ZWQ4IiBmb250LXNpemU9IjE2IiBmb250LXdlaWdodD0iNzAwIj4xPC90ZXh0PgogICAgICA8dGV4dCB4PSI5NiIgeT0iMTg4IiBmaWxsPSIjMGYxNzJhIiBmb250LXNpemU9IjIwIiBmb250LXdlaWdodD0iNzAwIj5Jbml0aWFsPC90ZXh0PgogICAgICA8dGV4dCB4PSI2OCIgeT0iMjI5IiBmaWxsPSIjNDc1NTY5IiBmb250LXNpemU9IjE2Ij5DaXJjbGUgcGFpcmluZ3M8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjY4IiB5PSIyNTMiIGZpbGw9IiM0NzU1NjkiIGZvbnQtc2l6ZT0iMTYiPkRldGVybWluaXN0aWMgc2VlZHM8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjY4IiB5PSIyNzciIGZpbGw9IiM0NzU1NjkiIGZvbnQtc2l6ZT0iMTYiPk5vIGNhY2hlZCBpbnB1dDwvdGV4dD4KICAgIDwvZz4KCiAgICA8Zz4KICAgICAgPHJlY3QgeD0iMzE0IiB5PSIxNTAiIHdpZHRoPSIyMzIiIGhlaWdodD0iMTY4IiByeD0iMTQiIGZpbGw9IiNmZmYiIHN0cm9rZT0iIzkzYzVmZCIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICAgIDxjaXJjbGUgY3g9IjM0NSIgY3k9IjE4MSIgcj0iMTQiIGZpbGw9IiNkYmVhZmUiLz4KICAgICAgPHRleHQgeD0iMzQ1IiB5PSIxODciIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMxZDRlZDgiIGZvbnQtc2l6ZT0iMTYiIGZvbnQtd2VpZ2h0PSI3MDAiPjI8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjM2OCIgeT0iMTg4IiBmaWxsPSIjMGYxNzJhIiBmb250LXNpemU9IjIwIiBmb250LXdlaWdodD0iNzAwIj5IZXVyaXN0aWM8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjM0MCIgeT0iMjI5IiBmaWxsPSIjNDc1NTY5IiBmb250LXNpemU9IjE2Ij5UYXJnZXRlZCBsb2NhbCBzZWFyY2g8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjM0MCIgeT0iMjUzIiBmaWxsPSIjNDc1NTY5IiBmb250LXNpemU9IjE2Ij5SZWNvbnN0cnVjdGlvbjwvdGV4dD4KICAgICAgPHRleHQgeD0iMzQwIiB5PSIyNzciIGZpbGw9IiM0NzU1NjkiIGZvbnQtc2l6ZT0iMTYiPkluZGVwZW5kZW50IHBvcHVsYXRpb25zPC90ZXh0PgogICAgPC9nPgoKICAgIDxnPgogICAgICA8cmVjdCB4PSI1ODYiIHk9IjE1MCIgd2lkdGg9IjIzMiIgaGVpZ2h0PSIxNjgiIHJ4PSIxNCIgZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjZmJiZjI0IiBzdHJva2Utd2lkdGg9IjIiLz4KICAgICAgPGNpcmNsZSBjeD0iNjE3IiBjeT0iMTgxIiByPSIxNCIgZmlsbD0iI2ZlZjNjNyIvPgogICAgICA8dGV4dCB4PSI2MTciIHk9IjE4NyIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0iI2I0NTMwOSIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjcwMCI+MzwvdGV4dD4KICAgICAgPHRleHQgeD0iNjQwIiB5PSIxODgiIGZpbGw9IiMwZjE3MmEiIGZvbnQtc2l6ZT0iMjAiIGZvbnQtd2VpZ2h0PSI3MDAiPkV4YWN0PC90ZXh0PgogICAgICA8dGV4dCB4PSI2MTIiIHk9IjIyOSIgZmlsbD0iIzQ3NTU2OSIgZm9udC1zaXplPSIxNiI+Q29uc3RyYWludCBzb2x2aW5nPC90ZXh0PgogICAgICA8dGV4dCB4PSI2MTIiIHk9IjI1MyIgZmlsbD0iIzQ3NTU2OSIgZm9udC1zaXplPSIxNiI+QnJhbmNoLWFuZC1ib3VuZDwvdGV4dD4KICAgICAgPHRleHQgeD0iNjEyIiB5PSIyNzciIGZpbGw9IiM0NzU1NjkiIGZvbnQtc2l6ZT0iMTYiPkNvbWJpbmF0b3JpYWwgZGVzaWduczwvdGV4dD4KICAgIDwvZz4KCiAgICA8Zz4KICAgICAgPHJlY3QgeD0iODU4IiB5PSIxNTAiIHdpZHRoPSIyMzIiIGhlaWdodD0iMTY4IiByeD0iMTQiIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2NiZDVlMSIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICAgIDxjaXJjbGUgY3g9Ijg4OSIgY3k9IjE4MSIgcj0iMTQiIGZpbGw9IiNlMmU4ZjAiLz4KICAgICAgPHRleHQgeD0iODg5IiB5PSIxODciIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiMzMzQxNTUiIGZvbnQtc2l6ZT0iMTYiIGZvbnQtd2VpZ2h0PSI3MDAiPjQ8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjkxMiIgeT0iMTg4IiBmaWxsPSIjMGYxNzJhIiBmb250LXNpemU9IjIwIiBmb250LXdlaWdodD0iNzAwIj5WYWxpZGF0aW9uPC90ZXh0PgogICAgICA8dGV4dCB4PSI4ODQiIHk9IjIyOSIgZmlsbD0iIzQ3NTU2OSIgZm9udC1zaXplPSIxNSI+R2FtZXMsIGRyYXdzLCBhbmQgc2hlZXRzPC90ZXh0PgogICAgICA8dGV4dCB4PSI4ODQiIHk9IjI1MyIgZmlsbD0iIzQ3NTU2OSIgZm9udC1zaXplPSIxNSI+RmFpcm5lc3MgYW5kIHByb29mIHN0YXR1czwvdGV4dD4KICAgIDwvZz4KCiAgICA8Zz4KICAgICAgPHJlY3QgeD0iNzIwIiB5PSIzNTIiIHdpZHRoPSIzNzAiIGhlaWdodD0iMTM0IiByeD0iMTQiIGZpbGw9IiNlZmY2ZmYiIHN0cm9rZT0iIzI1NjNlYiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICAgIDxjaXJjbGUgY3g9Ijc1MSIgY3k9IjM4MyIgcj0iMTQiIGZpbGw9IiMyNTYzZWIiLz4KICAgICAgPHRleHQgeD0iNzUxIiB5PSIzODkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiNmZmYiIGZvbnQtc2l6ZT0iMTYiIGZvbnQtd2VpZ2h0PSI3MDAiPjU8L3RleHQ+CiAgICAgIDx0ZXh0IHg9Ijc3NCIgeT0iMzkwIiBmaWxsPSIjMGYxNzJhIiBmb250LXNpemU9IjIwIiBmb250LXdlaWdodD0iNzAwIj5DYXRhbG9nPC90ZXh0PgogICAgICA8dGV4dCB4PSI3
1NDYiIHk9IjQyOSIgZmlsbD0iIzQ3NTU2OSIgZm9udC1zaXplPSIxNiI+VmFsaWRhdGVkIGxheW91dHM8L3RleHQ+CiAgICAgIDx0ZXh0IHg9Ijc0NiIgeT0iNDUzIiBmaWxsPSIjNDc1NTY5IiBmb250LXNpemU9IjE2Ij5CdW5kbGVkIGludG8gQ3VybGluZyBJTyB2MzwvdGV4dD4KICAgIDwvZz4KICA8L2c+Cgo8L3N2Zz4K"},66899:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/canonical-8x4-6e54d901aa66b015304394203545e0eb.png"},97465:e=>{e.exports=JSON.parse('{"permalink":"/blog/optimizing-curling-draw-schedules","source":"@site/blog/2026-08-03-optimizing-curling-draw-schedules.mdx","title":"Optimizing Curling Draw Schedules","description":"*This post is part of our Curling IO v3","date":"2026-08-03T00:00:00.000Z","tags":[{"inline":true,"label":"v3","permalink":"/blog/tags/v-3"},{"inline":true,"label":"competition-management","permalink":"/blog/tags/competition-management"},{"inline":true,"label":"architecture","permalink":"/blog/tags/architecture"},{"inline":true,"label":"rust","permalink":"/blog/tags/rust"},{"inline":true,"label":"javascript","permalink":"/blog/tags/javascript"},{"inline":true,"label":"sneak-peek","permalink":"/blog/tags/sneak-peek"}],"readingTime":25.84,"hasTruncateMarker":true,"authors":[{"name":"Dave Rapin","title":"Founder @ Curling IO","imageURL":"https://avatars.githubusercontent.com/u/1202?v=4","key":"dave","page":null}],"frontMatter":{"slug":"optimizing-curling-draw-schedules","title":"Optimizing Curling Draw Schedules","authors":["dave"],"tags":["v3","competition-management","architecture","rust","javascript","sneak-peek"]},"unlisted":false,"prevItem":{"title":"Send Curling IO Purchases to Zapier or n8n","permalink":"/blog/product-webhooks-for-zapier-and-n8n"},"nextItem":{"title":"Renting Your Ice with Curling IO","permalink":"/blog/rentals-built-into-the-calendar"}}')}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.