From 719d61982953caf61c0df7776347e13105a2e8b2 Mon Sep 17 00:00:00 2001
From: Tor Andersson <tor@ccxvii.net>
Date: Sat, 22 Oct 2022 12:53:57 +0200
Subject: Add asset rendering scripts.

---
 tools/boxes.svg          | 688 +++++++++++++++++++++++++++++++++++++++++++++
 tools/build_cards1.sh    |  86 ++++++
 tools/build_cards2.sh    |  10 +
 tools/build_counters1.sh | 117 ++++++++
 tools/build_counters2.sh | 119 ++++++++
 tools/build_counters3.sh |   5 +
 tools/build_map.sh       |  16 ++
 tools/build_mats.sh      |  35 +++
 tools/build_screen.sh    |  10 +
 tools/build_stickers.sh  |  37 +++
 tools/colors.mjs         | 108 ++++++++
 tools/genboxes.py        |  65 +++++
 tools/gencss.js          |   5 +
 tools/gencyl.js          |  51 ++++
 tools/gendata.js         | 705 +++++++++++++++++++++++++++++++++++++++++++++++
 tools/scale.sh           |  18 ++
 tools/svgo.config.js     |  10 +
 17 files changed, 2085 insertions(+)
 create mode 100644 tools/boxes.svg
 create mode 100644 tools/build_cards1.sh
 create mode 100644 tools/build_cards2.sh
 create mode 100644 tools/build_counters1.sh
 create mode 100644 tools/build_counters2.sh
 create mode 100644 tools/build_counters3.sh
 create mode 100644 tools/build_map.sh
 create mode 100644 tools/build_mats.sh
 create mode 100644 tools/build_screen.sh
 create mode 100644 tools/build_stickers.sh
 create mode 100644 tools/colors.mjs
 create mode 100644 tools/genboxes.py
 create mode 100644 tools/gencss.js
 create mode 100644 tools/gencyl.js
 create mode 100644 tools/gendata.js
 create mode 100644 tools/scale.sh
 create mode 100644 tools/svgo.config.js

(limited to 'tools')

diff --git a/tools/boxes.svg b/tools/boxes.svg
new file mode 100644
index 0000000..16b8e71
--- /dev/null
+++ b/tools/boxes.svg
@@ -0,0 +1,688 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="svg2"
+   width="5100"
+   height="6600"
+   viewBox="0 0 5100 6600"
+   sodipodi:docname="boxes.svg"
+   inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="640"
+     inkscape:window-height="480"
+     id="namedview8"
+     showgrid="false"
+     inkscape:zoom="0.4218644"
+     inkscape:cx="2021.1195"
+     inkscape:cy="4535.2592"
+     inkscape:current-layer="svg2"
+     inkscape:document-rotation="0" />
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <image
+     sodipodi:absref="/home/tor/src/rally/public/nevsky/tools/map300.png"
+     xlink:href="map300.png"
+     id="image12"
+     preserveAspectRatio="none"
+     height="6600"
+     width="5100"
+     style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+     sodipodi:insensitive="true"
+     x="0"
+     y="0" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+     id="rect838"
+     width="303.94965"
+     height="60.271549"
+     x="1448.1355"
+     y="3624.7134"
+     ry="0.381295"
+     inkscape:label="Wesenberg" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+     id="rect840"
+     width="184.05482"
+     height="60.919552"
+     x="1012.6255"
+     y="4583.2241"
+     ry="0.381295"
+     inkscape:label="Fellin" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+     id="rect844"
+     width="250.44016"
+     height="60.948803"
+     x="1377.8793"
+     y="5103.0913"
+     ry="0.61047864"
+     inkscape:label="Odenpäh" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+     id="rect848"
+     width="184.90872"
+     height="60.490585"
+     x="1503.5576"
+     y="5612.335"
+     ry="0.610479"
+     inkscape:label="Adsel" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-opacity:1"
+     id="rect850"
+     width="231.68872"
+     height="60.27153"
+     x="909.25671"
+     y="5759.166"
+     ry="0.610479"
+     inkscape:label="Wenden" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect854"
+     width="147.6003"
+     height="62.215714"
+     x="2666.8506"
+     y="3294.6785"
+     ry="0.610479"
+     inkscape:label="Luga" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect856"
+     width="284.58069"
+     height="62.094578"
+     x="2904.4648"
+     y="3521.9727"
+     ry="0.610479"
+     inkscape:label="Kaibolovo" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect858"
+     width="240.76186"
+     height="61.891674"
+     x="3132.8203"
+     y="3159.7158"
+     ry="0.610479"
+     inkscape:label="Koporye" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect860"
+     width="147.92438"
+     height="61.891689"
+     x="3924.1265"
+     y="2933.6978"
+     ry="0.610479"
+     inkscape:label="Neva" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect864"
+     width="230.96405"
+     height="63.011024"
+     x="4591.4414"
+     y="3783.0674"
+     ry="0.610479"
+     inkscape:label="Volkhov" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect870"
+     width="186.64714"
+     height="62.539753"
+     x="4242.9819"
+     y="5580.9438"
+     ry="0.610479"
+     inkscape:label="Lovat" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect872"
+     width="240.92387"
+     height="62.539753"
+     x="3514.5398"
+     y="5466.5576"
+     ry="0.610479"
+     inkscape:label="Porkhov" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00dfff;stroke-opacity:1"
+     id="rect876"
+     width="240.81668"
+     height="62.323635"
+     x="2239.7556"
+     y="5430.8633"
+     ry="0.610479"
+     inkscape:label="Izborsk" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect878"
+     width="351.25955"
+     height="60.595512"
+     x="3706.0474"
+     y="6347.2988"
+     ry="0.610479"
+     inkscape:label="Velikiye Luki" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect880"
+     width="121.21022"
+     height="32.307468"
+     x="3936.2415"
+     y="4101.6738"
+     ry="0.610479"
+     inkscape:label="Tesovo" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect886"
+     width="127.99587"
+     height="30.459776"
+     x="3500.9299"
+     y="4176.23"
+     ry="0.610479"
+     inkscape:label="Zheltsy" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect888"
+     width="104.25461"
+     height="31.390947"
+     x="3787.9937"
+     y="4540.689"
+     ry="0.610479"
+     inkscape:label="Sablia" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect890"
+     width="88.138931"
+     height="30.135736"
+     x="2427.0608"
+     y="4148.6865"
+     ry="0.610479"
+     inkscape:label="Gdov" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect892"
+     width="161.37201"
+     height="31.107857"
+     x="3152.9109"
+     y="5214.4546"
+     ry="0.610479"
+     inkscape:label="Dubrovno" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect894"
+     width="115.3583"
+     height="30.459776"
+     x="2745.9165"
+     y="5716.7168"
+     ry="0.610479"
+     inkscape:label="Ostrov" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect896"
+     width="145.81808"
+     height="30.459776"
+     x="2045.9896"
+     y="6307.4419"
+     ry="0.610479"
+     inkscape:label="Rositten" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect898"
+     width="174.98169"
+     height="30.135736"
+     x="1876.8407"
+     y="5389.436"
+     ry="0.610479"
+     inkscape:label="Kirrumpäh" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-opacity:1"
+     id="rect900"
+     width="118.43668"
+     height="29.811697"
+     x="516.68207"
+     y="4580.3081"
+     ry="0.610479"
+     inkscape:label="Pernau" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#efc300;stroke-opacity:1"
+     id="rect902"
+     width="123.13527"
+     height="30.783817"
+     x="2371.002"
+     y="3548.8879"
+     ry="0.610479"
+     inkscape:label="Narwia" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904"
+     cx="2099.4792"
+     cy="3729.7024"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Wierland" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#00ff34;stroke-width:0.938948;stroke-opacity:1"
+     id="rect892-8"
+     width="141.99065"
+     height="31.168909"
+     x="292.25372"
+     y="3796.748"
+     ry="0.61167711"
+     inkscape:label="Warbola" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-3"
+     cx="1635.0789"
+     cy="4162.5376"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Waiga"
+     sodipodi:insensitive="true" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-6"
+     cx="1163.9856"
+     cy="3996.0444"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Jerwen" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7"
+     cx="667.22955"
+     cy="4033.4651"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Harrien" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-5"
+     cx="716.7218"
+     cy="4819.083"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Sackala" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-35"
+     cx="608.5719"
+     cy="5275.5122"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Metsepole" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-62"
+     cx="2148.3323"
+     cy="5827.2598"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Lettgallia" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-9"
+     cx="1640.578"
+     cy="5983.0688"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Tolowa" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-1"
+     cx="2056.6799"
+     cy="4989.5566"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Ugaunia" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-4"
+     cx="1130.2521"
+     cy="3460.1011"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Revala" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-3"
+     cx="3129.4712"
+     cy="6140.2524"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Velikaya River" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-1"
+     cx="3399.3877"
+     cy="5830.9258"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Sorot River" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-49"
+     cx="3754.0825"
+     cy="4914.4019"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Shelon River" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-2"
+     cx="2881.5515"
+     cy="4635.7783"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Zhelcha River" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-0"
+     cx="2928.7524"
+     cy="4283.8335"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Plyussa River" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-6"
+     cx="3919.5151"
+     cy="3689.009"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Ingria" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-8"
+     cx="3587.7727"
+     cy="3394.645"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Vod"
+     sodipodi:insensitive="true" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-9"
+     cx="4173.6377"
+     cy="3372.6101"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Izhora" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-26"
+     cx="3932.5515"
+     cy="2457.5205"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Karelia" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1468"
+     width="237.84526"
+     height="89.759171"
+     x="4618.8687"
+     y="2816.719"
+     ry="0.610479"
+     inkscape:label="Ladoga" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1470"
+     width="333.15622"
+     height="112.27441"
+     x="4318.2031"
+     y="4315.4531"
+     ry="0.610479"
+     inkscape:label="Novgorod" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1472"
+     width="205.44145"
+     height="91.54155"
+     x="4328.8525"
+     y="5165.6865"
+     ry="0.610479"
+     inkscape:label="Rusa" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1474"
+     width="205.44148"
+     height="91.217155"
+     x="272.84183"
+     y="6230.9683"
+     ry="0.610479"
+     inkscape:label="Riga"
+     sodipodi:insensitive="true" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1476"
+     width="205.07263"
+     height="90.965332"
+     x="2679.9163"
+     y="5263.1392"
+     ry="0.610479"
+     inkscape:label="Pskov" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1478"
+     width="252.50232"
+     height="90.965202"
+     x="1625.2262"
+     y="4588.5771"
+     ry="0.610479"
+     inkscape:label="Dorpat" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1480"
+     width="205.2198"
+     height="90.645508"
+     x="107.90538"
+     y="4265.9893"
+     ry="0.610479"
+     inkscape:label="Leal" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1482"
+     width="205.75963"
+     height="90.735939"
+     x="601.01062"
+     y="3563.9038"
+     ry="0.610479"
+     inkscape:label="Reval" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1506"
+     width="591.99841"
+     height="916.72931"
+     x="39.683407"
+     y="168.04262"
+     ry="0.610479"
+     inkscape:label="box1" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1508"
+     width="590.72522"
+     height="913.46924"
+     x="649.70056"
+     y="168.17685"
+     ry="0.610479"
+     inkscape:label="box2" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1510"
+     width="591.04926"
+     height="916.38562"
+     x="1313.0107"
+     y="167.20473"
+     ry="0.610479"
+     inkscape:label="box3" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1512"
+     width="589.75311"
+     height="916.38562"
+     x="1922.2063"
+     y="167.20473"
+     ry="0.610479"
+     inkscape:label="box4" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1514"
+     width="592.34546"
+     height="917.68176"
+     x="2587.1367"
+     y="167.20473"
+     ry="0.610479"
+     inkscape:label="box5" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1516"
+     width="588.45697"
+     height="916.38562"
+     x="3196.3323"
+     y="167.20473"
+     ry="0.610479"
+     inkscape:label="box6" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1518"
+     width="593.6416"
+     height="916.38562"
+     x="3858.6704"
+     y="167.20473"
+     ry="0.610479"
+     inkscape:label="box7" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1520"
+     width="591.04926"
+     height="917.68176"
+     x="4469.1621"
+     y="165.90857"
+     ry="0.610479"
+     inkscape:label="box8" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1522"
+     width="593.6416"
+     height="915.08942"
+     x="38.884819"
+     y="1118.5867"
+     ry="0.610479"
+     inkscape:label="box9" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1524"
+     width="217.2162"
+     height="215.38313"
+     x="172.3065"
+     y="183.30479"
+     ry="0.610479"
+     inkscape:label="Victory" />
+  <rect
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect1526"
+     width="216.29967"
+     height="213.55009"
+     x="398.68796"
+     y="184.22133"
+     ry="0.610479"
+     inkscape:label="Turn" />
+  <rect
+     style="fill:none;stroke:#000000"
+     id="rect74"
+     width="843.68152"
+     height="627.99255"
+     x="4192.9326"
+     y="5846.7227"
+     ry="0.610479"
+     inkscape:label="Novgorod Veche" />
+  <ellipse
+     style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.52793;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path904-7-36"
+     cx="2212.0798"
+     cy="4741.5249"
+     rx="100.02692"
+     ry="50.425739"
+     inkscape:label="Uzmen" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect895"
+     width="461.87738"
+     height="148.64993"
+     x="1499.7745"
+     y="4716.9902"
+     ry="0"
+     inkscape:label="way-crossroads" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect897"
+     width="175.19495"
+     height="350.38925"
+     x="1295.3806"
+     y="4525.8687"
+     inkscape:label="way-wirz" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect899"
+     width="220.32043"
+     height="464.53174"
+     x="2232.4082"
+     y="4196.7144"
+     inkscape:label="way-peipus-east" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect901"
+     width="361.00775"
+     height="228.28438"
+     x="2065.1765"
+     y="3835.7068"
+     ry="2.6544683"
+     inkscape:label="way-peipus-north" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect903"
+     width="217.66652"
+     height="520.27612"
+     x="1988.1968"
+     y="4140.9712"
+     ry="0"
+     inkscape:label="way-peipus-west" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect77"
+     width="431.35098"
+     height="84.279167"
+     x="4397.7905"
+     y="5836.5122"
+     inkscape:label="veche-label-top" />
+  <rect
+     style="fill:#008900;fill-opacity:0.288618;stroke:#000000"
+     id="rect79"
+     width="362.33572"
+     height="60.389202"
+     x="4426.3257"
+     y="6428.459"
+     inkscape:label="veche-label-bottom" />
+</svg>
diff --git a/tools/build_cards1.sh b/tools/build_cards1.sh
new file mode 100644
index 0000000..3459345
--- /dev/null
+++ b/tools/build_cards1.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# Extract, crop, and rename card images to create 300 dpi PNG versions.
+
+function extract_images {
+	echo "$1"
+	mutool extract -r "$1"
+	mkdir -p $2
+	mv image* $2
+	rm -f font*
+}
+
+extract_images "../HIRES/Nevsky_Arts of War Cards_42 Anverses.pdf" tmp/aow_front
+extract_images "../HIRES/Nevsky_Arts of War Cards_42 Backs.pdf" tmp/aow_back
+extract_images "../HIRES/Nevsky_Arts of War Cards_R1 and R2 Anverses_Corrected.pdf" tmp/aow_corr
+extract_images "../HIRES/Nevsky_Command Cards_42 Anverses.pdf" tmp/cc_front
+extract_images "../HIRES/Nevsky_Command Cards_42 Backs.pdf" tmp/cc_back
+
+# cards are 63.5 x 89.0 mm - 2.5 x 3.5 inches - 750 x 1050 pixels
+# rounded to multiple of 12 = 744 x 1044
+for IM in tmp/aow_*/*.png tmp/cc_*/*.png
+do
+	echo $IM
+	convert $IM -gravity Center -crop 744x1040+0+0 +repage ${IM/png/ppm}
+done
+
+mkdir -p cards300
+pnmtopng tmp/aow_back/image-0083.ppm	>cards300/aow_russian_back.png
+pnmtopng tmp/aow_back/image-0102.ppm	>cards300/aow_teutonic_back.png
+pnmtopng tmp/aow_corr/image-0004.ppm	>cards300/aow_russian_02.png
+pnmtopng tmp/aow_corr/image-0015.ppm	>cards300/aow_russian_01.png
+pnmtopng tmp/aow_front/image-0004.ppm	>cards300/aow_teutonic_02.png
+pnmtopng tmp/aow_front/image-0008.ppm	>cards300/aow_teutonic_03.png
+pnmtopng tmp/aow_front/image-0012.ppm	>cards300/aow_teutonic_04.png
+pnmtopng tmp/aow_front/image-0016.ppm	>cards300/aow_teutonic_05.png
+pnmtopng tmp/aow_front/image-0020.ppm	>cards300/aow_teutonic_06.png
+pnmtopng tmp/aow_front/image-0024.ppm	>cards300/aow_teutonic_07.png
+pnmtopng tmp/aow_front/image-0028.ppm	>cards300/aow_teutonic_08.png
+pnmtopng tmp/aow_front/image-0032.ppm	>cards300/aow_teutonic_09.png
+pnmtopng tmp/aow_front/image-0036.ppm	>cards300/aow_teutonic_10.png
+pnmtopng tmp/aow_front/image-0040.ppm	>cards300/aow_teutonic_11.png
+pnmtopng tmp/aow_front/image-0044.ppm	>cards300/aow_teutonic_12.png
+pnmtopng tmp/aow_front/image-0048.ppm	>cards300/aow_teutonic_13.png
+pnmtopng tmp/aow_front/image-0052.ppm	>cards300/aow_teutonic_14.png
+pnmtopng tmp/aow_front/image-0056.ppm	>cards300/aow_teutonic_15.png
+pnmtopng tmp/aow_front/image-0060.ppm	>cards300/aow_teutonic_16.png
+pnmtopng tmp/aow_front/image-0064.ppm	>cards300/aow_teutonic_17.png
+pnmtopng tmp/aow_front/image-0068.ppm	>cards300/aow_teutonic_18.png
+# pnmtopng tmp/aow_front/image-0078.ppm	>cards300/aow_russian_01.ppm # corrected
+# pnmtopng tmp/aow_front/image-0082.ppm	>cards300/aow_russian_02.ppm # corrected
+pnmtopng tmp/aow_front/image-0086.ppm	>cards300/aow_russian_03.png
+pnmtopng tmp/aow_front/image-0090.ppm	>cards300/aow_russian_04.png
+pnmtopng tmp/aow_front/image-0094.ppm	>cards300/aow_russian_05.png
+pnmtopng tmp/aow_front/image-0098.ppm	>cards300/aow_russian_06.png
+pnmtopng tmp/aow_front/image-0102.ppm	>cards300/aow_russian_07.png
+pnmtopng tmp/aow_front/image-0106.ppm	>cards300/aow_russian_08.png
+pnmtopng tmp/aow_front/image-0110.ppm	>cards300/aow_russian_09.png
+pnmtopng tmp/aow_front/image-0114.ppm	>cards300/aow_russian_10.png
+pnmtopng tmp/aow_front/image-0118.ppm	>cards300/aow_russian_11.png
+pnmtopng tmp/aow_front/image-0122.ppm	>cards300/aow_russian_12.png
+pnmtopng tmp/aow_front/image-0126.ppm	>cards300/aow_russian_13.png
+pnmtopng tmp/aow_front/image-0130.ppm	>cards300/aow_russian_14.png
+pnmtopng tmp/aow_front/image-0134.ppm	>cards300/aow_russian_15.png
+pnmtopng tmp/aow_front/image-0138.ppm	>cards300/aow_russian_16.png
+pnmtopng tmp/aow_front/image-0142.ppm	>cards300/aow_russian_17.png
+pnmtopng tmp/aow_front/image-0146.ppm	>cards300/aow_russian_18.png
+pnmtopng tmp/aow_front/image-0153.ppm	>cards300/aow_teutonic_none.png
+pnmtopng tmp/aow_front/image-0155.ppm	>cards300/aow_russian_none.png
+pnmtopng tmp/aow_front/image-0175.ppm	>cards300/aow_teutonic_01.png
+pnmtopng tmp/cc_back/image-0083.ppm	>cards300/cc_russian_back.png
+pnmtopng tmp/cc_back/image-0102.ppm	>cards300/cc_teutonic_back.png
+pnmtopng tmp/cc_front/image-0083.ppm	>cards300/cc_teutonic_andreas.png
+pnmtopng tmp/cc_front/image-0085.ppm	>cards300/cc_teutonic_pass.png
+pnmtopng tmp/cc_front/image-0087.ppm	>cards300/cc_teutonic_heinrich.png
+pnmtopng tmp/cc_front/image-0089.ppm	>cards300/cc_teutonic_rudolf.png
+pnmtopng tmp/cc_front/image-0091.ppm	>cards300/cc_teutonic_knud_and_abel.png
+pnmtopng tmp/cc_front/image-0093.ppm	>cards300/cc_teutonic_yaroslav.png
+pnmtopng tmp/cc_front/image-0095.ppm	>cards300/cc_russian_aleksandr.png
+pnmtopng tmp/cc_front/image-0097.ppm	>cards300/cc_russian_domash.png
+pnmtopng tmp/cc_front/image-0099.ppm	>cards300/cc_russian_vladislav.png
+pnmtopng tmp/cc_front/image-0101.ppm	>cards300/cc_russian_pass.png
+pnmtopng tmp/cc_front/image-0103.ppm	>cards300/cc_russian_karelians.png
+pnmtopng tmp/cc_front/image-0105.ppm	>cards300/cc_russian_andrey.png
+pnmtopng tmp/cc_front/image-0107.ppm	>cards300/cc_russian_gavrilo.png
+pnmtopng tmp/cc_front/image-0126.ppm	>cards300/cc_teutonic_hermann.png
+
+rm -rf tmp
diff --git a/tools/build_cards2.sh b/tools/build_cards2.sh
new file mode 100644
index 0000000..bcf4062
--- /dev/null
+++ b/tools/build_cards2.sh
@@ -0,0 +1,10 @@
+# 300dpi @ 744 x 1044
+
+mkdir -p cards75 cards150
+
+for IM in cards300/*.png
+do
+	echo $IM
+	convert $IM -colorspace RGB -resize 25% -colorspace sRGB ${IM/cards300/cards75}
+	convert $IM -colorspace RGB -resize 50% -colorspace sRGB ${IM/cards300/cards150}
+done
diff --git a/tools/build_counters1.sh b/tools/build_counters1.sh
new file mode 100644
index 0000000..a8573e2
--- /dev/null
+++ b/tools/build_counters1.sh
@@ -0,0 +1,117 @@
+# 3 counter sheet dies
+# 1 and 3 has square counters, only bottom row differs
+# 2 has rectangular tiles
+
+# circular (sheet 1 bottom) 210x210
+# large square 190x190 (188-190-ish)
+# rectangle 380x190 (188-190-ish)
+# small square 150x150
+
+mkdir -p tmp
+
+# large squares (top sheet 1 and 3)
+ROW=1
+for TOP in 340 586 833 1080 1326
+do
+	COL=1
+	RCOL=12
+	for LEFT in 304 551 797 1043 1290 1536 1874 2121 2367 2613 2860 3106
+	do
+		echo large square $ROW $COL
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_sq_large_1_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_sq_large_1_${ROW}_${RCOL}_b.ppm
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-3F-nf.ppm > tmp/cs_sq_large_3_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-3B-nf.ppm > tmp/cs_sq_large_3_${ROW}_${RCOL}_b.ppm
+		COL=$(expr $COL + 1)
+		RCOL=$(expr $RCOL - 1)
+	done
+	ROW=$(expr $ROW + 1)
+done
+
+# large squares (bottom sheet 2)
+ROW=1
+for TOP in 2075 2322
+do
+	COL=1
+	RCOL=12
+	for LEFT in 304 551 797 1043 1290 1536 1874 2121 2367 2613 2860 3106
+	do
+		echo large square $ROW $COL
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-2F-nf.ppm > tmp/cs_sq_large_2_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 168 -height 168 ../HIRES/nocut/NEVSKY-2B-nf.ppm > tmp/cs_sq_large_2_${ROW}_${RCOL}_b.ppm
+		COL=$(expr $COL + 1)
+		RCOL=$(expr $RCOL - 1)
+	done
+	ROW=$(expr $ROW + 1)
+done
+
+# small squares (bottom sheet 1 and 3)
+ROW=1
+for TOP in 1595 1745 1970 2120 2345
+do
+	COL=1
+	RCOL=4
+	for LEFT in 305 1275 2175 2695
+	do
+		echo small square $ROW $COL
+		pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_sq_small_1_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_sq_small_1_${ROW}_${RCOL}_b.ppm
+		pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-3F-nf.ppm > tmp/cs_sq_small_3_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 144 -height 144 ../HIRES/nocut/NEVSKY-3B-nf.ppm > tmp/cs_sq_small_3_${ROW}_${RCOL}_b.ppm
+		COL=$(expr $COL + 1)
+		RCOL=$(expr $RCOL - 1)
+	done
+	ROW=$(expr $ROW + 1)
+done
+
+# large rects (top sheet 2)
+ROW=1
+for TOP in 340 586 833 1080 1326 1572 1818
+do
+	COL=1
+	RCOL=7
+	TOP=$(expr $TOP + 1)
+	for LEFT in 304 739 1175 1611 2047 2483 2919
+	do
+		echo rectangle $ROW $COL
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-2F-nf.ppm > tmp/cs_rect_2_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-2B-nf.ppm > tmp/cs_rect_2_${ROW}_${RCOL}_b.ppm
+		COL=$(expr $COL + 1)
+		RCOL=$(expr $RCOL - 1)
+	done
+	ROW=$(expr $ROW + 1)
+done
+
+# circles (sheet 1)
+ROW=1
+for TOP in 2332
+do
+	COL=1
+	RCOL=6
+	for LEFT in 987 1253 1518 1874 2139 2404
+	do
+		echo circle $ROW $COL
+		pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 204 -height 204 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_circle_1_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 3 + $TOP) -left $(expr 3 + $LEFT) -width 204 -height 204 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_circle_1_${ROW}_${RCOL}_b.ppm
+		COL=$(expr $COL + 1)
+		RCOL=$(expr $RCOL - 1)
+	done
+	ROW=$(expr $ROW + 1)
+done
+
+# rectangles (sheet 1)
+ROW=1
+for TOP in 2342
+do
+	COL=1
+	RCOL=2
+	for LEFT in 416 2806
+	do
+		echo rectangle $ROW $COL
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-1F-nf.ppm > tmp/cs_rect_1_${ROW}_${COL}_a.ppm
+		pnmcut -top $(expr 10 + $TOP) -left $(expr 10 + $LEFT) -width 360 -height 168 ../HIRES/nocut/NEVSKY-1B-nf.ppm > tmp/cs_rect_1_${ROW}_${RCOL}_b.ppm
+		COL=$(expr $COL + 1)
+		RCOL=$(expr $RCOL - 1)
+	done
+	ROW=$(expr $ROW + 1)
+done
diff --git a/tools/build_counters2.sh b/tools/build_counters2.sh
new file mode 100644
index 0000000..ad7bc5d
--- /dev/null
+++ b/tools/build_counters2.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+
+# set -x
+
+mkdir -p counters300
+
+function marker {
+	pnmtopng tmp/${1}_a.ppm > counters300/$2.a.png
+	pnmtopng tmp/${1}_b.ppm > counters300/$2.b.png
+}
+
+function marker2 {
+	pnmtopng tmp/${1}_a.ppm > counters300/$2.png
+	pnmtopng tmp/${1}_b.ppm > counters300/$3.png
+}
+
+marker2 cs_circle_1_1_1 marker_battle marker_storm
+marker2 cs_circle_1_1_2 marker_levy marker_campaign
+marker2 cs_circle_1_1_3 marker_victory_teutonic marker_victory_half_teutonic
+marker2 cs_circle_1_1_4 marker_victory_russian marker_victory_half_russian
+marker2 cs_circle_1_1_5 marker_enemy_lords_removed_teutonic marker_pleskau_blue
+marker2 cs_circle_1_1_6 marker_enemy_lords_removed_russian makrer_pleskau_gray
+
+marker2 cs_sq_small_1_1_2 marker_ravaged_teutonic marker_ravaged_russian
+
+marker cs_sq_small_1_1_1 unit_men_at_arms
+marker cs_sq_small_3_1_1 unit_knights
+marker cs_sq_small_3_1_3 unit_asiatic_horse
+marker cs_sq_small_3_1_4 unit_militia
+marker cs_sq_small_3_3_1 unit_light_horse
+marker cs_sq_small_3_3_3 unit_serfs
+marker cs_sq_small_3_5_1 unit_sergeants
+
+marker2 cs_sq_large_1_1_7 marker_conquered_teutonic marker_conquered_russian
+marker2 cs_sq_large_1_4_11 marker_moved_fought marker_supply_source
+marker2 cs_sq_large_2_1_1 marker_siege_teutonic marker_siege_russian
+marker cs_sq_large_2_1_8 marker_walls
+
+marker2 cs_sq_large_2_1_10 marker_1_teutonic marker_1_russian
+marker2 cs_sq_large_2_1_11 marker_2_teutonic marker_2_russian
+marker2 cs_sq_large_2_1_12 marker_3_teutonic marker_3_russian
+marker2 cs_sq_large_2_2_10 marker_4_teutonic marker_4_russian
+marker2 cs_sq_large_2_2_11 marker_5_teutonic marker_5_russian
+marker2 cs_sq_large_2_2_12 marker_6_teutonic marker_6_russian
+
+marker2 cs_sq_large_1_1_2 asset_coin_x1 asset_coin_x2
+marker2 cs_sq_large_1_3_1 asset_coin_x3 asset_coin_x4
+
+marker2 cs_sq_large_1_3_3 asset_loot_x1 asset_loot_x2
+marker2 cs_sq_large_1_4_6 asset_loot_x3 asset_loot_x4
+
+marker2 cs_sq_large_1_5_1 asset_prov_x1 asset_prov_x2
+marker2 cs_sq_large_3_4_10 asset_prov_x3 asset_prov_x4
+
+marker2 cs_sq_large_3_1_1 asset_boat_x1 asset_ship_x1
+marker2 cs_sq_large_3_4_1 asset_boat_x2 asset_ship_x2
+marker2 cs_sq_large_3_4_2 asset_boat_x4 asset_ship_x4
+
+marker2 cs_sq_large_3_1_7 asset_sled_x1 asset_cart_x1
+marker2 cs_sq_large_3_4_4 asset_sled_x2 asset_cart_x2
+marker2 cs_sq_large_3_5_6 asset_sled_x4 asset_cart_x4
+
+marker cs_rect_1_1_1 marker_sea_trade_blocked
+
+marker cs_rect_2_1_1 lord_russian_aleksandr
+marker cs_rect_2_1_2 lord_russian_andrey
+marker cs_rect_2_1_3 vassal_russian_gavrilo_pskov
+marker cs_rect_2_1_4 vassal_russian_andrey_vladimir
+marker cs_rect_2_1_5 vassal_russian_domash_novgorod
+marker cs_rect_2_1_6 vassal_russian_andrey_kipchaqs
+marker cs_rect_2_1_7 vassal_teutonic_knud_and_abel_dietrich_von_kivel
+
+marker cs_rect_2_2_1 lord_russian_vladislav
+marker cs_rect_2_2_2 lord_russian_karelians
+marker cs_rect_2_2_3 vassal_russian_aleksandr_pereyaslavl
+marker cs_rect_2_2_4 vassal_russian_andrey_suzdal
+marker cs_rect_2_2_5 lord_teutonic_andreas
+marker cs_rect_2_2_6 vassal_teutonic_andreas_summer_crusaders
+marker cs_rect_2_2_7 vassal_teutonic_knud_and_abel_otto_von_luneburg
+
+marker cs_rect_2_3_1 lord_russian_domash
+marker cs_rect_2_3_2 vassal_russian_vladislav_vepsian_auxiliaries
+marker cs_rect_2_3_3 vassal_russian_aleksandr_rostov
+marker cs_rect_2_3_4 vassal_teutonic_hermann_ugaunian_auxiliaries
+marker cs_rect_2_3_5 lord_teutonic_hermann
+marker cs_rect_2_3_6 lord_teutonic_knud_and_abel
+marker cs_rect_2_3_7 vassal_teutonic_knud_and_abel_estonian_auxiliaries
+
+marker cs_rect_2_4_1 lord_russian_gavrilo
+marker cs_rect_2_4_2 vassal_russian_vladislav_ingrian_auxiliaries
+marker cs_rect_2_4_3 vassal_russian_aleksandr_yaroslavl
+marker cs_rect_2_4_4 vassal_teutonic_hermann_helmond_von_luneburg
+marker cs_rect_2_4_5 lord_teutonic_heinrich
+marker cs_rect_2_4_6 lord_teutonic_yaroslav
+marker2 cs_rect_2_4_7 marker_castle_teutonic marker_castle_russian
+
+marker cs_rect_2_5_1 vassal_russian_vladislav_vodian_auxiliaries
+marker cs_rect_2_5_2 vassal_russian_vladislav_izhoran_auxiliaries
+marker cs_rect_2_5_3 vassal_russian_aleksandr_mongols
+marker cs_rect_2_5_4 vassal_teutonic_hermann_johannes_von_dolen
+marker cs_rect_2_5_5 lord_teutonic_rudolf
+marker cs_rect_2_5_6 vassal_teutonic_rudolf_summer_crusaders
+#marker2 cs_rect_2_5_7 marker_castle_teutonic marker_castle_russian
+
+marker cs_rect_2_6_1 vassal_russian_gavrilo_borderland_russians
+marker cs_rect_2_6_2 vassal_russian_domash_novgorod
+#marker cs_rect_2_6_3 vassal_russian_aleksandr_mongols
+marker cs_rect_2_6_4 vassal_teutonic_heinrich_odward_von_lode
+marker cs_rect_2_6_5 vassal_teutonic_andreas_teutonic_vassals
+marker cs_rect_2_6_6 vassal_teutonic_rudolf_ex_sword_brethren
+marker cs_rect_2_6_7 vassal_teutonic_yaroslav_mstislavich_partisans
+
+marker cs_rect_2_7_1 vassal_russian_gavrilo_pskov_militia
+marker cs_rect_2_7_2 vassal_russian_domash_novgorod
+#marker cs_rect_2_7_3 vassal_russian_andrey_kipchaqs
+marker cs_rect_2_7_4 vassal_teutonic_heinrich_heinrich_von_lode
+marker cs_rect_2_7_5 vassal_teutonic_andreas_lettgallian_auxiliaries
+marker cs_rect_2_7_6 vassal_teutonic_rudolf_jerwen_teutonic_vassals
+marker2 cs_rect_2_7_7 marker_pursuit_teutonic marker_pursuit_russian
diff --git a/tools/build_counters3.sh b/tools/build_counters3.sh
new file mode 100644
index 0000000..142d802
--- /dev/null
+++ b/tools/build_counters3.sh
@@ -0,0 +1,5 @@
+mkdir -p service300
+montage -mode concatenate -tile 2x counters300/lord_teutonic_andreas.a.png counters300/lord_teutonic_andreas.b.png counters300/lord_teutonic_heinrich.a.png counters300/lord_teutonic_heinrich.b.png counters300/lord_teutonic_hermann.a.png counters300/lord_teutonic_hermann.b.png counters300/lord_teutonic_knud_and_abel.a.png counters300/lord_teutonic_knud_and_abel.b.png counters300/lord_teutonic_rudolf.a.png counters300/lord_teutonic_rudolf.b.png counters300/lord_teutonic_yaroslav.a.png counters300/lord_teutonic_yaroslav.b.png service300/service_lords_teutonic.png
+montage -mode concatenate -tile 2x counters300/vassal_teutonic_andreas_lettgallian_auxiliaries.a.png counters300/vassal_teutonic_andreas_lettgallian_auxiliaries.b.png counters300/vassal_teutonic_andreas_summer_crusaders.a.png counters300/vassal_teutonic_andreas_summer_crusaders.b.png counters300/vassal_teutonic_andreas_teutonic_vassals.a.png counters300/vassal_teutonic_andreas_teutonic_vassals.b.png counters300/vassal_teutonic_heinrich_heinrich_von_lode.a.png counters300/vassal_teutonic_heinrich_heinrich_von_lode.b.png counters300/vassal_teutonic_heinrich_odward_von_lode.a.png counters300/vassal_teutonic_heinrich_odward_von_lode.b.png counters300/vassal_teutonic_hermann_helmond_von_luneburg.a.png counters300/vassal_teutonic_hermann_helmond_von_luneburg.b.png counters300/vassal_teutonic_hermann_johannes_von_dolen.a.png counters300/vassal_teutonic_hermann_johannes_von_dolen.b.png counters300/vassal_teutonic_hermann_ugaunian_auxiliaries.a.png counters300/vassal_teutonic_hermann_ugaunian_auxiliaries.b.png counters300/vassal_teutonic_knud_and_abel_dietrich_von_kivel.a.png counters300/vassal_teutonic_knud_and_abel_dietrich_von_kivel.b.png counters300/vassal_teutonic_knud_and_abel_estonian_auxiliaries.a.png counters300/vassal_teutonic_knud_and_abel_estonian_auxiliaries.b.png counters300/vassal_teutonic_knud_and_abel_otto_von_luneburg.a.png counters300/vassal_teutonic_knud_and_abel_otto_von_luneburg.b.png counters300/vassal_teutonic_rudolf_ex_sword_brethren.a.png counters300/vassal_teutonic_rudolf_ex_sword_brethren.b.png counters300/vassal_teutonic_rudolf_jerwen_teutonic_vassals.a.png counters300/vassal_teutonic_rudolf_jerwen_teutonic_vassals.b.png counters300/vassal_teutonic_rudolf_summer_crusaders.a.png counters300/vassal_teutonic_rudolf_summer_crusaders.b.png counters300/vassal_teutonic_yaroslav_mstislavich_partisans.a.png counters300/vassal_teutonic_yaroslav_mstislavich_partisans.b.png service300/service_vassals_teutonic.png
+montage -mode concatenate -tile 2x counters300/lord_russian_aleksandr.a.png counters300/lord_russian_aleksandr.b.png counters300/lord_russian_andrey.a.png counters300/lord_russian_andrey.b.png counters300/lord_russian_domash.a.png counters300/lord_russian_domash.b.png counters300/lord_russian_gavrilo.a.png counters300/lord_russian_gavrilo.b.png counters300/lord_russian_karelians.a.png counters300/lord_russian_karelians.b.png counters300/lord_russian_vladislav.a.png counters300/lord_russian_vladislav.b.png service300/service_lords_russian.png
+montage -mode concatenate -tile 2x counters300/vassal_russian_aleksandr_mongols.a.png counters300/vassal_russian_aleksandr_mongols.b.png counters300/vassal_russian_aleksandr_pereyaslavl.a.png counters300/vassal_russian_aleksandr_pereyaslavl.b.png counters300/vassal_russian_aleksandr_rostov.a.png counters300/vassal_russian_aleksandr_rostov.b.png counters300/vassal_russian_aleksandr_yaroslavl.a.png counters300/vassal_russian_aleksandr_yaroslavl.b.png counters300/vassal_russian_andrey_kipchaqs.a.png counters300/vassal_russian_andrey_kipchaqs.b.png counters300/vassal_russian_andrey_suzdal.a.png counters300/vassal_russian_andrey_suzdal.b.png counters300/vassal_russian_andrey_vladimir.a.png counters300/vassal_russian_andrey_vladimir.b.png counters300/vassal_russian_domash_novgorod.a.png counters300/vassal_russian_domash_novgorod.b.png counters300/vassal_russian_gavrilo_borderland_russians.a.png counters300/vassal_russian_gavrilo_borderland_russians.b.png counters300/vassal_russian_gavrilo_pskov_militia.a.png counters300/vassal_russian_gavrilo_pskov_militia.b.png counters300/vassal_russian_gavrilo_pskov.a.png counters300/vassal_russian_gavrilo_pskov.b.png counters300/vassal_russian_vladislav_izhoran_auxiliaries.a.png counters300/vassal_russian_vladislav_izhoran_auxiliaries.b.png counters300/vassal_russian_vladislav_ingrian_auxiliaries.a.png counters300/vassal_russian_vladislav_ingrian_auxiliaries.b.png counters300/vassal_russian_vladislav_vepsian_auxiliaries.a.png counters300/vassal_russian_vladislav_vepsian_auxiliaries.b.png counters300/vassal_russian_vladislav_vodian_auxiliaries.a.png counters300/vassal_russian_vladislav_vodian_auxiliaries.b.png service300/service_vassals_russian.png
diff --git a/tools/build_map.sh b/tools/build_map.sh
new file mode 100644
index 0000000..1af18c4
--- /dev/null
+++ b/tools/build_map.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -x
+
+mutool extract -r "../HIRES/Nevsky - Game Board_CORRECTED 080919.pdf"
+mv image-0017.png map300_uncropped.png
+
+# ArtBox 63.5197 63.5197 1287.52 1647.52
+# 5100x6600
+convert map300_uncropped.png -gravity Center -crop 5100x6600+0+0 +repage map300.png
+rm map300_uncropped.png
+
+# convert map300.png -colorspace RGB -resize 33.3333333% -colorspace sRGB map100.png
+# convert map300.png -colorspace RGB -resize 66.6666667% -colorspace sRGB map200.png
+convert map300.png -colorspace RGB -resize 25% -colorspace sRGB map75.png
+convert map300.png -colorspace RGB -resize 50% -colorspace sRGB map150.png
diff --git a/tools/build_mats.sh b/tools/build_mats.sh
new file mode 100644
index 0000000..a9e348b
--- /dev/null
+++ b/tools/build_mats.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+set -x
+
+mkdir -p tmp/mat
+mkdir -p output300
+
+if [ ! -f ../HIRES/raw_mat_01.1200.png ]
+then
+	gs -sDEVICE=png16m -r1200 -o ../HIRES/raw_mat_%02d.1200.png "../HIRES/Lord Mats-FINAL-nf.pdf"
+fi
+
+for F in ../HIRES/raw_mat_*.1200.png
+do
+	convert -colorspace RGB -scale 25% -colorspace sRGB $F ${F/.1200.png/.png}
+done
+
+# crop: 150,150 to 1650,1650 = 1500x1500
+
+# ARGS="-gravity Center -crop 1500x1500+0+0 +repage -background #d0bf7d -flatten"
+ARGS="-gravity Center -crop 1440x1440+0+0 +repage -background #d0bf7d -flatten"
+
+convert ../HIRES/raw_mat_01.png $ARGS output300/mat_russian_aleksandr.png
+convert ../HIRES/raw_mat_02.png $ARGS output300/mat_russian_andrey.png
+convert ../HIRES/raw_mat_03.png $ARGS output300/mat_russian_domash.png
+convert ../HIRES/raw_mat_04.png $ARGS output300/mat_russian_gavrilo.png
+convert ../HIRES/raw_mat_05.png $ARGS output300/mat_russian_vladislav.png
+convert ../HIRES/raw_mat_06.png $ARGS output300/mat_russian_karelians.png
+convert ../HIRES/raw_mat_07.png $ARGS output300/mat_teutonic_yaroslav.png
+convert ../HIRES/raw_mat_08.png $ARGS output300/mat_teutonic_knud_and_abel.png
+convert ../HIRES/raw_mat_09.png $ARGS output300/mat_teutonic_rudolf.png
+convert ../HIRES/raw_mat_10.png $ARGS output300/mat_teutonic_heinrich.png
+convert ../HIRES/raw_mat_11.png $ARGS output300/mat_teutonic_hermann.png
+convert ../HIRES/raw_mat_12.png $ARGS output300/mat_teutonic_andreas.png
+convert ../HIRES/raw_mat_13.png $ARGS output300/mat_battle.png
diff --git a/tools/build_screen.sh b/tools/build_screen.sh
new file mode 100644
index 0000000..607ca7b
--- /dev/null
+++ b/tools/build_screen.sh
@@ -0,0 +1,10 @@
+mkdir -p tmp
+convert -size 2160x750 gradient:white-none tmp/gradient.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen1.png tmp/screen1.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen2.png tmp/screen2.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen3.png tmp/screen3.png
+convert -resize 2250x750 -gravity Center -crop 2160x750+0+0 +repage ../HIRES/screen4.png tmp/screen4.png
+convert tmp/screen1.png gradient.png -compose Over -flatten screen1.png
+convert tmp/screen2.png gradient.png -compose Over -flatten screen2.png
+convert tmp/screen3.png gradient.png -compose Over -flatten screen3.png
+convert tmp/screen4.png gradient.png -compose Over -flatten screen4.png
diff --git a/tools/build_stickers.sh b/tools/build_stickers.sh
new file mode 100644
index 0000000..3e49e69
--- /dev/null
+++ b/tools/build_stickers.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+set -x
+
+#gs -sDEVICE=png16m -r1200 -o tmp/stickers1200.png ../HIRES/stickers3.pdf
+
+mkdir -p tmp
+
+# 672x672 stickers @1200
+#pngtopnm tmp/stickers1200.png > tmp/stickers1200.ppm
+pnmcut -top 1912 -height 672 tmp/stickers1200.ppm > tmp/row1x.ppm
+pnmcut -top 2856 -height 672 tmp/stickers1200.ppm > tmp/row2x.ppm
+
+I=1
+for LEFT in 405 641 877 1113 1349 1586 1822
+do
+	pnmcut -left $(expr 4 '*' $LEFT) -width 672 tmp/row1x.ppm | pnmtopng > tmp/lord_teutonic_$I.png
+	pnmcut -left $(expr 4 '*' $LEFT) -width 672 tmp/row2x.ppm | pnmtopng > tmp/lord_russian_$I.png
+	I=$(expr $I + 1)
+done
+
+# 150dpi -> 84x84
+# 75dpi -> 42x42
+# 3d -> 42x28
+for I in 1 2 3 4 5 6 7
+do
+	convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 84x56! -colorspace sRGB output150/lord_teutonic_${I}_3d.png
+	convert tmp/lord_russian_$I.png -colorspace RGB -geometry 84x56! -colorspace sRGB output150/lord_russian_${I}_3d.png
+	convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 84x84! -colorspace sRGB output150/lord_teutonic_${I}.png
+	convert tmp/lord_russian_$I.png -colorspace RGB -geometry 84x84! -colorspace sRGB output150/lord_russian_${I}.png
+
+	convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 42x28! -colorspace sRGB output75/lord_teutonic_${I}_3d.png
+	convert tmp/lord_russian_$I.png -colorspace RGB -geometry 42x28! -colorspace sRGB output75/lord_russian_${I}_3d.png
+	convert tmp/lord_teutonic_$I.png -colorspace RGB -geometry 42x42! -colorspace sRGB output75/lord_teutonic_${I}.png
+	convert tmp/lord_russian_$I.png -colorspace RGB -geometry 42x42! -colorspace sRGB output75/lord_russian_${I}.png
+done
+
diff --git a/tools/colors.mjs b/tools/colors.mjs
new file mode 100644
index 0000000..4af8545
--- /dev/null
+++ b/tools/colors.mjs
@@ -0,0 +1,108 @@
+import { formatHex, filterBrightness, parseHex, convertRgbToOklab, convertRgbToLrgb, interpolate } from 'culori'
+
+const yuv = true;
+const gamma = 2.2;
+
+const data = [
+[".mat .background", "d1c07e"],
+[".card.teutonic", "e1e6e8"],
+[".card.russian", "e1d6c1"],
+[".service_marker.teutonic.vassal", "777474"],
+[".service_marker.russian.vassal", "f0ead8"],
+[".asset.sled", "e5dcc1"],
+[".asset.boat", "adceed"],
+[".asset.cart.x1", "daba8b"],
+[".asset.cart.x2", "d1a973"],
+[".asset.cart.x4", "c4975b"],
+[".asset.coin.x1", "d2d5d4"],
+[".asset.coin.x2", "d2d5d4"],
+[".asset.coin.x3", "b3b5b4"],
+[".asset.coin.x4", "b3b5b4"],
+[".asset.prov.x1", "ffe293"],
+[".asset.prov.x2", "ffe293"],
+[".asset.prov.x3", "ffcd66"],
+[".asset.prov.x4", "ffcd66"],
+[".asset.ship.x1", "79b7e4"],
+[".asset.ship.x2", "79b7e4"],
+[".asset.ship.x4", "5da9dd"],
+[".asset.loot.x1", "f0b64f"],
+[".asset.loot.x2", "eda44c"],
+[".asset.loot.x3", "eb924a"],
+[".asset.loot.x4", "e1884a"],
+[".marker.battle", "d0bf7d"],
+[".marker.storm", "d0bf7d"],
+[".marker.pursuit", "c6ab7f"],
+[".marker.turn.campaign", "6a8aa8"],
+[".marker.turn.levy", "967348"],
+[".marker.teutonic.victory", "ffd400"],
+[".marker.teutonic.siege", "a39382"],
+[".marker.russian.conquered", "649655"],
+[".marker.russian.enemy_lords_removed", "ffd400"],
+[".marker.russian.victory", "2d8b47"],
+[".marker.walls", "e3dedc"],
+[".marker.russian.number", "c6992f"],
+[".marker.teutonic.number", "a02532"],
+
+/*
+[".unit", "ffd768"],
+[".marker.supply_source", "e6dcb9"],
+[".marker.moved_fought", "0072bc"],
+[".marker.pleskau_black", "324b5b"],
+[".marker.pleskau_white", "e3dedc"],
+*/
+].map(([sel,color])=>[ sel, parseHex(color) ])
+
+const colors = `knights_fill ffffff
+knights_stroke d1d3d4
+asiatic_horse_fill f7df93
+asiatic_horse_stroke 908357
+serfs_fill e39c43
+serfs_stroke b17b33
+militia_fill cc6a2c
+militia_stroke 773b0c
+men_at_arms_fill c0b6b3
+men_at_arms_stroke 716c6b
+die_1 662c91
+die_2 ee161c
+die_3 f7941d
+die_4 ffd400
+die_5 26903a`
+
+let css = []
+
+function brighten(color, n) {
+	return { mode: 'lrgb',
+		r: 1 - (1-color.r) * n,
+		g: 1 - (1-color.g) * n,
+		b: 1 - (1-color.b) * n,
+	}
+}
+
+function darken(color, n) {
+	return { mode: 'lrgb',
+		r: (color.r) * n,
+		g: (color.g) * n,
+		b: (color.b) * n,
+	}
+}
+
+let white = parseHex('#fff')
+let black = parseHex('#000')
+
+for (let [ sel, rgb ] of data) {
+	let base = formatHex(rgb)
+	//let hi = formatHex(brighten(convertRgbToLrgb(rgb), 0.8))
+	//let lo = formatHex(darken(convertRgbToLrgb(rgb), 0.8))
+	//let sh = formatHex(darken(convertRgbToLrgb(rgb), 0.125))
+	//let hi = formatHex(interpolate([rgb,white],'lrgb')(0.2))
+	//let lo = formatHex(interpolate([rgb,black],'lrgb')(0.2))
+	//let sh = formatHex(interpolate([rgb,black],'lrgb')(0.6))
+	let hic = convertRgbToOklab(rgb); hic.l = Math.min(1,hic.l+0.1)
+	let loc = convertRgbToOklab(rgb); loc.l = Math.max(0,loc.l-0.1)
+	let shc = convertRgbToOklab(rgb); shc.l = Math.max(0,shc.l-0.4)
+	let sh = formatHex(shc)
+	let hi = formatHex(hic)
+	let lo = formatHex(loc)
+	css.push(`${sel} { background-color: ${base}; border-color: ${hi} ${lo} ${lo} ${hi}; box-shadow: 0 0 0 1px ${sh}, 1px 2px 4px #0008; }`)
+}
+console.log(css.join("\n"))
diff --git a/tools/genboxes.py b/tools/genboxes.py
new file mode 100644
index 0000000..92488ed
--- /dev/null
+++ b/tools/genboxes.py
@@ -0,0 +1,65 @@
+mode = None
+
+list = []
+
+x = y = w = h = 0
+name = None
+
+def flush():
+	global x, y, w, h, name
+	if mode == 'rect':
+		list.append((x,y,w,h,'box',name))
+	if mode == 'circle':
+		x = cx - rx
+		y = cy - ry
+		w = rx * 2
+		h = ry * 2
+		list.append((x,y,w,h,'circle',name))
+	x = y = w = h = 0
+	name = None
+
+for line in open("boxes.svg").readlines():
+	line = line.strip()
+	if line == "<rect":
+		flush()
+		mode = 'rect'
+		x = y = w = h = 0
+	elif line == "<ellipse":
+		flush()
+		mode = 'circle'
+		cx = cy = rx = ry = 0
+	if line.startswith('x="'): x = round(float(line.split('"')[1]))
+	if line.startswith('y="'): y = round(float(line.split('"')[1]))
+	if line.startswith('width="'): w = round(float(line.split('"')[1]))
+	if line.startswith('height="'): h = round(float(line.split('"')[1]))
+	if line.startswith('cx="'): cx = round(float(line.split('"')[1]))
+	if line.startswith('cy="'): cy = round(float(line.split('"')[1]))
+	if line.startswith('rx="'): rx = round(float(line.split('"')[1]))
+	if line.startswith('ry="'): ry = round(float(line.split('"')[1]))
+	if line.startswith('inkscape:label="'): name = line.split('"')[1]
+flush()
+
+def print_list():
+	print("const boxes = {")
+	for (x,y,w,h,c,name) in list:
+		print(f'"{name}": [{x},{y},{w},{h}],')
+	print("}")
+
+def print_html():
+	print('<html><style>')
+	print('.box{position:absolute;background-color:#f008;border:2px solid blue;}')
+	print('.circle{position:absolute;background-color:#0f08;border-radius:50%;border:2px solid blue;}')
+	print('img{position:absolute;display:block}')
+	print('</style>')
+	print('<div style="position:relative;width:1275px;heigth:1650px;">')
+	print('<img src="map75.png">')
+	for (x,y,w,h,c,name) in list:
+		x = round(x/4) - 1
+		y = round(y/4) - 1
+		w = round(w/4) + 2
+		h = round(h/4) + 2
+		print(f'<div class="{c}" style="top:{y}px;left:{x}px;width:{w-4}px;height:{h-4}px">{name}</div>')
+	print('</div>')
+
+#print_html()
+print_list()
diff --git a/tools/gencss.js b/tools/gencss.js
new file mode 100644
index 0000000..4edc4be
--- /dev/null
+++ b/tools/gencss.js
@@ -0,0 +1,5 @@
+const h = 42
+for (let i = 0; i < 15; ++i)
+	console.log(`.service_marker.image${i}{background-position:0 -${i*h}px}`)
+for (let i = 0; i < 15; ++i)
+	console.log(`.service_marker.image${i}:hover{background-position:100% -${i*h}px}`)
diff --git a/tools/gencyl.js b/tools/gencyl.js
new file mode 100644
index 0000000..bae34b9
--- /dev/null
+++ b/tools/gencyl.js
@@ -0,0 +1,51 @@
+// physical cylinders are diameter 15mm x 10mm
+// at 75dpi => 44px x 29px
+// stickers: 42x28
+// image: 44x? - outline at 1 to 2 - start at 1.5
+
+const fs = require('fs')
+
+function print_lord(output, side, label) {
+	let image = fs.readFileSync(label).toString('base64')
+	let svg = []
+	let bd = '#222'
+	let f = 'url(#g)'
+	svg.push('<svg xmlns="http://www.w3.org/2000/svg" width="44" height="48">')
+	svg.push('<clipPath id="c"><ellipse cx="22" cy="15" rx="20.5" ry="13.5"/></clipPath>')
+
+	if (1) {
+		svg.push('<linearGradient id="g">')
+		if (side === 'russian') {
+			svg.push('<stop offset="0%" stop-color="#ddd"/>')
+			svg.push('<stop offset="40%" stop-color="#fff"/>')
+			svg.push('<stop offset="100%" stop-color="#ccc"/>')
+			bd = '#555'
+		} else {
+			svg.push('<stop offset="0%" stop-color="#444"/>')
+			svg.push('<stop offset="40%" stop-color="#666"/>')
+			svg.push('<stop offset="100%" stop-color="#333"/>')
+			bd = '#111'
+		}
+		svg.push('</linearGradient>')
+	} else {
+		if (side === 'russian') {
+			f = '#ddd'
+			bd = '#222'
+		} else {
+			f = '#555'
+			bd = '#222'
+		}
+	}
+
+	svg.push(`<path fill="${f}" stroke="${bd}" d="M1.5 15v18A20.5 13.5 0 0 0 22 46.5 20.5 13.5 0 0 0 42.5 33V15h-41z"/>`)
+	svg.push(`<image href="data:image/png;base64,${image}" clip-path="url(#c)" x="1" y="1" width="42" height="28"/>`)
+	svg.push(`<ellipse fill="none" stroke="${bd}" cx="22" cy="15" rx="20.5" ry="13.5"/>`)
+
+	svg.push('</svg>')
+	fs.writeFileSync(output, svg.join("\n") + "\n")
+}
+
+for (let i = 1; i <= 7; ++i) {
+	print_lord(`images/lord_teutonic_${i}.svg`, "teutonic", `tools/output150/lord_teutonic_${i}_3d.png`)
+	print_lord(`images/lord_russian_${i}.svg`, "russian", `tools/output150/lord_russian_${i}_3d.png`)
+}
diff --git a/tools/gendata.js b/tools/gendata.js
new file mode 100644
index 0000000..2968023
--- /dev/null
+++ b/tools/gendata.js
@@ -0,0 +1,705 @@
+// Run this script inside the "tools" directory to generate data.js and build_counters3.sh
+
+"use strict"
+
+const fs = require('fs')
+
+// :r !python3 genboxes.py
+const boxes = {
+"Wesenberg": [1448,3625,304,60],
+"Fellin": [1013,4583,184,61],
+"Odenpäh": [1378,5103,250,61],
+"Adsel": [1504,5612,185,60],
+"Wenden": [909,5759,232,60],
+"Luga": [2667,3295,148,62],
+"Kaibolovo": [2904,3522,285,62],
+"Koporye": [3133,3160,241,62],
+"Neva": [3924,2934,148,62],
+"Volkhov": [4591,3783,231,63],
+"Lovat": [4243,5581,187,63],
+"Porkhov": [3515,5467,241,63],
+"Izborsk": [2240,5431,241,62],
+"Velikiye Luki": [3706,6347,351,61],
+"Tesovo": [3936,4102,121,32],
+"Zheltsy": [3501,4176,128,30],
+"Sablia": [3788,4541,104,31],
+"Gdov": [2427,4149,88,30],
+"Dubrovno": [3153,5214,161,31],
+"Ostrov": [2746,5717,115,30],
+"Rositten": [2046,6307,146,30],
+"Kirrumpäh": [1877,5389,175,30],
+"Pernau": [517,4580,118,30],
+"Narwia": [2371,3549,123,31],
+"Wierland": [1999,3680,200,100],
+"Warbola": [292,3797,142,31],
+"Waiga": [1535,4113,200,100],
+"Jerwen": [1064,3946,200,100],
+"Harrien": [567,3983,200,100],
+"Sackala": [617,4769,200,100],
+"Metsepole": [509,5226,200,100],
+"Lettgallia": [2048,5777,200,100],
+"Tolowa": [1541,5933,200,100],
+"Ugaunia": [1957,4940,200,100],
+"Revala": [1030,3410,200,100],
+"Velikaya River": [3029,6090,200,100],
+"Sorot River": [3299,5781,200,100],
+"Shelon River": [3654,4864,200,100],
+"Zhelcha River": [2782,4586,200,100],
+"Plyussa River": [2829,4234,200,100],
+"Ingria": [3820,3639,200,100],
+"Vod": [3488,3345,200,100],
+"Izhora": [4074,3323,200,100],
+"Karelia": [3833,2408,200,100],
+"Ladoga": [4619,2817,238,90],
+"Novgorod": [4318,4315,333,112],
+"Rusa": [4329,5166,205,92],
+"Riga": [273,6231,205,91],
+"Pskov": [2680,5263,205,91],
+"Dorpat": [1625,4589,253,91],
+"Leal": [108,4266,205,91],
+"Reval": [601,3564,206,91],
+"box1": [40,168,592,917],
+"box2": [650,168,591,913],
+"box3": [1313,167,591,916],
+"box4": [1922,167,590,916],
+"box5": [2587,167,592,918],
+"box6": [3196,167,588,916],
+"box7": [3859,167,594,916],
+"box8": [4469,166,591,918],
+"box9": [39,1119,594,915],
+"Victory": [172,183,217,215],
+"Turn": [399,184,216,214],
+"Novgorod Veche": [4193,5847,844,628],
+"Uzmen": [2112,4692,200,100],
+"way-crossroads": [1500,4717,462,149],
+"way-wirz": [1295,4526,175,350],
+"way-peipus-east": [2232,4197,220,465],
+"way-peipus-north": [2065,3836,361,228],
+"way-peipus-west": [1988,4141,218,520],
+}
+
+let data = []
+function print(str) {
+	data.push(str)
+}
+
+var locmap = {}
+
+// 0=offmap, 1-N=map locales, 100-M=calendar boxes
+var locales = [null]
+var ways = []
+var waterways = []
+var trackways = []
+
+const scale = 1
+
+function defloc(region, stronghold, type, name) {
+	let [x, y, w, h] = boxes[name]
+	x = Math.round(x * scale)
+	y = Math.round(y * scale)
+	w = Math.round(w * scale)
+	h = Math.round(h * scale)
+	locmap[name] = locales.length
+	locales.push({ name, type, stronghold, region, ways: [], box: { x, y, w, h } })
+}
+
+function defway(type, list) {
+	let ix = ways.length
+	list = list.map(name=>locmap[name]).sort((a,b)=>a-b)
+	ways.push({type, locales: list})
+	for (let from of list) {
+		for (let to of list) {
+			if (from !== to) {
+				let old = locales[from].ways.find(w => w[0] === to)
+				if (old)
+					old.push(ix)
+				else
+					locales[from].ways.push([to, ix])
+			}
+		}
+	}
+	return ways[ix]
+}
+
+function waterway(locs) { return defway('waterway', locs.split(", ")) }
+function trackway(locs) { return defway('trackway', locs.split(", ")) }
+
+defloc("Danish Estonia", 3, "bishopric", "Reval")
+defloc("Danish Estonia", 2, "castle", "Wesenberg")
+defloc("Danish Estonia", 0, "town", "Narwia")
+defloc("Danish Estonia", 0, "town", "Warbola")
+defloc("Danish Estonia", 0, "region", "Harrien")
+defloc("Danish Estonia", 0, "region", "Revala")
+defloc("Danish Estonia", 0, "region", "Wierland")
+
+defloc("Crusader Livonia", 3, "bishopric", "Dorpat")
+defloc("Crusader Livonia", 3, "bishopric", "Leal")
+defloc("Crusader Livonia", 3, "bishopric", "Riga")
+defloc("Crusader Livonia", 2, "castle", "Adsel")
+defloc("Crusader Livonia", 2, "castle", "Fellin")
+defloc("Crusader Livonia", 2, "castle", "Odenpäh")
+defloc("Crusader Livonia", 2, "castle", "Wenden")
+defloc("Crusader Livonia", 0, "town", "Kirrumpäh")
+defloc("Crusader Livonia", 0, "town", "Pernau")
+defloc("Crusader Livonia", 0, "town", "Rositten")
+defloc("Crusader Livonia", 0, "region", "Jerwen")
+defloc("Crusader Livonia", 0, "region", "Lettgallia")
+defloc("Crusader Livonia", 0, "region", "Metsepole")
+defloc("Crusader Livonia", 0, "region", "Sackala")
+defloc("Crusader Livonia", 0, "region", "Tolowa")
+defloc("Crusader Livonia", 0, "region", "Ugaunia")
+defloc("Crusader Livonia", 0, "region", "Waiga")
+
+defloc("Novgorodan Rus", 3, "archbishopric", "Novgorod")
+defloc("Novgorodan Rus", 3, "city", "Ladoga")
+defloc("Novgorodan Rus", 3, "city", "Pskov")
+defloc("Novgorodan Rus", 3, "city", "Rusa")
+defloc("Novgorodan Rus", 0, "traderoute", "Lovat")
+defloc("Novgorodan Rus", 0, "traderoute", "Luga")
+defloc("Novgorodan Rus", 0, "traderoute", "Neva")
+defloc("Novgorodan Rus", 0, "traderoute", "Volkhov")
+defloc("Novgorodan Rus", 1, "fort", "Izborsk")
+defloc("Novgorodan Rus", 1, "fort", "Kaibolovo")
+defloc("Novgorodan Rus", 1, "fort", "Koporye")
+defloc("Novgorodan Rus", 1, "fort", "Porkhov")
+defloc("Novgorodan Rus", 1, "fort", "Velikiye Luki")
+defloc("Novgorodan Rus", 0, "town", "Dubrovno")
+defloc("Novgorodan Rus", 0, "town", "Gdov")
+defloc("Novgorodan Rus", 0, "town", "Ostrov")
+defloc("Novgorodan Rus", 0, "town", "Sablia")
+defloc("Novgorodan Rus", 0, "town", "Tesovo")
+defloc("Novgorodan Rus", 0, "town", "Zheltsy")
+defloc("Novgorodan Rus", 0, "region", "Ingria")
+defloc("Novgorodan Rus", 0, "region", "Izhora")
+defloc("Novgorodan Rus", 0, "region", "Karelia")
+defloc("Novgorodan Rus", 0, "region", "Plyussa River")
+defloc("Novgorodan Rus", 0, "region", "Shelon River")
+defloc("Novgorodan Rus", 0, "region", "Sorot River")
+defloc("Novgorodan Rus", 0, "region", "Uzmen")
+defloc("Novgorodan Rus", 0, "region", "Velikaya River")
+defloc("Novgorodan Rus", 0, "region", "Vod")
+defloc("Novgorodan Rus", 0, "region", "Zhelcha River")
+
+waterway("Dorpat, Narwia, Gdov, Uzmen").name = "Pleipat W"
+waterway("Gdov, Uzmen").name = "Pleipat E"
+waterway("Fellin, Dorpat, Odenpäh").name = "Wirz"
+trackway("Dorpat, Odenpäh, Ugaunia").name = "Crossroads"
+
+waterway("Uzmen, Pskov")
+waterway("Uzmen, Zhelcha River")
+waterway("Novgorod, Shelon River, Rusa")
+waterway("Pernau, Fellin, Jerwen")
+waterway("Riga, Wenden")
+waterway("Wenden, Adsel")
+waterway("Pskov, Ostrov")
+waterway("Ostrov, Velikaya River")
+waterway("Sorot River, Velikaya River")
+waterway("Rusa, Lovat")
+waterway("Lovat, Velikiye Luki")
+waterway("Shelon River, Dubrovno")
+waterway("Shelon River, Porkhov")
+waterway("Narwia, Plyussa River")
+waterway("Luga, Kaibolovo")
+waterway("Kaibolovo, Zheltsy")
+waterway("Zheltsy, Sablia")
+waterway("Zheltsy, Tesovo")
+waterway("Ladoga, Volkhov")
+waterway("Volkhov, Novgorod")
+waterway("Neva, Ladoga")
+
+trackway("Reval, Revala")
+trackway("Revala, Wesenberg")
+trackway("Wierland, Narwia")
+trackway("Narwia, Kaibolovo")
+trackway("Wierland, Waiga")
+trackway("Wesenberg, Jerwen")
+trackway("Reval, Warbola")
+trackway("Warbola, Harrien")
+trackway("Harrien, Jerwen")
+trackway("Warbola, Leal")
+
+trackway("Waiga, Dorpat")
+trackway("Leal, Pernau")
+trackway("Fellin, Sackala")
+trackway("Sackala, Metsepole")
+trackway("Metsepole, Wenden")
+trackway("Wenden, Tolowa")
+trackway("Tolowa, Rositten")
+trackway("Lettgallia, Rositten")
+trackway("Adsel, Tolowa, Lettgallia")
+trackway("Adsel, Kirrumpäh")
+trackway("Odenpäh, Kirrumpäh")
+trackway("Kirrumpäh, Izborsk")
+trackway("Ugaunia, Uzmen")
+trackway("Ugaunia, Izborsk")
+trackway("Lettgallia, Ostrov")
+trackway("Lettgallia, Izborsk")
+
+trackway("Izborsk, Pskov")
+trackway("Kaibolovo, Koporye")
+trackway("Koporye, Vod")
+trackway("Vod, Neva")
+trackway("Vod, Ingria")
+trackway("Karelia, Neva")
+trackway("Neva, Izhora")
+trackway("Izhora, Ladoga")
+trackway("Izhora, Ingria")
+trackway("Ingria, Tesovo")
+trackway("Tesovo, Novgorod")
+trackway("Novgorod, Sablia")
+trackway("Sablia, Shelon River")
+
+trackway("Gdov, Plyussa River")
+trackway("Plyussa River, Zheltsy")
+trackway("Plyussa River, Zhelcha River")
+trackway("Zhelcha River, Pskov")
+trackway("Pskov, Dubrovno")
+trackway("Dubrovno, Porkhov")
+trackway("Porkhov, Sorot River")
+trackway("Velikaya River, Velikiye Luki")
+
+let seaports = [
+	"Riga", "Pernau", "Leal", "Reval", "Narwia", "Luga", "Koporye", "Neva"
+].map(name => locmap[name]).sort((a,b)=>a-b)
+
+function dumplist(name, list) {
+	print(name + ":[")
+	for (let item of list)
+		print(JSON.stringify(item) + ",") 
+	print("],")
+}
+
+function seats(list) {
+	return list.split(", ").map(name => locmap[name]).sort((a,b)=>a-b)
+}
+
+let lords = [
+
+	{
+		side: "Teutonic",
+		name: "Andreas",
+		full_name: "Andreas von Felben",
+		title: "Landmeister in Livonia",
+		seats: seats("Riga, Wenden"),
+		marshal: 2,
+		fealty: 2,
+		service: 4,
+		lordship: 3,
+		command: 3,
+		forces: {
+			knights: 1,
+			sergeants: 2,
+			light_horse: 0,
+			men_at_arms: 1,
+			militia: 0,
+		},
+		assets: {
+			transport: 2,
+			prov: 2,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Teutonic",
+		name: "Heinrich",
+		full_name: "Heinrich",
+		title: "Bishop of Ösel-Wiek",
+		seats: seats("Leal"),
+		marshal: 0,
+		fealty: 3,
+		service: 4,
+		lordship: 2,
+		command: 1,
+		forces: {
+			knights: 1,
+			sergeants: 1,
+			light_horse: 0,
+			men_at_arms: 1,
+			militia: 0,
+		},
+		assets: {
+			ship: 1,
+			coin: 2,
+			prov: 1,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Teutonic",
+		name: "Hermann",
+		full_name: "Hermann",
+		title: "Bishop of Dorpat",
+		seats: seats("Dorpat, Odenpäh"),
+		marshal: 1,
+		fealty: 4,
+		service: 4,
+		lordship: 3,
+		command: 3,
+		forces: {
+			knights: 1,
+			sergeants: 1,
+			light_horse: 0,
+			men_at_arms: 2,
+			militia: 0,
+		},
+		assets: {
+			transport: 1,
+			coin: 1,
+			prov: 1,
+		},
+		ships: 0,
+	},
+
+	{
+		side: "Teutonic",
+		name: "Knud & Abel",
+		full_name: "Knud & Abel",
+		title: "Princes of Denmark",
+		seats: seats("Reval, Wesenburg"),
+		marshal: 0,
+		fealty: 2,
+		service: 3,
+		lordship: 3,
+		command: 2,
+		forces: {
+			knights: 1,
+			sergeants: 1,
+			light_horse: 3,
+			men_at_arms: 0,
+			militia: 0,
+		},
+		assets: {
+			ship: 2,
+			prov: 2,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Teutonic",
+		name: "Rudolf",
+		full_name: "Rudolf von Kassel",
+		title: "Castellan of Wenden",
+		seats: seats("Wenden"),
+		marshal: 0,
+		fealty: 5,
+		service: 2,
+		lordship: 1,
+		command: 3,
+		forces: {
+			knights: 1,
+			sergeants: 1,
+			light_horse: 0,
+			men_at_arms: 1,
+			militia: 0,
+		},
+		assets: {
+			transport: 1,
+			prov: 1,
+		},
+		ships: 0,
+	},
+
+	{
+		side: "Teutonic",
+		name: "Yaroslav",
+		full_name: "Yaroslav",
+		title: "Exile of Pskov",
+		seats: seats("Odenpäh, Pskov"),
+		marshal: 0,
+		fealty: 4,
+		service: 2,
+		lordship: 1,
+		command: 2,
+		forces: {
+			knights: 1,
+			sergeants: 0,
+			light_horse: 1,
+			men_at_arms: 1,
+			militia: 0,
+		},
+		assets: {
+			transport: 1,
+			prov: 1,
+		},
+		ships: 0,
+	},
+
+	{
+		side: "Russian",
+		name: "Aleksandr",
+		full_name: "Aleksandr",
+		title: "Prince of Novgorod",
+		seats: seats("Novgorod, Rusa"),
+		marshal: 2,
+		fealty: 0,
+		service: 6,
+		lordship: 4,
+		command: 3,
+		forces: {
+			knights: 3,
+			sergeants: 0,
+			light_horse: 0,
+			men_at_arms: 2,
+			militia: 0,
+		},
+		assets: {
+			transport: 2,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Russian",
+		name: "Andrey",
+		full_name: "Andrey",
+		title: "Prince of Suzdal",
+		seats: seats("Novgorod, Rusa"),
+		marshal: 1,
+		fealty: 4,
+		service: 5,
+		lordship: 3,
+		command: 2,
+		forces: {
+			knights: 3,
+			sergeants: 0,
+			light_horse: 0,
+			men_at_arms: 2,
+			militia: 0,
+		},
+		assets: {
+			transport: 2,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Russian",
+		name: "Domash",
+		full_name: "Domash",
+		title: "Tysyatskiy of Novgorod",
+		seats: seats("Novgorod"),
+		marshal: 0,
+		fealty: 4,
+		service: 4,
+		lordship: 2,
+		command: 2,
+		forces: {
+			knights: 0,
+			sergeants: 1,
+			light_horse: 1,
+			men_at_arms: 2,
+			militia: 1,
+		},
+		assets: {
+			transport: 4,
+			prov: 4,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Russian",
+		name: "Gavrilo",
+		full_name: "Gavrilo",
+		title: "Voyevoda of Pskov",
+		seats: seats("Pskov"),
+		marshal: 0,
+		fealty: 3,
+		service: 4,
+		lordship: 3,
+		command: 2,
+		forces: {
+			knights: 1,
+			sergeants: 0,
+			light_horse: 1,
+			men_at_arms: 1,
+			militia: 1,
+		},
+		assets: {
+			transport: 2,
+			coin: 1,
+			prov: 2,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Russian",
+		name: "Karelians",
+		full_name: "Karelians",
+		title: "Tributaries of Novgorod",
+		seats: seats("Ladoga"),
+		marshal: 0,
+		fealty: 4,
+		service: 2,
+		lordship: 1,
+		command: 2,
+		forces: {
+			knights: 0,
+			sergeants: 0,
+			light_horse: 1,
+			men_at_arms: 0,
+			militia: 4,
+		},
+		assets: {
+			transport: 1,
+		},
+		ships: 1,
+	},
+
+	{
+		side: "Russian",
+		name: "Vladislav",
+		full_name: "Vladislav",
+		title: "Bailiff of Ladoga",
+		seats: seats("Ladoga"),
+		marshal: 0,
+		fealty: 5,
+		service: 3,
+		lordship: 2,
+		command: 3,
+		forces: {
+			knights: 0,
+			sergeants: 1,
+			light_horse: 1,
+			men_at_arms: 2,
+			militia: 0,
+		},
+		assets: {
+			transport: 1,
+			prov: 1,
+		},
+		ships: 1,
+	},
+
+]
+
+let vassals = []
+for (let lord of lords)
+	lord.vassals = []
+
+function vassal(lord, service, name, forces, capability) {
+	let lord_id = lords.findIndex(x => x.name === lord)
+	if (lord_id < 0) throw Error("no such lord", lord)
+	lords[lord_id].vassals.push(vassals.length)
+	vassals.push({ lord: lord_id, name, service, forces, capability })
+}
+
+vassal("Andreas", 1, "Lettgallian Auxiliaries", { light_horse: 1, militia: 1 })
+vassal("Andreas", 2, "Summer Crusaders", { knights: 3 }, "Crusade")
+vassal("Andreas", 3, "Teutonic Vassals", { knights: 1, men_at_arms: 2 })
+
+vassal("Heinrich", 2, "Heinrich von Lode", { knights: 1, men_at_arms: 1 })
+vassal("Heinrich", 2, "Odward von Lode", { knights: 1, men_at_arms: 1 })
+
+vassal("Hermann", 2, "Helmond von Lüneburg", { knights: 1, men_at_arms: 1 })
+vassal("Hermann", 2, "Johannes von Dolen", { knights: 1, men_at_arms: 1 })
+vassal("Hermann", 1, "Ugaunian Auxiliaries", { light_horse: 1, militia: 1 })
+
+vassal("Knud & Abel", 2, "Dietrich von Kivel", { knights: 1, men_at_arms: 1 })
+vassal("Knud & Abel", 1, "Estonian Auxiliaries", { light_horse: 1, militia: 1 })
+vassal("Knud & Abel", 2, "Otto von Lüneburg", { knights: 1, men_at_arms: 1 })
+
+vassal("Rudolf", 2, "Ex-Sword Brethren", { knights: 1, sergeants: 1 })
+vassal("Rudolf", 2, "Jerwen Teutonic Vassals", { knights: 1, men_at_arms: 1 })
+vassal("Rudolf", 2, "Summer Crusaders", { knights: 2 }, "Crusade")
+
+vassal("Yaroslav", 1, "Mstislavich Partisans", { militia: 2 })
+
+vassal("Aleksandr", 3, "Mongols", { asiatic_horse: 2 }, "Steppe Warriors")
+vassal("Aleksandr", 3, "Mongols", { asiatic_horse: 2 }, "Steppe Warriors")
+vassal("Aleksandr", 4, "Pereyaslavl", { men_at_arms: 1 })
+vassal("Aleksandr", 3, "Rostov", { men_at_arms: 1 })
+vassal("Aleksandr", 3, "Yaroslavl", { men_at_arms: 1 })
+
+vassal("Andrey", 3, "Kipchaqs", { asiatic_horse: 3 }, "Steppe Warriors")
+vassal("Andrey", 3, "Kipchaqs", { asiatic_horse: 3 }, "Steppe Warriors")
+vassal("Andrey", 4, "Suzdal", { men_at_arms: 1 })
+vassal("Andrey", 4, "Vladimir", { men_at_arms: 1 })
+
+vassal("Domash", 2, "Novgorod", { militia: 2 })
+vassal("Domash", 2, "Novgorod", { militia: 2 })
+vassal("Domash", 2, "Novgorod", { militia: 2 })
+
+vassal("Gavrilo", 1, "Borderland Russians", { light_horse: 1, militia: 1 })
+vassal("Gavrilo", 2, "Pskov Militia", { militia: 2 })
+vassal("Gavrilo", 4, "Pskov", { men_at_arms: 1 })
+
+vassal("Vladislav", 1, "Izhoran Auxiliaries", { militia: 1 })
+vassal("Vladislav", 1, "Ingrian Auxiliaries", { militia: 1 })
+vassal("Vladislav", 1, "Vepsian Auxiliaries", { militia: 1 })
+vassal("Vladislav", 1, "Vodian Auxiliaries", { militia: 1 })
+
+function to_path(name) {
+	return name
+		.toLowerCase()
+		.replace(/&/g, 'and')
+		.replace(/[ -]/g, '_')
+		.replace(/ü/g, 'u')
+		.replace(/ö/g, 'o')
+		.replace(/ä/g, 'a')
+}
+
+let lord_service = {Russian:[],Teutonic:[]}
+let vassal_service = {Russian:[],Teutonic:[]}
+
+let last_path, last_side, last_ix
+
+last_path = null
+last_side = null
+lords.forEach((lord,id) => {
+	let side = lord.side
+	let path = "counters300/lord_" + side.toLowerCase() + "_" + to_path(lord.name)
+	if (side !== last_side) {
+		last_ix = 0
+		last_side = side
+	}
+	lord.image = last_ix
+	if (path !== last_path) {
+		last_ix++
+		last_path = path
+		lord_service[side].push(path + ".a.png")
+		lord_service[side].push(path + ".b.png")
+	}
+})
+
+last_path = null
+last_side = null
+vassals.forEach((vassal,id) => {
+	let lord = lords[vassal.lord]
+	let side = lord.side
+	let path = "counters300/vassal_" + side.toLowerCase() + "_" + to_path(lord.name) + "_" + to_path(vassal.name)
+	if (side !== last_side) {
+		last_ix = 0
+		last_side = side
+	}
+	vassal.image = last_ix
+	if (path !== last_path) {
+		last_ix++
+		last_path = path
+		vassal_service[side].push(path + ".a.png")
+		vassal_service[side].push(path + ".b.png")
+	}
+})
+
+let script = []
+script.push("mkdir -p service300")
+script.push("montage -mode concatenate -tile 2x " + lord_service.Teutonic.join(" ") + " service300/service_lords_teutonic.png")
+script.push("montage -mode concatenate -tile 2x " + vassal_service.Teutonic.join(" ") + " service300/service_vassals_teutonic.png")
+script.push("montage -mode concatenate -tile 2x " + lord_service.Russian.join(" ") + " service300/service_lords_russian.png")
+script.push("montage -mode concatenate -tile 2x " + vassal_service.Russian.join(" ") + " service300/service_vassals_russian.png")
+
+print("const data = {")
+print("seaports:" + JSON.stringify(seaports) + ",")
+dumplist("locales", locales)
+dumplist("ways", ways)
+dumplist("lords", lords)
+dumplist("vassals", vassals)
+print("}")
+print("if (typeof module !== 'undefined') module.exports = data")
+
+fs.writeFileSync("build_counters3.sh", script.join("\n") + "\n")
+fs.writeFileSync("../data.js", data.join("\n") + "\n")
diff --git a/tools/scale.sh b/tools/scale.sh
new file mode 100644
index 0000000..a54d41e
--- /dev/null
+++ b/tools/scale.sh
@@ -0,0 +1,18 @@
+mkdir -p output75
+mkdir -p output150
+for F in output300/*.png
+do
+	echo $F
+
+	O=${F/300/75}
+	gm convert $F -colorspace RGB -resize 25% -colorspace sRGB $O.tmp
+	rm -f $O
+	zopflipng -m $O.tmp $O
+	rm -f $O.tmp
+
+	O=${F/300/150}
+	gm convert $F -colorspace RGB -resize 50% -colorspace sRGB $O.tmp
+	rm -f $O
+	zopflipng -m $O.tmp $O
+	rm -f $O.tmp
+done
diff --git a/tools/svgo.config.js b/tools/svgo.config.js
new file mode 100644
index 0000000..ef3339c
--- /dev/null
+++ b/tools/svgo.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+	js2svg: {
+		'finalNewline': true,
+	},
+	plugins: [
+		'preset-default',
+		'convertStyleToAttrs',
+		'sortAttrs',
+	],
+}
-- 
cgit v1.2.3