Changes › freemapper-courseplan
Changes: freemapper-courseplan
This file lists all notable changes to freemapper-courseplan, newest first. The format follows Keep a Changelog.
[Unreleased]
[0.18.1] - 2026-11-15
Changed
- freemapper-core pin v0.28.0 (AGPL core, ADR 0094). No change in behaviour.
- Licence: AGPL-3.0-or-later instead of GPL-3.0-or-later (ADR 0093), like freemapper, freemapper-core and the other app. Whoever runs a modified version as a network service must offer its source. Releases up to now stay GPL-3.0-or-later.
[0.18.0] - 2026-11-14
Added
- Forest maps in ISOM 2017-2 (ADR 0092).
initaccepts a forest map at 1:5000 to 1:15000 whose symbol set isISOM 2017-2, and you set courses on it by hand with the same commands as on a sprint map, in the editor, on the command line and through MCP.checkuses the forest variants of its rules, and the symbol numbers they read are confirmed against ISOM 2017-2 (docs/research/2026-11-14-isom-2017-2-symbols.md). On a forest map the limit for the mapped area (map.inside,map.margin) is 10 mm on the sheet, which is 100 m at 1:10000. Rules for sprints only are listed as not for forest maps, the Lkm rules ofnor-bb-2021as not checked without a climb. score suggest,score draftandscore length, their MCP tools and the editor's Score panel refuse a forest map: the route search has no elevation and no vegetation speed. A score course set by hand works;checklists its length bands as not checked.
Changed
- A forest map in another symbol set (ISOM 2000, the first edition ISOM2017) is refused by
initwith a message that names the set found, instead of the general refusal of forest maps. - freemapper-core goes from v0.19.0 to v0.24.0 (ADR 0091). Print preview and editor map look the same. Code that writes PDF or SVG from the core now leaves out objects wholly outside the visible frame and writes paths more compactly, so PDFs and SVGs from the core are smaller; courseplan itself writes none yet.
[0.17.0] - 2026-11-13
Added
- The event has a date and an organizer (ADR 0090), requested by freemapper-print for its title block.
event set PROJECT --date YYYY-MM-DD --organizer TEXTsets them,--no-dateand--no-organizerremove them. The tool isset_event_details, the event settings of the editor have a "Date" and an "Organizer" field, andinfoandevent_infoshow both. The project file gets the optional keyorganizerin[event](format stays 1; a program from before this version refuses a file that has it). The IOF XML export writes a set date asEvent/StartTime/Dateand a set organizer asEvent/Organiser/Name, and nothing for a field that is not set. Purple Pen has no field for either, so.ppenexport, import andsyncleave them alone. - You can reject candidate sites (ADR 0089).
score reject --at X,Ystores the position in the project file (new optional keyrejected_sites, format stays 1), andscore suggest,score draftand the candidate list of the editor leave out every site within 3 m of it.score unreject --at X,Ytakes the rejection back. The tools arereject_siteandunreject_site, the editor has a "Reject" button beside "Accept" in the candidate table. A program from before this version refuses a project file that has the new key.
Changed
- The mapped area reaches 50 m beyond the map objects instead of 40 m (rule
wkb2026.map.inside,metres, ADR 0088), the same number as the default margin. With the defaults, a control may stand at the outline of the objects; before it had to stand 10 m inside. Profiles that setmetresthemselves keep their value.
[0.16.0] - 2026-11-13
Added
- Three recommendations of the project owner, each with a setting in the
[score]table (ADR 0085 to 0087). A manual course may deviate;checkonly warns, hints or reports, andscore suggestandscore draftkeep to them.- Dead ends:
checkwarns when a control stands deeper in a dead end thandead_end_max_m(default 60 m, 20 to 300; rulewkb2026.control.dead_end, sprint and schoolyard maps). The depth is the walk from the control to where the path network offers a choice again; open areas and courtyards are no corridors. A control beside a wall, fence or thin building is also judged from the free cells a few metres around it, and the deepest answer counts. The draft and the suggestion leave such sites out. On the Gleisdreieck map a quay spur of 208 m is the case; a control that stood 68 m deep on it set the default (ADR 0085). - Start and finish:
checkgives a hint when they are more thanstart_finish_max_m(default 400 m, 50 to 2000) apart in a straight line (rulewkb2026.course.start_finish_distance).score draft, andscore suggest --course, refuse such a start and finish with a message that names both positions, the distance, the limit and the setting (ADR 0086). - Margin from the edge of the mapped area:
checkreports a start, finish, control or crossing point closer thanmap_margin_m(default 50 m, 0 to 200) to the edge as an error (rulewkb2026.map.margin). The edge is where no map object lies within 40 m any more; the distance is measured on a raster, to about 2 m. The draft leaves such sites out and refuses a start or finish that is too close (ADR 0087). - The settings come with
score settings --dead-end-max M --start-finish-max M --map-margin M, the toolset_score_settingsand three fields in the Score settings panel of the editor.
- Dead ends:
checkreports a start, finish, control or crossing point in the blank part of the sheet as an error. The new rulewkb2026.map.inside(source BL02, as the research file cites it) measures against the map objects themselves: a position counts as mapped when a map object lies within 40 m of it. The sheet frame (an axis-aligned rectangle line that spans 80 percent of the terrain) and layout symbols such as the north lines pattern (601, 602) do not count as objects. The message names the position, the nearest object and the limit.map_edgestays as it was; it measures against the box around the map objects and did not see a position in the blank corner of a diagonal map. Score courses are tested with start, controls and finish. See ADR 0084.- The pictures of the examples: the examples page and both example READMEs show the course in the editor, with start and finish on the map. The Gleisdreieck picture is new (
docs/manual/images/gleisdreieck-course.jpg).
Changed
- The example "Gleisdreieck" is redrafted under the new limits: 32 minutes, 26 controls, 63 points, the very good runner inside the band and the good runner near the target (before: 34 minutes, 33 controls, 95 points). Control 54 of the old version stood at the tip of the quay spur, 208 m deep, and six more controls were 39 to 47 m from the edge of the mapped area. Project file, IOF XML, control list, sheets, transcripts,
checkoutput, READMEs, the manual and its four Gleisdreieck pictures follow; the filesscore-34.*becamescore-32.*.checkis quiet apart from four accepted warnings (three alike code pairs and the share of hard controls). - The score drafts on the Rummelsburger Bucht reach 11 minutes with both runners inside their bands and 12 near the target (before: 16 and 17), since sites keep 50 m from the edge of this narrow strip. Start, finish and the four controls of the example course already kept the margin and stay.
- The score draft keeps its sites off the sheet frame by the same test as the new rule, with the extent of the terrain objects instead of the extent of all objects. On the Rummelsburger Bucht the frame was not recognised before, so a draft could take sites at its corners. The largest draft with both runners in their bands on that map is now 16 minutes (17 near the target). See ADR 0084.
- The README of the Rummelsburger Bucht example and the manual (captions, section "Checking") follow the new course, and the manual pictures of the example are new.
Fixed
- The example "Rummelsburger Bucht" had its finish in the blank part of the sheet, beside the map, and its start far from it. Start and finish now stand in the park in the middle, 34 m apart, and the course is a loop of four controls and 905 m (before: five controls, 1226 m). Project file, IOF XML, Purple Pen file, outputs, READMEs and all editor pictures of the example follow.
checkis clean. The example "Gleisdreieck" was checked: its start and finish (60 m apart) and all controls lie on the map.
[0.15.0] - 2026-11-12
Added
checkreports a control that stands in an out-of-bounds area as an error. The new ruleswkb2026.control.in_forbidden(forest maps) andwkb2026.control.in_forbidden_sprint(sprint and schoolyard maps) cite WKB26 A 2.2. An area is a special object of the kind out-of-bounds area that belongs to the course of the control or names no course, and any map area with the symbol 520 or 709, on sprint and schoolyard maps also 714. A control on the edge stands in the area. The rule does not test start, finish, crossing points or controls that are on no course. See ADR 0081.- The length bands of the score draft are targets with a tolerance. A runner is in the band, near the target (up to 3 percentage points outside, exactly 3 still counts) or misses the band. Near the target is a success:
score draftandscore lengthexit with 0 and name the deviation, and the JSON answers and the MCP tools carrylength.state. Only a miss beyond the tolerance exits with 1 and is a finding ofcheck. The draft still aims at the band; the tolerance only decides what it may return. See ADR 0083. - The bands and the tolerance are settable: a new setting
band_tolerance_percent(0 to 10, default 3),score settings --good-band MIN-MAX --very-good-band MIN-MAX --band-tolerance N, the parametersgood_band_percent,very_good_band_percentandband_tolerance_percentofset_score_settings, and input fields in the Score panel of the editor, whose length report says "in the band", "near the target" or "missed". The very good band must not start below the good band.
Changed
checkno longer tests the straight line between two controls against out-of-bounds areas. The warning "the straight leg crosses ..." is gone: WKB26 B 7.2.6 is about the running route, and a straight line over an out-of-bounds area is normal. The ruleswkb2026.leg.through_forbiddenandwkb2026.leg.through_forbidden_sprintstay in the rule profile and now appear incheck --allamong the rules that were not checked, until the route analysis exists. If you accepted a finding of these rules, the exception stays valid and shows at most among the acceptances that match no finding. See ADR 0080.- The example "Gleisdreieck" is redrafted: 34 minutes, 33 controls, 95 points (before: 33 minutes, 27 controls, 83 points). Six controls of the old version stood in map areas with the symbol 520 (ADR 0082). The good runner is at 94.7 percent, near the target (ADR 0083);
checkshows four accepted warnings about alike codes. Files, outputs, pictures and the numbers in the READMEs and the manual follow. - The example "Rummelsburger Bucht" now has five controls and only straight legs, with no bends and no marked leg. A leg is the straight line from control to control, and bends are the exception. IOF XML, the Purple Pen file, the outputs and both READMEs follow the new course.
- The screenshots in the manual show the new course. The picture for bends has bends for illustration only, and its caption says so. The findings pictures show three angle hints that came from moved controls in a working copy. The section "Legs and bends" now says that a leg is straight until you bend it.
Fixed
score suggestandscore draftno longer propose control sites that lie inside or on the edge of a map area with the symbol 520, 709 or 714, nor on the edge of an out-of-bounds special object. Before, a draft on Gleisdreieck had six controls in "do not enter" areas, whichcheckhas reported as an error since ADR 0081. Draft and check use the same geometry and the same symbol list (ADR 0082).
[0.14.1] - 2026-11-12
Added
- Screenshots in the user manual, chapter 4. Fourteen pictures per language show the editor on the example maps by Remo Madella (Rummelsburger Bucht for the sprint course, Gleisdreieck for the score course): the whole page, a control, a course with its categories, a bent and taped leg, control numbers, control descriptions, a special object, findings, the Event menu, the Purple Pen export dialog, and the Score panel with candidates, a draft and the applied course. The German manual shows the German interface and the English manual the English one. Each caption names the map and points to the
ATTRIBUTION.txtof the example.
Fixed
- Editor: when you select a leg, "Length" now shows the length of that leg. It used to show the length of the whole course.
- Editor, Score panel: the candidate table fits the panel, and the Accept column is no longer cut off. The note "No free code" appears only when no candidate has a code. If only the rest of the list has none, the rows say so themselves.
- Editor: the dialog for a new score course is titled "New score course", not "New course".
- Editor: the status bar stays on one line. A long message ends in an ellipsis and shows in full as a tooltip, and the area above no longer jumps.
- Editor: the findings drawer covers the properties and map area completely. A strip of the properties pane used to show above it.
edit: for undo, redo and quit the server now reads the request body before it answers. Otherwise a client on macOS could now and then see "Connection reset by peer" instead of the answer (ADR 0079).
[0.14.0] - 2026-11-12
Added
- Self-update (ADR 0076).
freemapper-courseplan updatereplaces the program in place with the newest signed release, after asking (--checkexits with 10 when a newer version exists,--yes,--version X.Y.Z,--rollback,--mode notify|install|off). The release is checked with minisign against a key built into the program, then by SHA-256 of the download and of the unpacked file, and the new file must answer--versionbefore anything is swapped. The old file stays asfreemapper-courseplan.previous. A release build looks for a newer version at most once a day, afterinfoand when the editor starts, and prints one line on stderr; the editor shows it in its status bar. The MCP toolscheck_updateandinstall_update(only withconfirm: true) tell the assistant, which asks you first; the first tool result carries a note once. Builds from source never update themselves. The program sends only a fixed User-Agent to the GitHub release servers. The release workflow now signs a manifest with the secretsMINISIGN_SECRET_KEYandMINISIGN_PASSWORDof the environmentrelease; the environment has a required reviewer, so a release asks for approval once, before it is signed and published. Unpacking by hand stays the way to reach the first release with the updater. User manual, chapter 2. - Examples on two maps by Remo Madella (MOO Challenge Berlin 2025), who released them on 2026-11-12 for use within the freemapper suite; this is not an open licence (ADR 0077).
examples/gleisdreieckholds a 33-minute score course (27 controls, 83 points; the good runner scores 92.8 percent, the very good runner finishes after 96.1 percent of the limit), andexamples/rummelsburger-buchta sprint course with four controls, bends, a marked leg, a first aid post and three accepted hints, with its IOF XML and Purple Pen export. Both come with transcripts and check output in English and German; each folder has anATTRIBUTION.txt. 45 minutes does not work on either map: the READMEs give the tries and the real limits. The Purple Pen file of the new example has not been opened in Purple Pen. - Wider pool of control sites for score courses (ADR 0073).
score suggestnow also uses the free ends of walls, fences, hedges, step lines, stairways and bridges, path ends and path junctions, bends and edges of paved ground, small buildings, canopies, water banks and more point symbols, each with a clarity that lowers the difficulty and never removes a site. A closed line gives all its corners. A site that cannot be routed no longer removes every site within 100 m, only those within 30 m. On the Lübeck mapsuggestfinds 31 sites instead of 19; on a 1.8 x 2.0 km map of Berlin-Kreuzberg 183 instead of 137. Both maps hold mostly buildings, so the gain is modest; on the Kreuzberg map the draft ends at 13 controls for 30 minutes and 26 to 28 for 45, no longer bound by the pool but by the time of the very good runner. - Four distance settings for score courses, in metres on the ground:
value1_min_m(150) keeps every control away from start and finish, so value 1 starts there;min_gap_m(100, not lower) is the least distance between two controls and replaces the scale-derived default ofscore suggest;max_reach_mlimits the distance from the start (automatic: a quarter of the ground the good runner covers in the time limit);high_value_gap_m(200) is the least distance between two high-valued controls, the values above 0.6 of the highest.score settings(--value1-min,--min-gap,--max-reach,--auto-reach,--high-value-gap), the toolset_score_settingsand the editor's Score panel set them; older project files load with the defaults. The draft keeps the high-value gap, andcheckwarns when a score course falls below either gap (score_min_gap,score_high_gap; ADR 0072).
Fixed
score suggestandscore draftno longer take the corners of a map frame as sites (ADR 0078). The Gleisdreieck map has a closed rectangle of symbol 501.1 around its content, and a draft used its four corners as controls.score draftandscore length(andcheck) now agree on a set. The same leg time came out a few digits apart depending on the search that made it, and the tour heuristic turned that into another tour: at the start 40,-40 of the Berlin example the draft promised 97 points and 99.8 percent, the applied course gave 96 points and 100.7 percent of the limit. Leg times are now rounded to a millisecond where they are made (ADR 0075).score draftfinds a set that meets both bands where one exists, and runs faster (ADR 0074). After the prefix search it now searches locally: it swaps, adds or drops a control, or moves a value up to two steps off its quantile, for at most 150 tour evaluations with the seed of the settings. On a 1.8 x 2.0 km map of Berlin-Kreuzberg every start and limit we tried (30 and 45 minutes, three starts) now meets both bands; before, the good runner landed 0.3 to 3 points above his band every time. The values of a draft are therefore not always exact quantiles. A pool that is too small still fails: on the Lübeck map 30 and 45 minutes give the good runner every point.- Faster route searches. The shortest-path search uses A*, one search per control reads its legs to all others, the route-choice searches of
score suggestandscore draftrun on several threads, and the or-opt step of the tour heuristic no longer copies the tour. On the Kreuzberg map the CPU time ofscore draftfell from 44 to 63 s to 24 to 35 s; most of what is left is the route search ofscore suggest(ADR 0074). - Score suggestions sit less at the rim of the map and closer to the start: the pick spaces sites more densely near the start and weighs down sites at the rim. On the Lübeck example the candidates within 48 m of the rim fell from 21 of 60 to 3, and the value-1 controls moved from 360 m to 230 m from the start (ADR 0071).
Changed
- The documentation examples
examples/luebeck-sprintandexamples/berlin-kreuzbergare gone, replaced by the two examples above. The Lübeck project, its Purple Pen and IOF XML exports and its score project stay as test fixtures intests/fixtures/luebeck-sprint/. The testberlin_score_example_meets_its_bands_and_check_is_quietbecamegleisdreieck_score_example_meets_its_bands_and_check_is_quiet.
[0.13.0] - 2026-11-11
Added
- The Lübeck example has a score course in a second project file,
luebeck-score.courseplan.toml: 9 controls, 25 points, 15 minutes, fromscore draft, with its transcript, check output, IOF XML, CSV list and evaluation sheet in English and German. A draft at 45 minutes fails because the free codes run out (30 controls at the highest value 5, 54 at 9), not because the map is small; the README says so. score suggesttakes--course NAME(tool parametercourse): start and finish of that score course. The editor's Suggest sends the selected course; before, it used the first start of the event, which can lie inside a building.- A failed draft whose control count is capped by the free codes says so (hint "raise the highest value or free codes") instead of blaming the map.
infonames controls, points and time limit of a score course. It used to say that the length was not supported.- Score courses in the exports. The IOF XML now holds score courses with the value of each control in
CourseControl/Score; time limit, penalty and mandatory links sit in anExtensionselement, because the schema has no place for them. Newexport --csv FILE --course NAMEwrites code, value and position of the controls, andexport --sheet FILE --course NAMEwrites an evaluation sheet with the time limit, the penalty, the rule that the faster runner wins on equal points and the rule that a runner who skips a link is disqualified (toolsexport_score_csv,export_score_sheet). The Purple Pen export still skips score courses and says so. The IOF XML has not been tried in OE (ADR 0070). - The editor draws score courses and has a Score panel: a suggestion with candidate markers and their values, "Accept" for a single candidate, a draft with a length report, "Apply" and a progress line while the program computes. Mandatory links appear as dashed lines between their controls. Candidates and drafts are not saved until you accept or apply them. A long run no longer stops the editor from noticing changes to the file (ADR 0069).
score suggest,score draftandscore length(toolsscore_suggest,score_draft,score_length): ranked control sites from the map, a draft of a score course whose length meets the bands of both runner profiles, and the length analysis of an existing course. Onlydraft --applywrites; it creates the course or replaces the controls of a score course, and the editor can undo it. A draft that misses a band exits with 1 and names each miss and a hint. Sites stay 4 m away from forbidden routes (ADR 0068).- Editing score courses on the command line, over MCP and in the editor's operations:
course add --kind score --time-limit N,score controls,score value,score link add|remove,score settingsandscore course set, with the toolsadd_score_course,set_score_controls,set_control_value,add_score_link,remove_score_link,set_score_settingsandset_score_course_details. The code decides a control's value, so a different value is refused. Without a penalty a score course stores none and uses the default (the highest control value, at leastvalue_max);--no-penaltygoes back to it. Recoding a control gives it the value of its new code.rename,remove, classes and print scale work on score courses; legs, bends and numbers stay with individual courses (ADR 0067). - Score courses: the project has a
[score]table with the value range, runner profiles and seed, and a score course can name mandatory links between two controls. Loading a project refuses links to unknown controls, duplicate links and branching chains (ADR 0060). checknow checks score courses: value in the code, codes, ring and order of the controls, length, time limit, penalty, mandatory links and mix. Team and relay courses stay skipped (ADR 0066).
Changed
- Score fixes before the first release: a suggested control always has a route in the tours, a length report never says "inf percent", the speed table treats forest 405 as good running (1.1) and the railway helper 515.1 as no wall, and
score suggestshows route choice and difficulty to two decimals and seconds to one.score settings --value-maxrefuses a value below that of an existing control. - Score fixes before the first release: a suggested control always has a route in the tours, a length report never says "inf percent", the speed table treats forest 405 as good running (1.1) and the railway helper 515.1 as no wall, and
score suggestshows route choice and difficulty to two decimals and seconds to one.score settings --value-maxrefuses a value below that of an existing control. - The tool descriptions of
checkandexport_iof_xmlno longer say that score courses are not checked or written. README, manual, glossary, design and AGENTS.md describe score courses as built. - Internal: the map kind, the sidecar reader and the object class now come from
freemapper-corev0.19.0 (freemapper-contract) instead of local copies. Names and output stay the same.
[0.12.0] - 2026-11-11
Added
list controls|courses|specials|classeslist the details of a project as a table, andmap extentandmap near --at X,Y --radius MMreport the map extent and the map objects near a point. With--jsoneach prints the answer of the matching MCP tool unchanged (ADR 0059).event start-window --noneremoves the start window, asset_start_windowwithno_start_windowdoes.
Changed
AGENTS.mdputs the command line first and MCP second. An assistant that can run commands needs fewer turns; MCP stays for assistants without a shell, such as Claude Desktop. The command line section points to--help, names output and exit codes, says thateditblocks, and lists what the command line cannot do compared with the MCP tools.
[0.11.1] - 2026-11-11
Changed
- freemapper-core 0.16.1. The shared placement cases use the renderer's number box (0.8083747 x height x digits) and pass without exceptions.
- At scales the standard does not foresee (a sprint map at 1:5000, for example), the course symbols keep their base size, as in freemapper-print (ADR 0058). The overlay, the print preview, the placement and the checks measure with the same factor; circles used to shrink to 4.8 mm there. The check notes on such maps that the course is only fit for training.
[0.11.0] - 2026-11-11
Added
- The editor has a print preview (ADR 0057). The "Print preview" button or the P key shows the selected course the way freemapper-print draws it. The server hands the course to the renderer (freemapper-core 0.16.0), which paints start, controls, finish, legs, special objects and numbers as purple layers into the map image. The overlay is hidden and nothing can be edited meanwhile: the tree, the properties, undo and redo and the keys are switched off. A test compares the overlay's circles, legs, marked legs, crossing points and number boxes with the renderer's, for sprint, schoolyard and forest sizes.
Fixed
- The number box is as wide as freemapper-print draws it (3.23 mm instead of 2.4 mm for one digit, ADR 0030). Stored number positions (direction and gap) are drawn about 0.4 mm differently, and the automatic placement, the checks and the positions in the Purple Pen and IOF XML exports use the new width. The example's Purple Pen file and the IOF XML are regenerated.
- The overlay's crossing point is the 3 mm symbol turned along the course, and a marked leg has the print's dashes. A map without a scale no longer gives infinite symbol sizes.
- The overlay draws start and finish at the IOF sizes and ends legs where the renderer ends them: at the start triangle's corner, at the outer finish circle, at the control circle, at the crossing point itself. Before, it cut every leg at both ends by the control circle's radius.
[0.10.0] - 2026-11-11
Added
AGENTS.md, instructions for AI agents: what the program can and cannot do, installing it from the release archives, registering it over MCP (every tool with its parameters), the command line, troubleshooting. It is part of every release archive. The READMEs and chapter 3 of the manual explain how an assistant that can run commands installs and registers the program.The editor:
freemapper-courseplan edit <project>shows the event in the browser (ADR 0042, 0045, 0046). On the left is the tree of courses, categories, controls and special objects. In the centre the view shows the selected course with circles, legs and numbers over the map; on the right are the properties. The status bar is at the bottom.The editor can place, move and delete controls, and undo and redo each step (ADR 0043, 0047). Click with the "Place control" tool to place one, drag a circle or change X and Y in the properties (a comma or a dot works) to move it, press Delete or Backspace to remove it. Arrow keys move the selected control by 0.1 mm, with Shift by 1 mm. Every change is saved at once through the same operations as the CLI and the MCP tools, so the checks and messages are the same. If the project file changed outside the editor, the editor refuses the change, shows a message and loads the current state. When a course visits the control, deleting it asks first. Ctrl/Cmd+Z undoes, Ctrl/Cmd+Shift+Z or Ctrl+Y redoes, and
?lists all shortcuts. The undo history holds up to 100 steps and at most 64 MiB of file copies. If another program wrote the file, the next undo or redo refuses and clears the history. Undo and redo also refuse when the page shows an older state than the file, for example in a second tab.The editor lists the map objects near a selected control in the properties.
The editor sets control descriptions (ADR 0051). The properties of a control list the columns C to H, each folded and showing its current symbol. Open a column to pick a symbol from a grid of icons with numbers and texts, by mouse, arrow keys or by typing a number such as
1.3. Direction symbols are offered with their direction (12.1N), column E also offers the symbols of column D, and "No symbol" empties a column. Each pick is one step for undo. The number symbols 9.1 to 9.4 are not offered, because the project file holds no value for them. Setting a description now refuses a symbol that does not fit its column, on the command line, in the MCP tools and in the editor alike; symbols already in a file stay as they are.The editor builds courses, categories and event settings (ADR 0048, 0049). "New course..." makes a course with the event's start and finish. In course mode a click on a control appends it, and a click on a leg and then on a control inserts it there. The stop list reorders with Alt+Up and Alt+Down. Climb, planned print scale, categories, rule category, expected starters, rule profile with overlays and start window are fields in the properties.
Legs get bends and tape in the editor: drag a leg's line to add a bend, drag a bend to move it, press Delete to remove it, and tick "Taped" for a taped leg. The course length follows each change.
Control numbers can be dragged to a new place. The editor stores direction and gap for the course, or on the control when "for all courses" is ticked. "Place numbers" places the automatic ones and leaves set numbers alone, unless "also replace placed numbers" is ticked. The new route
api/number-atturns a drop point into direction and gap.Special objects can be drawn in the editor (start, finish, first aid, water, marked route, forbidden route, out-of-bounds area, dangerous area), assigned to courses and deleted. Lines and areas end with Enter or a double click. The editor has no move for special objects; delete and redraw.
A control gets a new code and a course a new name:
control code <project> <from> <to>,course rename <project> <from> <to>, the MCP toolsset_control_codeandrename_course, and the "Code" and "Name" fields in the editor's properties (ADR 0024). Courses, special objects and accepted findings that name the control or course follow. A code or name that is taken, empty, padded with spaces or too long is refused and the file stays as it was.Control codes may have at most 64 characters, course and class names and the notes of accepted findings at most 200. Longer texts are refused with a message, in the editor, on the command line and by the MCP tools alike; existing project files stay valid.
The editor shows the findings of the check (ADR 0052). Half a second after each change it runs the same check as
check. The status bar counts errors, warnings and hints; a drawer lists open and accepted findings and the rules that did not run. The properties list the findings of the selection, and the map marks them with a ring or a dashed line and a sign per severity. A gap outside the rule range gets a warning sign at the number box. "Accept..." stores an exception with a note of up to 200 characters; "Remove acceptance" brings the finding back. Each is one step for undo.The Event menu exports and imports (ADR 0053). "Export IOF XML" downloads the file, "Export to Purple Pen" writes the linked
.ppenor downloads a new one, and "Import Purple Pen file" reads an upload. The browser names no file; only the filesources.ppennames is written. Importing into a project that already has content needs "Replace". Writing to the linked file and importing clear the undo history. An empty event now shows correctly in the page.The Event menu syncs with Purple Pen (ADR 0054). A warning asks you to close the event in Purple Pen first; it can be silenced for the session. The panel lists the changes and the conflicts with the values from the base copy, courseplan and Purple Pen. You choose a side for each conflict, or "All courseplan" or "All Purple Pen" to sync at once. A sync that wrote files clears the undo history; if Purple Pen or another program saves a file during the sync, nothing is written and the editor loads the current state.
The editor shows the real map (ADR 0056, 0046). The shared renderer freemapper-render draws it as SVG, and the course overlay lies on top. The page asks for the visible section 150 ms after the view stops moving; the server keeps the page per file state and the last section per view. A map larger than one A3 sheet at map scale is drawn clipped to the middle part, and the status bar says so. If the renderer leaves out or shortens objects, the status bar says the map area is incomplete. A section too large to draw is refused with a message, and the overlay stays. Not built yet: the print preview and the test that compares the overlay with the print overprint; both wait for freemapper-print's course layer.
Changed
- Saving a project file now refuses when the file changed since the command read it (CLI, MCP tools and editor alike). Nothing is written, and the CLI exits with code 1 and prints
<file> changed since it was read; nothing was written. Run the command again. This closes the lost-update limit of the sync (ADR 0044, 0040). - The shared building blocks come from freemapper-core v0.14.1. The editor draws the map with
freemapper-renderand uses its list of description symbol ids (description_symbols::ids) and the drawings and texts of the symbols (render); the rest of the program does not change. The lock file pinscrc32fast1.5.3 andflate21.1.10 because newer releases were younger than two weeks (ADR 0006, 0056). - Tests: every test run now reads four maps from freemapper (schoolyard 1:1000, sprint 1:4000, a map from freemapper 0.9.0 and one with an unknown map kind) and compares map kind, title, sheet objects, base area and map extent with the expected values from
freemapper-contract(ADR 0041). If freemapper changes a tag or a key offreemapper.toml, these tests fail.
Fixed
- The source check (clippy) failed on Windows because an error type of
syncwas too large there. The program behaves as before.
[0.9.0] - 2026-11-10
Added
syncmerges the project with its linked.ppenin both directions (ADR 0040). It compares, field by field, the base copyX.courseplan.ppen-base(belongs in git;syncremoves the map'sabsolute-pathfrom it), the file now and the project now. If both sides changed the same field differently, or one side deletes what the other edited,synclists both values and writes nothing.--take ours,--take theirsor--take KEY=SIDEpick a side,--dry-runwrites nothing, and--jsonprints the result for machines. The next run finishes an interrupted sync. The sync refuses files from an unknown Purple Pen version. Classes, accepted findings and team, relay and score courses stay in the project.- New MCP tools
sync_ppenandresolve_sync_conflict. - The project file has an optional
[sync]table with the link, a hash of the base copy and stored decisions. The format stays 1.
[0.8.0] - 2026-11-10
Added
- The IOF XML export writes latitude and longitude (
Position, six decimals) for start, controls and finish when the map is georeferenced (ADR 0039). Without usable georeferencing the file keeps map positions only, andexportsays so. The MCP toolexport_iof_xmlalso returnsgeo_positions.
[0.7.5] - 2026-11-10
Changed
- freemapper-core 0.6.1: automatically placed numbers store their direction in clean tenths of a degree again (ADR 0036). The rounding workaround in courseplan is gone.
[0.7.4] - 2026-11-10
Fixed
- The Purple Pen export writes the variant
singleinto the right box. The same feature in columns D and E over a crossing or junction in column F now comes out assinglein column F. - The export leaves out description symbols that the standard of the file does not have, such as 2.10 in a 2004 file, and names each symbol it left out (ADR 0037).
- The export normalises the directions
0.1and0.2. It writes plain 9.1 and 9.3 as9.1highand9.3highand says so in a note.
Changed
- Number placement and the description symbol tables now come from freemapper-core 0.6.0, the same code freemapper-print uses. Positions change only in two edge cases: a bend closer to the circle than one line width, and a rule minimum with three decimals. In the
luebeck-sprintexample no number moves (ADR 0036, ADR 0037).
[0.7.3] - 2026-11-10
Fixed
- The scale bar and the base area that freemapper 0.9.0 writes into the map (
freemapper:source=scale-barandbase) no longer count as map objects and no longer move the map edge. Themap_edgecheck finds controls at the south edge again.
Changed
- The map kind comes first from
map_kindin thefreemapper.tomlnext to the map, then from the symbol set name (case does not matter) and the scale, as freemapper-print decides it. An unknown value or a broken file is skipped, not reported (ADR 0038).
[0.7.2] - 2026-11-10
Fixed
- Purple Pen 4.0 crashed while opening a
.ppenwritten byexport --ppen: a finish without a description symbol broke the description view. The export now gives every finish, crossing point and map issue point Purple Pen's own symbol (14.3, 13.3, 13.6), and an export onto an older file repairs the missing ones. freemapper-core 0.4.2 checks the output against the loader's requirements in more depth. The exampleluebeck-sprint.ppenis regenerated.
[0.7.1] - 2026-11-10
Fixed
export --ppenwrote courses that Purple Pen 4.0 refused to open:<options>lacked the required attributedescription-kind. Files written by 0.7.0 load again after the next export onto them. freemapper-core 0.4.1 checks everything the writer produces against the attributes Purple Pen's loader requires. The exampleluebeck-sprint.ppenis regenerated.
[0.7.0] - 2026-11-10
Added
freemapper-courseplan export <project> --ppen <file> [--new]writes the individual courses as a Purple Pen event. It changes an existing file in place and leaves alone whatever the project does not know (gaps in circles and legs, map exchange, column F text, course length);--newwrites the file from scratch. The export never deletes: what is in the file but not in the project stays and is reported as kept. What it writes differently or not at all (team, relay and score courses, symbols with no Purple Pen counterpart, partial flagging after changed bends) is named in a note. A newly created file is recorded in the project assources.ppenwhen none is set yet; for that file the Purple Pen ids are stored in the project. The AI assistant has the toolexport_ppen. The writer comes from freemapper-core v0.4.0 (freemapper-course). Not yet opened in Purple Pen 4.0 (ADR 0035).- The example
luebeck-sprint.ppen; a test compares it with a fresh export, another reads it back and exports onto it. - A test reads the IOF XML export (the Lübeck example and an event with a crossing point) back through the IOF reader of freemapper-course and compares controls, positions, courses, order of stops and number positions.
Changed
- The documentation now states what is verified: the export passes the IOF schema and reads back. An import into OE, Purple Pen or Condes has not been tested, because nobody on the project has OE (ADR 0033, addendum).
[0.6.0] - 2026-11-09
Added
freemapper-courseplan init <map> --ppen <file>andimport <project> --ppen <file> [--replace]take over a Purple Pen event: controls, descriptions (renumbered to the 2024 standard), individual courses, bends, marked routes, number positions and special objects. The AI assistant has the toolimport_ppen. What the model cannot hold is named in a note. Without--replace,importrefuses to overwrite a project that has content (ADR 0034).- Controls and courses remember their Purple Pen id (
ppen_id) for the later sync.
Changed
- The map is now read by
freemapper-omapand the.ppenbyfreemapper-coursefrom freemapper-core 0.3.0, instead of a local copy.freemapper-textandfreemapper-rulesare on the same tag. - A forbidden route may consist of one point, as Purple Pen stores it.
[0.5.0] - 2026-11-09
Added
freemapper-courseplan export <project> --iof-xml <file>writes the individual courses as IOF XML 3.0 (CourseData) for scoring software. The AI assistant has the toolexport_iof_xmlfor it. Team, relay and score courses are skipped and named. Geo positions and taped legs are missing for now (ADR 0033). The output is checked against the IOF schema; an import into OE is still to be tested. An example is inexamples/luebeck-sprint/course-data.xml.
Changed
- Program texts now come from
freemapper-text(freemapper-core 0.1.0) instead of a copy of the code in this repository. Nothing changes for users. freemapper-rules(freemapper-core 0.2.0) now reads the rule profiles. The loader also checks value ranges by unit; the embedded profiles are unchanged.
[0.4.0] - 2026-11-09
Added
checkwarns when the gap between a control number and its circle is outside 0.5 to 5 mm (wkb2026.number.gap, valuesmin_gap_mmandmax_gap_mm). No standard gives a value; the user set the limits (ADR 0032). Stored numbers stay where they are; old gaps above 5 mm are now reported.
Changed
- Automatic placement tries gaps from the minimum of the rule profile upward in steps of 0.5 mm, up to the maximum. A larger gap in the preferred direction beats another direction at a smaller gap. Without an applicable rule it stays at 0.5 mm. Newly placed numbers can therefore stand farther out.
[0.3.0] - 2026-11-09
Added
checkhints at almost straight angles at controls: when the legs at a control form an angle above 170°, it gives a hint (wkb2026.leg.straight_angle_hint). Crossing points are left out.
Changed
- Automatic placement puts a control number behind the circle, in line with the leg that leads to the control. When the next leg runs along that line (an almost straight angle), the number goes outside, at the tip of the angle. The number covers no point or line symbol of the map as long as a free place is left; it may cover areas and magnetic north lines (symbol 601). Legs, circles and other numbers weigh more than map symbols. If the preferred place is taken, placement picks the free place that deviates least, in steps of 15°; the direction is rounded to 0.1°. Newly placed numbers therefore often stand differently than before (ADR 0031). freemapper-print builds the same rules into its shared placement module.
- The gap of a control number (
--gap,gap_mm) now starts at the outer edge of the circle line and means the shortest distance to the number box, in every direction. It used to start at the centre of the circle line, and on the diagonals the number stood farther away than given. freemapper-print computes it the same way from its M3 on. Stored number placements stay valid. Straight above, below or beside the circle a number moves 0.175 mm outwards; on the diagonals it moves up to 0.6 mm closer.
[0.2.0] - 2026-11-09
Added
check <PROJECT>checks the individual courses against the rule profile of the event and its overlays. The findings are grouped by course, each with severity, place, explanation, rule id and source, followed by a summary.--allalso lists the accepted findings with their notes and the skipped rules with the reason. A rule whose filters (discipline, map kind, print scale) do not fit counts as skipped and is reported as such. Team, relay and score courses are not checked yet. The exit code is 0 without an error finding, 1 with one or when the check is refused, and 2 for a wrong call.finding accept <PROJECT> <RULE> --note TEXTaccepts a finding as an intended exception. Give its place with--controls 31,32,--leg A:3or--course A, or none of them for a finding of the whole event. Accepting the same finding again replaces the note.finding removetakes it back and refuses an accepted finding that does not exist.event profile <PROJECT> <BASE> [--overlay ID …]sets the rule profile and the overlays after loading them.infoshows both.- MCP: the tools
check,accept_finding,remove_accepted_findingandset_rule_profile.checkreturns findings, accepted findings and skipped rules as JSON, with machine fields and the text in the server's language.event_infonames the rule profile and the overlays. checknow measures distances on the map: controls closer together than the minimum distance (the start and crossing points count), controls at similar features too close together, controls, start and finish near the map edge, legs that pass through another control of the same course, and straight legs through an out-of-bounds area, as a warning that the best route may lead through it. Taped legs may lead through out-of-bounds areas. Buildings, walls, fences and water wait for the later route analysis. Each finding gives the measured value and the limit, such as "control 31 and control 32 are 12 m apart, at least 15 m required". A pair or a control is reported once, even when several courses use it. Start and finish can be the place of a finding:finding accept … --controls 31,S1.finding acceptrefuses a place where the rule never reports a finding, such as--course Afor the minimum distance, which always concerns two controls.checknow looks at the shape of legs. Where the incoming and the outgoing leg meet at a control or crossing point at an acute angle, it reports a warning below 45° and a hint below 90°, but only the stricter of the two. Two legs of one course that run in opposite directions at most 10 m apart for 30 m or more give a warning that names both legs. The first 10 m around a control where both meet do not count, also at the centre of a butterfly loop. Accept such a finding with both legs:finding accept … --leg A:2 --leg A:5.checknow looks at control codes: a numeric code outside 31 to 599 is an error and a code that is no number a warning; both codes of a forbidden pair (66 and 99, 68 and 89, 86 and 98) used anywhere in the event are an error, also in different courses; two consecutive controls whose codes look alike upside down or differ only by 6 and 9 give a warning. Crossing points, start and finish have no code and are left out, and a crossing point between two controls does not break their pair. A control at a point symbol that occurs again within its circle gives a warning when column C of the control description is empty; lines and areas are not compared. Each code or pair is reported once, even when several courses use it.checknow looks at the start. Courses use the same start minute only at the same start. For each start, the expected starters of the classes on its courses, divided by the start window, give the number of different first controls needed per minute; if the courses have fewer, a warning at the start names the first controls shared by several courses. Without a start window the rule is skipped, and a start with a class that has no expected starters is skipped with the class names. As a hint, two courses at one start with different first controls whose first legs differ in direction by less than 30° are reported once per pair of first controls. The direction runs to the first start bend, or else to the first stop.checknow looks at the course tables. At long, middle and ultralong distances, a course whose length is outside the range of its category row, as a percentage of the HE course, gives a warning (WKB Anlage 5); without an HE course, or with one of length 0, the rule is skipped, and with two the first serves as the reference and a hint says so. A course with easy and hard categories (levels 1 and 2 against 3 to 5) is an error that names both sides. With the NOR overlay, the performance kilometres (length plus climb × 10 / 1000) outside the target of the category row ±6 % give a warning, shown with up to two decimals; a course without climb is skipped. A category in no row of the table skips the course for that category, in the length table and in the NOR table alike. A course whose categories hit several rows gets one finding that names every violated row. A class without rule category skips the course and is named.- User manual, chapter 8 "Checking: reading findings, giving reasons for exceptions": how to read a line of
check, what severity, exit code and skipped rules mean, how to accept and take back exceptions, and how to choose the profile and overlays. The Lübeck example shows the output ofcheckand one accepted hint incheck.en.txtandcheck.de.txt; the project file holds it. event start-window <PROJECT> <MINUTES>sets the start window that the start capacity check needs; 0 is refused.class set <PROJECT> <CLASS> [--rule-category C] [--starters N]changes the rule category and the expected starters of a class, and--no-rule-categoryand--no-startersremove them. Over MCP the tools areset_start_windowandset_class. When one of these values is missing,check --allnames the command that sets it in the skipped rule.check --alllists under "not matched" the accepted findings that match no finding any more, for example because a control or a course was removed, with their notes and a pointer tofinding remove. The summary counts them; they do not change the exit code. MCPcheckreturns them inunmatched.- A rule that an overlay switches off is listed among the skipped rules by
check --all, with a reason that names the overlay: "rule profile nor-bb-2021 switches the rule off". check --allprints the note of each finding's rule in the chosen language below it. A value the program proposes because no source gives a number names its source as "proposal of freemapper-courseplan".
Changed
- Output names a leg by its ends: "leg 36–37", "leg start–31", "leg 37–finish", and two legs as "legs 31–32 and 35–36". As places, start and finish read "start S1" and "finish F1". Within a course the course comes first, then its legs in running order. Input stays
--leg A:6, the leg from position 6 of the course with crossing points counted; the help says so. - An accepted finding on a leg stores the leg's ends (
from,to). When the leg later runs between other controls,checkreports the finding again.finding remove --leg A:6removes the exception for the leg as it runs now; if there is none, it removes the older ones at that position. - Texts: limits read "at least 15 m required", and a leg over a control "the leg runs over control 31" instead of "passes 0 m from control 31". Start and table findings no longer repeat their place in the text. An empty note is refused with a message of its own.
[0.1.0] - 2026-11-09
Added
- First example: a sprint course in Lübeck's old town in
examples/luebeck-sprint, with project file, commands and a sketch that the README shows too. - MCP server:
freemapper-courseplan mcpserves course setting to AI assistants over MCP (stdin and stdout). 25 tools read and change a project:event_info,list_controls|courses|specials|classes,add_control,move_control,remove_control,describe_control,set_control_number,add_special,remove_special,set_special_courses,add_class,remove_class,add_course,remove_course,set_course_controls,set_leg,set_course_details,place_numbers,set_course_number,map_features_near,map_extentandinit_event. Positions are objects withxandyin map millimetres. A control code may be a string or a whole number.set_course_controlsandremove_controllist the legs that lost their bends incleared_legs. Each call loads the project file, applies one change and saves only on success. The server refuses relative paths. Descriptions, hints for the assistant and error texts come from the catalog in the language of--lang,FREEMAPPER_LANGor the system; tool and field names stay English. The manual covers this in chapter 3. - Commands for courses:
course add,course controls,course leg,course set,course number,course numbersandcourse remove. Adding and changing work for individual courses only.course leg --after Ncounts from 1 as on the map, crossing points included. Give bends with a repeated--bend X,Y; without--bendthe leg loses its bends, and without--tapedit loses the marked-route flag. Whencourse controlsorcontrol removemakes a leg run to another stop, the leg loses its bends and its taped flag, and the command prints a line for it, so old bends cannot change the length unseen.course number --after Nsets or clears the number placement of one control in a course.course numbersplaces the other control numbers clear of legs and circles; it runs only when you call it, and it keeps central and course numbers. Number directions are brought into 0 to 360 degrees, and a gap must be more than 0 and at most 10 print mm. Output uses the decimal comma in German, while input keeps the dot. infoprints one line per course with the number of controls, the length in kilometres and the climb. Crossing points do not count as controls, here and in the line with the totals, which counts them apart. The length follows WKB B 7.2.5: straight lines, detours as bends, marked routes along their bends. Team, relay and score courses show no length yet.- Commands for controls, special objects and classes:
control add|move|remove|describe|number,special add|courses|removeandclass add|remove. Each change is checked first and saved after; if the check fails, the file stays as it was. Points are in map millimetres with a dot as decimal separator. The program refuses points outside the map.special courses <PROJECT> <ID> (A,B | --all)assigns a special object to courses. Removing the only course of a special object is refused with a message that says what to do. Codes, ids, names and the items of comma-separated lists lose surrounding spaces, so--controls "31, 32"works.help control addshows the help of a subcommand. German help shows the possible values in German words, and an invalid value lists the valid ones. - Library: course legs, course length and control numbers. The program computes legs with bends, the length of a course at a map scale and the numbers of the controls (crossing points get none). It places a number beside the circle without touching legs, circles or other numbers. Overprint sizes follow the map kind and stay the same size on the map for enlarged prints.
- Map index: the program looks up each map symbol once per symbol instead of once per object. This speeds up checking points against the map; results are unchanged.
- Data model: crossing points, ids for special objects, bends and marked-route flag for the leg from the start, and a print area per course.
- The project file refuses empty or space-padded control codes, special ids, course and class names, a control code that is also a special id, a course listed twice in one special object, lines with fewer than two points and areas with fewer than three.
- German program texts use the terms of the glossary: Pflichtstrecke, abgeflaggte Strecke, Querungsstelle, Gefahrenbereich, Erste-Hilfe-Stelle, Verpflegungsstelle, verbotene Route, Nummernlage, Strecke and Knickpunkt.
- Program texts come from Fluent files, one per language (
crates/freemapper-courseplan/i18n/). German and English ship now.--lang, thenFREEMAPPER_LANG, then the system language picks the language; help and messages follow it. Adding a language takes one new folder. Placeholders in the usage line and rare command-line parse errors stay English. - Project file
*.courseplan.tomlwith individual, team, relay and score courses. The program checks it on load and rejects unknown keys, so a typo shows up. When it saves the file, your comments and layout stay as they were. freemapper-courseplan init <map>starts a project from a sprint or schoolyard map.--titleand--projectset the title and the project file. Forest maps are refused for now.freemapper-courseplan info <project>prints the title, map, map kind, controls, courses by kind and classes.- Reading OOM maps (
.omap). The program refuses a map in the old binary format and tells you to save it again in OOM. - Project skeleton modelled on freemapper-print: Rust workspace with a data model, a library and the
freemapper-courseplanprogram, CI for Linux, macOS and Windows, release workflow. - Draft design, glossary, the first architecture decisions and a collection of course-setting rules from German and international rule books.