@orbat-mapper/control-measures 0.2.0-alpha.0 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,13 @@
1
1
  # @orbat-mapper/control-measures
2
2
 
3
- A TypeScript library for generating tactical control measures as GeoJSON, according to MIL-STD-2525 and APP-6 conventions.
3
+ Generates tactical control measures as GeoJSON, following MIL-STD-2525 and
4
+ APP-6 conventions.
4
5
 
5
- ## Features
6
-
7
- - Framework-agnostic: outputs plain GeoJSON.
8
- - Type-safe: full TypeScript typings.
9
- - Map-friendly: works with Leaflet, MapLibre, OpenLayers, or any GeoJSON consumer.
10
- - Registry-driven: a single `renderControlMeasure(cm, options)` entry point, plus a metadata catalog for discovering every measure and its options.
11
- - Validation controls: optional `validationMode` per render.
6
+ The output is plain GeoJSON, so it drops into Leaflet, MapLibre, OpenLayers,
7
+ or any other consumer without a mapping dependency of its own. Everything
8
+ goes through one entry point, `renderControlMeasure(cm, options)`, backed by
9
+ a metadata catalog for discovering the available measures and their options,
10
+ plus an optional `validationMode` per render for catching bad input.
12
11
 
13
12
  ## Installation
14
13
 
@@ -22,8 +21,8 @@ pnpm add @orbat-mapper/control-measures
22
21
 
23
22
  ## Quick Start
24
23
 
25
- The library is registry-driven. Describe a control measure as a `ControlMeasure`
26
- object — its `kind`, `controlPoints`, and optional `options` — then render it:
24
+ Describe a control measure as a `ControlMeasure` object — `kind`,
25
+ `controlPoints`, and optional `options` — then render it:
27
26
 
28
27
  ```ts
29
28
  import { renderControlMeasure, type ControlMeasure } from "@orbat-mapper/control-measures";
@@ -49,8 +48,7 @@ const render = renderControlMeasure(mainAttack, { validationMode: "warn" });
49
48
 
50
49
  ## Discovering measures and options
51
50
 
52
- Rather than importing per-measure generators, use the catalog to enumerate the
53
- available control measures and their parameters:
51
+ Skip the per-measure imports and query the catalog instead:
54
52
 
55
53
  ```ts
56
54
  import {
@@ -82,28 +80,79 @@ useful for strict integrations and tests.
82
80
 
83
81
  ## Supported control measures
84
82
 
85
- | `kind` | Measure |
86
- | --- | --- |
87
- | `ambush` | Ambush |
88
- | `search-area` | Search Area |
89
- | `main-attack` | Main Attack |
90
- | `supporting-attack` | Supporting Attack |
91
- | `airborne-attack` | Airborne Attack |
92
- | `attack-helicopter` | Attack Helicopter |
93
- | `support-by-fire` | Support by Fire |
94
- | `attack-by-fire` | Attack by Fire |
95
- | `flot` | FLOT (Forward Line of Own Troops) |
96
- | `breach` | Breach |
97
- | `bypass` | Bypass |
98
- | `canalize` | Canalize |
99
- | `fortified-line` | Fortified Line |
100
- | `fortified-area` | Fortified Area |
101
- | `block` | Block |
102
- | `disrupt` | Disrupt |
103
- | `fix` | Fix |
104
- | `obstacle-bypass-easy` | Obstacle Bypass (Easy) |
105
- | `obstacle-bypass-difficult` | Obstacle Bypass (Difficult) |
106
- | `obstacle-bypass-impossible` | Obstacle Bypass (Impossible) |
83
+ Grouped by APP-6/MIL-STD-2525 entity type, with each measure's 2525E symbol
84
+ code.
85
+
86
+ <!-- AUTO-GENERATED:measures-table -->
87
+
88
+ ### Maneuver Lines
89
+
90
+ | Preview | `kind` | Measure | 2525E Value |
91
+ | --- | --- | --- | --- |
92
+ | <img src="media/flot.svg" width="96" height="48" alt=""> | `flot` | Forward line of own troops (FLOT) | `140100` |
93
+ | <img src="media/final-protective-fire-left.svg" width="96" height="48" alt=""> | `final-protective-fire-left` | Final Protective Fire (Left) | `140501` |
94
+ | <img src="media/final-protective-fire-right.svg" width="96" height="48" alt=""> | `final-protective-fire-right` | Final Protective Fire (Right) | `140502` |
95
+ | <img src="media/principal-direction-of-fire.svg" width="96" height="48" alt=""> | `principal-direction-of-fire` | Principal Direction of Fire | `140503` |
96
+ | <img src="media/ambush.svg" width="96" height="48" alt=""> | `ambush` | Ambush | `141700` |
97
+
98
+ ### Maneuver Areas
99
+
100
+ | Preview | `kind` | Measure | 2525E Value |
101
+ | --- | --- | --- | --- |
102
+ | <img src="media/fortified-area.svg" width="96" height="48" alt=""> | `fortified-area` | Fortified Area | `151000` |
103
+ | <img src="media/airborne-attack.svg" width="96" height="48" alt=""> | `airborne-attack` | Airborne Attack | `151401` |
104
+ | <img src="media/attack-helicopter.svg" width="96" height="48" alt=""> | `attack-helicopter` | Attack Helicopter | `151402` |
105
+ | <img src="media/main-attack.svg" width="96" height="48" alt=""> | `main-attack` | Main Attack | `151403` |
106
+ | <img src="media/supporting-attack.svg" width="96" height="48" alt=""> | `supporting-attack` | Supporting Attack | `151404` |
107
+ | <img src="media/attack-by-fire.svg" width="96" height="48" alt=""> | `attack-by-fire` | Attack by Fire | `152000` |
108
+ | <img src="media/support-by-fire.svg" width="96" height="48" alt=""> | `support-by-fire` | Support By Fire | `152100` |
109
+ | <img src="media/search-area.svg" width="96" height="48" alt=""> | `search-area` | Search Area | `152200` |
110
+
111
+ ### Protection Areas
112
+
113
+ | Preview | `kind` | Measure | 2525E Value |
114
+ | --- | --- | --- | --- |
115
+ | <img src="media/block.svg" width="96" height="48" alt=""> | `block` | Block | `270501` |
116
+ | <img src="media/disrupt.svg" width="96" height="48" alt=""> | `disrupt` | Disrupt | `270502` |
117
+ | <img src="media/fix.svg" width="96" height="48" alt=""> | `fix` | Fix | `270503` |
118
+ | <img src="media/turn.svg" width="96" height="48" alt=""> | `turn` | Turn | `270504` |
119
+ | <img src="media/obstacle-bypass-easy.svg" width="96" height="48" alt=""> | `obstacle-bypass-easy` | Obstacle Bypass Easy | `270601` |
120
+ | <img src="media/obstacle-bypass-difficult.svg" width="96" height="48" alt=""> | `obstacle-bypass-difficult` | Obstacle Bypass Difficult | `270602` |
121
+ | <img src="media/obstacle-bypass-impossible.svg" width="96" height="48" alt=""> | `obstacle-bypass-impossible` | Obstacle Bypass Impossible | `270603` |
122
+
123
+ ### Protection Lines
124
+
125
+ | Preview | `kind` | Measure | 2525E Value |
126
+ | --- | --- | --- | --- |
127
+ | <img src="media/antitank-ditch-under-construction.svg" width="96" height="48" alt=""> | `antitank-ditch-under-construction` | Ditch - Under Construction | `290201` |
128
+ | <img src="media/antitank-ditch-completed.svg" width="96" height="48" alt=""> | `antitank-ditch-completed` | Ditch - Completed | `290202` |
129
+ | <img src="media/antitank-wall.svg" width="96" height="48" alt=""> | `antitank-wall` | Antitank Wall | `290204` |
130
+ | <img src="media/fortified-line.svg" width="96" height="48" alt=""> | `fortified-line` | Fortified Line | `290900` |
131
+
132
+ ### Mission Tasks
133
+
134
+ | Preview | `kind` | Measure | 2525E Value |
135
+ | --- | --- | --- | --- |
136
+ | <img src="media/block-mission-task.svg" width="96" height="48" alt=""> | `block-mission-task` | Block | `340100` |
137
+ | <img src="media/breach.svg" width="96" height="48" alt=""> | `breach` | Breach | `340200` |
138
+ | <img src="media/bypass.svg" width="96" height="48" alt=""> | `bypass` | Bypass | `340300` |
139
+ | <img src="media/canalize.svg" width="96" height="48" alt=""> | `canalize` | Canalize | `340400` |
140
+ | <img src="media/clear.svg" width="96" height="48" alt=""> | `clear` | Clear | `340500` |
141
+ | <img src="media/delay.svg" width="96" height="48" alt=""> | `delay` | Delay | `340800` |
142
+ | <img src="media/isolate.svg" width="96" height="48" alt=""> | `isolate` | Isolate | `341500` |
143
+
144
+ ### Generic Arrows
145
+
146
+ | Preview | `kind` | Measure | 2525E Value |
147
+ | --- | --- | --- | --- |
148
+ | <img src="media/block-arrow.svg" width="96" height="48" alt=""> | `block-arrow` | Block Arrow | `990001` |
149
+ | <img src="media/classic-arrow.svg" width="96" height="48" alt=""> | `classic-arrow` | Classic Arrow | `990002` |
150
+
151
+ <!-- /AUTO-GENERATED:measures-table -->
152
+
153
+ This table and the preview SVGs are generated; run
154
+ `pnpm --filter @orbat-mapper/control-measures generate-docs` after adding or
155
+ changing a measure to refresh both.
107
156
 
108
157
  ## License
109
158
 
package/dist/index.d.mts CHANGED
@@ -277,6 +277,91 @@ declare const DEFAULT_SUPPORTING_ATTACK_OPTIONS: Required<SupportingAttackOption
277
277
  */
278
278
  declare function createSupportingAttack(coordinates: Position[], options?: SupportingAttackOptions): FeatureCollection<LineString>;
279
279
  //#endregion
280
+ //#region src/generators/cm99-generic-arrows/classicArrow.d.ts
281
+ /**
282
+ * A plain, clean arrow for illustrations — not a doctrinal symbol. The shaft is
283
+ * a straight polyline through the drawn control points (Line1 contract) and the
284
+ * tip carries a solid triangular arrowhead. No wobble, no curve: the canonical
285
+ * "draw an arrow" graphic.
286
+ */
287
+ /**
288
+ * Arrowhead silhouette:
289
+ * - `triangle` — solid triangle with a flat back (the default).
290
+ * - `barbed` — swept-back barbs with a forward notch (classic arrowhead).
291
+ * - `concave` — a sharper barbed head with a deep forward notch.
292
+ * - `diamond` — a rhombus, widest at its middle.
293
+ * - `harpoon` — an asymmetric single-sided barb.
294
+ * - `swallowtail` — barbs that sweep back into two trailing tails.
295
+ * - `chevron` — a thick hollow "V".
296
+ * - `circle` — a round dot terminator.
297
+ * - `tee` — a flat crossbar terminator (no point).
298
+ * - `open` — an unfilled "V", drawn as two strokes meeting at the tip.
299
+ */
300
+ type ClassicArrowHeadStyle = "triangle" | "barbed" | "concave" | "diamond" | "harpoon" | "swallowtail" | "chevron" | "circle" | "tee" | "open";
301
+ interface ClassicArrowOptions {
302
+ /** Arrowhead silhouette. */
303
+ headStyle?: ClassicArrowHeadStyle;
304
+ /** Round the shaft's corners by curving it through the control points. */
305
+ smooth?: boolean;
306
+ /** Arrowhead length as a fraction of the total path length. */
307
+ headLength?: number;
308
+ /** Arrowhead base width as a fraction of the total path length. */
309
+ headWidth?: number;
310
+ }
311
+ /**
312
+ * Generates a GeoJSON FeatureCollection for the Classic Arrow: a shaft
313
+ * LineString (trimmed so it doesn't poke through the head) plus a head feature
314
+ * whose silhouette follows `headStyle` — a filled Polygon for the solid heads,
315
+ * or an open "V" LineString for `open`.
316
+ */
317
+ declare function createClassicArrow(coordinates: Position[], options?: ClassicArrowOptions): FeatureCollection<LineString | Polygon, {
318
+ part: "shaft" | "head";
319
+ fill?: boolean;
320
+ }>;
321
+ //#endregion
322
+ //#region src/generators/cm99-generic-arrows/blockArrow.d.ts
323
+ /**
324
+ * A solid "block" arrow for illustrations — not a doctrinal symbol. The whole
325
+ * arrow is one filled polygon: a shaft band of controllable width that runs
326
+ * through the drawn control points (Line1 contract) and flares into a wider
327
+ * triangular head at the tip.
328
+ */
329
+ /**
330
+ * Arrowhead silhouette (all carved from the one filled body):
331
+ * - `triangle` — a flat-backed triangular head (the default).
332
+ * - `barbed` — swept-back barbs with the shaft entering at a forward notch.
333
+ * - `concave` — a sharper barbed head with a deeper notch.
334
+ * - `diamond` — a kite that flares to its widest mid-head.
335
+ * - `harpoon` — an asymmetric single-sided barb.
336
+ * - `swallowtail` — barbs that sweep back into two trailing tails.
337
+ * - `tee` — a flat crossbar terminator (no point).
338
+ */
339
+ type BlockArrowHeadStyle = "triangle" | "barbed" | "concave" | "diamond" | "harpoon" | "swallowtail" | "tee";
340
+ interface BlockArrowOptions {
341
+ /** Shaft band width as a fraction of the total path length. */
342
+ shaftWidth?: number;
343
+ /** Arrowhead silhouette. */
344
+ headStyle?: BlockArrowHeadStyle;
345
+ /** Arrowhead base width as a fraction of the total path length. */
346
+ headWidth?: number;
347
+ /** Arrowhead length as a fraction of the total path length. */
348
+ headLength?: number;
349
+ /** Round the shaft's corners by curving it through the control points. */
350
+ smooth?: boolean;
351
+ /** Fill the body solid; when false the arrow is drawn as an outline only. */
352
+ filled?: boolean;
353
+ }
354
+ /**
355
+ * Generates a GeoJSON FeatureCollection for the Block Arrow: a single filled
356
+ * Polygon whose outline runs up the left side of the shaft, around the head
357
+ * (whose silhouette follows `headStyle`) to the tip and back, then down the
358
+ * right side of the shaft.
359
+ */
360
+ declare function createBlockArrow(coordinates: Position[], options?: BlockArrowOptions): FeatureCollection<Polygon, {
361
+ part: "body";
362
+ fill: boolean;
363
+ }>;
364
+ //#endregion
280
365
  //#region src/generators/cm15-maneuver-areas/airborneAttack.d.ts
281
366
  type AirborneAttackOptions = AttackOptions;
282
367
  declare const DEFAULT_AIRBORNE_ATTACK_OPTIONS: Required<AirborneAttackOptions>;
@@ -906,6 +991,8 @@ declare const DEFINITIONS: {
906
991
  }>>;
907
992
  "main-attack": ControlMeasureDefinition<"main-attack", typeof createMainAttack>;
908
993
  "supporting-attack": ControlMeasureDefinition<"supporting-attack", typeof createSupportingAttack>;
994
+ "classic-arrow": ControlMeasureDefinition<"classic-arrow", typeof createClassicArrow>;
995
+ "block-arrow": ControlMeasureDefinition<"block-arrow", typeof createBlockArrow>;
909
996
  "airborne-attack": ControlMeasureDefinition<"airborne-attack", typeof createAirborneAttack>;
910
997
  "attack-helicopter": ControlMeasureDefinition<"attack-helicopter", typeof createAttackHelicopter>;
911
998
  "support-by-fire": ControlMeasureDefinition<"support-by-fire", typeof createSupportByFire>;
package/dist/index.mjs CHANGED
@@ -1821,6 +1821,276 @@ const BLOCK = defineControlMeasure({
1821
1821
  rule: blockDrawRule
1822
1822
  });
1823
1823
  //#endregion
1824
+ //#region src/internal/sketch.ts
1825
+ /**
1826
+ * Projects `coordinates` and derives the arrow's tip, heading and total length.
1827
+ * Returns `null` for degenerate input (fewer than two points, a zero-length
1828
+ * path, or a zero-length final segment) so the caller can decide what to emit.
1829
+ */
1830
+ function arrowAxis(coordinates) {
1831
+ if (coordinates.length < 2) return null;
1832
+ const pts = coordinates.map((c) => project(c[0], c[1]));
1833
+ let pathLength = 0;
1834
+ for (let i = 0; i < pts.length - 1; i++) pathLength += Math.hypot(pts[i + 1][0] - pts[i][0], pts[i + 1][1] - pts[i][1]);
1835
+ const tip = pts[pts.length - 1];
1836
+ const prev = pts[pts.length - 2];
1837
+ const hx = tip[0] - prev[0];
1838
+ const hy = tip[1] - prev[1];
1839
+ const segLength = Math.hypot(hx, hy);
1840
+ if (segLength === 0 || pathLength === 0) return null;
1841
+ const dir = [hx / segLength, hy / segLength];
1842
+ const perp = [-dir[1], dir[0]];
1843
+ return {
1844
+ pts,
1845
+ pathLength,
1846
+ tip,
1847
+ dir,
1848
+ perp,
1849
+ segLength
1850
+ };
1851
+ }
1852
+ /**
1853
+ * Centripetal Catmull-Rom spline through `points`, sampled `samples` times per
1854
+ * segment. Endpoints are duplicated so the curve passes through them, so the
1855
+ * first and last points are preserved exactly. With `samples <= 1` (or fewer
1856
+ * than 3 points) it returns the input unchanged.
1857
+ */
1858
+ function catmullRom(points, samples) {
1859
+ if (points.length < 3 || samples <= 1) return points;
1860
+ const pts = [
1861
+ points[0],
1862
+ ...points,
1863
+ points[points.length - 1]
1864
+ ];
1865
+ const out = [points[0]];
1866
+ for (let i = 1; i < pts.length - 2; i++) {
1867
+ const p0 = pts[i - 1];
1868
+ const p1 = pts[i];
1869
+ const p2 = pts[i + 1];
1870
+ const p3 = pts[i + 2];
1871
+ for (let s = 1; s <= samples; s++) {
1872
+ const t = s / samples;
1873
+ const t2 = t * t;
1874
+ const t3 = t2 * t;
1875
+ const x = .5 * (2 * p1[0] + (-p0[0] + p2[0]) * t + (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3);
1876
+ const y = .5 * (2 * p1[1] + (-p0[1] + p2[1]) * t + (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3);
1877
+ out.push([x, y]);
1878
+ }
1879
+ }
1880
+ return out;
1881
+ }
1882
+ //#endregion
1883
+ //#region src/generators/cm99-generic-arrows/blockArrow.ts
1884
+ const DEFAULT_BLOCK_ARROW_OPTIONS = {
1885
+ shaftWidth: .06,
1886
+ headStyle: "triangle",
1887
+ headWidth: .18,
1888
+ headLength: .22,
1889
+ smooth: false,
1890
+ filled: true
1891
+ };
1892
+ const BLOCK_ARROW_METADATA = {
1893
+ id: "block-arrow",
1894
+ name: "Block Arrow",
1895
+ description: "Solid filled arrow with a controllable shaft width and selectable head (triangle, barbed, concave, diamond, harpoon, swallowtail, tee). Not a doctrinal symbol.",
1896
+ entity: "Generic Arrows",
1897
+ entityType: "Illustrative Arrow",
1898
+ entitySubtype: "Block Arrow",
1899
+ value: "990001",
1900
+ minCoordinates: 2,
1901
+ geometry: "line",
1902
+ geometryTypes: ["Polygon"],
1903
+ drawRule: "Line1",
1904
+ params: [
1905
+ {
1906
+ key: "shaftWidth",
1907
+ label: "Shaft width",
1908
+ description: "Shaft band width as a fraction of the total path length.",
1909
+ type: "number",
1910
+ min: .01,
1911
+ max: .3,
1912
+ step: .01
1913
+ },
1914
+ {
1915
+ key: "headStyle",
1916
+ label: "Head style",
1917
+ description: "Arrowhead silhouette.",
1918
+ type: "enum",
1919
+ options: [
1920
+ {
1921
+ label: "Triangle",
1922
+ value: "triangle"
1923
+ },
1924
+ {
1925
+ label: "Barbed",
1926
+ value: "barbed"
1927
+ },
1928
+ {
1929
+ label: "Concave",
1930
+ value: "concave"
1931
+ },
1932
+ {
1933
+ label: "Diamond",
1934
+ value: "diamond"
1935
+ },
1936
+ {
1937
+ label: "Harpoon",
1938
+ value: "harpoon"
1939
+ },
1940
+ {
1941
+ label: "Swallowtail",
1942
+ value: "swallowtail"
1943
+ },
1944
+ {
1945
+ label: "Tee",
1946
+ value: "tee"
1947
+ }
1948
+ ]
1949
+ },
1950
+ {
1951
+ key: "headWidth",
1952
+ label: "Head width",
1953
+ description: "Arrowhead base width as a fraction of the total path length.",
1954
+ type: "number",
1955
+ min: .05,
1956
+ max: .5,
1957
+ step: .01
1958
+ },
1959
+ {
1960
+ key: "headLength",
1961
+ label: "Head length",
1962
+ description: "Arrowhead length as a fraction of the total path length.",
1963
+ type: "number",
1964
+ min: .05,
1965
+ max: .5,
1966
+ step: .01
1967
+ },
1968
+ {
1969
+ key: "smooth",
1970
+ label: "Smooth",
1971
+ description: "Round the shaft's corners by curving it through the control points.",
1972
+ type: "boolean"
1973
+ },
1974
+ {
1975
+ key: "filled",
1976
+ label: "Filled",
1977
+ description: "Fill the body solid; when off the arrow is drawn as an outline only.",
1978
+ type: "boolean"
1979
+ }
1980
+ ]
1981
+ };
1982
+ /**
1983
+ * Generates a GeoJSON FeatureCollection for the Block Arrow: a single filled
1984
+ * Polygon whose outline runs up the left side of the shaft, around the head
1985
+ * (whose silhouette follows `headStyle`) to the tip and back, then down the
1986
+ * right side of the shaft.
1987
+ */
1988
+ function createBlockArrow(coordinates, options = {}) {
1989
+ const { shaftWidth, headStyle, headWidth, headLength, smooth, filled } = {
1990
+ ...DEFAULT_BLOCK_ARROW_OPTIONS,
1991
+ ...options
1992
+ };
1993
+ const axis = arrowAxis(coordinates);
1994
+ if (!axis) return {
1995
+ type: "FeatureCollection",
1996
+ features: []
1997
+ };
1998
+ const { pts, pathLength, tip, dir, perp, segLength } = axis;
1999
+ const headLen = Math.min(pathLength * headLength, segLength * .95);
2000
+ const halfShaft = pathLength * shaftWidth / 2;
2001
+ const halfHead = pathLength * headWidth / 2;
2002
+ const onAxis = (d, side = 0) => [tip[0] - dir[0] * d + perp[0] * side, tip[1] - dir[1] * d + perp[1] * side];
2003
+ let shaftEndDist;
2004
+ let headPts;
2005
+ switch (headStyle) {
2006
+ case "barbed":
2007
+ shaftEndDist = headLen * .55;
2008
+ headPts = [
2009
+ onAxis(headLen, halfHead),
2010
+ tip,
2011
+ onAxis(headLen, -halfHead)
2012
+ ];
2013
+ break;
2014
+ case "concave":
2015
+ shaftEndDist = headLen * .3;
2016
+ headPts = [
2017
+ onAxis(headLen, halfHead),
2018
+ tip,
2019
+ onAxis(headLen, -halfHead)
2020
+ ];
2021
+ break;
2022
+ case "diamond":
2023
+ shaftEndDist = headLen;
2024
+ headPts = [
2025
+ onAxis(headLen * .5, halfHead),
2026
+ tip,
2027
+ onAxis(headLen * .5, -halfHead)
2028
+ ];
2029
+ break;
2030
+ case "harpoon":
2031
+ shaftEndDist = headLen;
2032
+ headPts = [onAxis(headLen, halfHead), tip];
2033
+ break;
2034
+ case "swallowtail": {
2035
+ const tailDist = Math.min(headLen * 1.25, segLength * .95);
2036
+ shaftEndDist = headLen * .6;
2037
+ headPts = [
2038
+ onAxis(tailDist, halfHead),
2039
+ tip,
2040
+ onAxis(tailDist, -halfHead)
2041
+ ];
2042
+ break;
2043
+ }
2044
+ case "tee":
2045
+ shaftEndDist = headLen * .4;
2046
+ headPts = [
2047
+ onAxis(headLen * .4, halfHead),
2048
+ onAxis(0, halfHead),
2049
+ onAxis(0, -halfHead),
2050
+ onAxis(headLen * .4, -halfHead)
2051
+ ];
2052
+ break;
2053
+ default:
2054
+ shaftEndDist = headLen;
2055
+ headPts = [
2056
+ onAxis(headLen, halfHead),
2057
+ tip,
2058
+ onAxis(headLen, -halfHead)
2059
+ ];
2060
+ break;
2061
+ }
2062
+ let spine = [...pts.slice(0, -1), onAxis(shaftEndDist)];
2063
+ if (smooth) spine = catmullRom(spine, 12);
2064
+ const leftSide = offsetPolyline(spine, halfShaft);
2065
+ const rightSide = offsetPolyline(spine, -halfShaft);
2066
+ const ring = [
2067
+ ...leftSide,
2068
+ ...headPts,
2069
+ ...rightSide.slice().reverse()
2070
+ ];
2071
+ ring.push(ring[0]);
2072
+ return {
2073
+ type: "FeatureCollection",
2074
+ features: [{
2075
+ type: "Feature",
2076
+ properties: {
2077
+ part: "body",
2078
+ fill: filled
2079
+ },
2080
+ geometry: {
2081
+ type: "Polygon",
2082
+ coordinates: [ring.map((p) => unproject(p[0], p[1]))]
2083
+ }
2084
+ }]
2085
+ };
2086
+ }
2087
+ const BLOCK_ARROW = defineControlMeasure({
2088
+ metadata: BLOCK_ARROW_METADATA,
2089
+ generator: createBlockArrow,
2090
+ defaultOptions: DEFAULT_BLOCK_ARROW_OPTIONS,
2091
+ rule: line1DrawRule
2092
+ });
2093
+ //#endregion
1824
2094
  //#region src/internal/angle-utils.ts
1825
2095
  /**
1826
2096
  * Wraps an angle (radians) into the half-open range (-π, π].
@@ -2323,6 +2593,286 @@ const CANALIZE = defineControlMeasure({
2323
2593
  rule: point12DrawRule
2324
2594
  });
2325
2595
  //#endregion
2596
+ //#region src/generators/cm99-generic-arrows/classicArrow.ts
2597
+ const DEFAULT_CLASSIC_ARROW_OPTIONS = {
2598
+ headStyle: "triangle",
2599
+ smooth: false,
2600
+ headLength: .2,
2601
+ headWidth: .14
2602
+ };
2603
+ const CLASSIC_ARROW_METADATA = {
2604
+ id: "classic-arrow",
2605
+ name: "Classic Arrow",
2606
+ description: "Clean straight arrow with a selectable head (triangle, barbed, concave, diamond, harpoon, swallowtail, chevron, circle, tee, open). Not a doctrinal symbol.",
2607
+ entity: "Generic Arrows",
2608
+ entityType: "Illustrative Arrow",
2609
+ entitySubtype: "Classic Arrow",
2610
+ value: "990002",
2611
+ minCoordinates: 2,
2612
+ geometry: "line",
2613
+ geometryTypes: ["LineString", "Polygon"],
2614
+ drawRule: "Line1",
2615
+ params: [
2616
+ {
2617
+ key: "smooth",
2618
+ label: "Smooth",
2619
+ description: "Round the shaft's corners by curving it through the control points.",
2620
+ type: "boolean"
2621
+ },
2622
+ {
2623
+ key: "headStyle",
2624
+ label: "Head style",
2625
+ description: "Arrowhead silhouette.",
2626
+ type: "enum",
2627
+ options: [
2628
+ {
2629
+ label: "Triangle",
2630
+ value: "triangle"
2631
+ },
2632
+ {
2633
+ label: "Barbed",
2634
+ value: "barbed"
2635
+ },
2636
+ {
2637
+ label: "Concave",
2638
+ value: "concave"
2639
+ },
2640
+ {
2641
+ label: "Diamond",
2642
+ value: "diamond"
2643
+ },
2644
+ {
2645
+ label: "Harpoon",
2646
+ value: "harpoon"
2647
+ },
2648
+ {
2649
+ label: "Swallowtail",
2650
+ value: "swallowtail"
2651
+ },
2652
+ {
2653
+ label: "Chevron",
2654
+ value: "chevron"
2655
+ },
2656
+ {
2657
+ label: "Circle",
2658
+ value: "circle"
2659
+ },
2660
+ {
2661
+ label: "Tee",
2662
+ value: "tee"
2663
+ },
2664
+ {
2665
+ label: "Open",
2666
+ value: "open"
2667
+ }
2668
+ ]
2669
+ },
2670
+ {
2671
+ key: "headLength",
2672
+ label: "Head length",
2673
+ description: "Arrowhead length as a fraction of the total path length.",
2674
+ type: "number",
2675
+ min: .05,
2676
+ max: .5,
2677
+ step: .01
2678
+ },
2679
+ {
2680
+ key: "headWidth",
2681
+ label: "Head width",
2682
+ description: "Arrowhead base width as a fraction of the total path length.",
2683
+ type: "number",
2684
+ min: .05,
2685
+ max: .5,
2686
+ step: .01
2687
+ }
2688
+ ]
2689
+ };
2690
+ /**
2691
+ * Generates a GeoJSON FeatureCollection for the Classic Arrow: a shaft
2692
+ * LineString (trimmed so it doesn't poke through the head) plus a head feature
2693
+ * whose silhouette follows `headStyle` — a filled Polygon for the solid heads,
2694
+ * or an open "V" LineString for `open`.
2695
+ */
2696
+ function createClassicArrow(coordinates, options = {}) {
2697
+ if (coordinates.length < 2) return {
2698
+ type: "FeatureCollection",
2699
+ features: []
2700
+ };
2701
+ const { headStyle, smooth, headLength, headWidth } = {
2702
+ ...DEFAULT_CLASSIC_ARROW_OPTIONS,
2703
+ ...options
2704
+ };
2705
+ const axis = arrowAxis(coordinates);
2706
+ if (!axis) return {
2707
+ type: "FeatureCollection",
2708
+ features: [{
2709
+ type: "Feature",
2710
+ properties: { part: "shaft" },
2711
+ geometry: {
2712
+ type: "LineString",
2713
+ coordinates: coordinates.map((c) => [c[0], c[1]])
2714
+ }
2715
+ }]
2716
+ };
2717
+ const { pts, pathLength, tip, dir, perp, segLength } = axis;
2718
+ const headLen = Math.min(pathLength * headLength, segLength * .95);
2719
+ const headHalf = pathLength * headWidth / 2;
2720
+ const onAxis = (d) => [tip[0] - dir[0] * d, tip[1] - dir[1] * d];
2721
+ const off = (p, s) => [p[0] + perp[0] * s, p[1] + perp[1] * s];
2722
+ const base = onAxis(headLen);
2723
+ const cornerL = off(base, headHalf);
2724
+ const cornerR = off(base, -headHalf);
2725
+ const ll = (p) => unproject(p[0], p[1]);
2726
+ let headRing;
2727
+ let headFilled = true;
2728
+ let shaftEnd = base;
2729
+ switch (headStyle) {
2730
+ case "barbed": {
2731
+ const notch = onAxis(headLen * .55);
2732
+ headRing = [
2733
+ tip,
2734
+ cornerL,
2735
+ notch,
2736
+ cornerR
2737
+ ];
2738
+ shaftEnd = notch;
2739
+ break;
2740
+ }
2741
+ case "concave": {
2742
+ const notch = onAxis(headLen * .3);
2743
+ headRing = [
2744
+ tip,
2745
+ cornerL,
2746
+ notch,
2747
+ cornerR
2748
+ ];
2749
+ shaftEnd = notch;
2750
+ break;
2751
+ }
2752
+ case "harpoon":
2753
+ headRing = [
2754
+ tip,
2755
+ cornerL,
2756
+ base
2757
+ ];
2758
+ shaftEnd = base;
2759
+ break;
2760
+ case "circle": {
2761
+ const radius = headHalf;
2762
+ const center = onAxis(radius);
2763
+ const segments = 24;
2764
+ headRing = [];
2765
+ for (let i = 0; i < segments; i++) {
2766
+ const a = i / segments * Math.PI * 2;
2767
+ headRing.push([center[0] + Math.cos(a) * radius, center[1] + Math.sin(a) * radius]);
2768
+ }
2769
+ shaftEnd = onAxis(Math.min(radius * 2, segLength * .95));
2770
+ break;
2771
+ }
2772
+ case "diamond": {
2773
+ const widest = onAxis(headLen * .5);
2774
+ headRing = [
2775
+ tip,
2776
+ off(widest, headHalf),
2777
+ base,
2778
+ off(widest, -headHalf)
2779
+ ];
2780
+ shaftEnd = base;
2781
+ break;
2782
+ }
2783
+ case "swallowtail": {
2784
+ const tail = onAxis(Math.min(headLen * 1.25, segLength * .95));
2785
+ const notch = onAxis(headLen * .6);
2786
+ headRing = [
2787
+ tip,
2788
+ off(tail, headHalf),
2789
+ notch,
2790
+ off(tail, -headHalf)
2791
+ ];
2792
+ shaftEnd = notch;
2793
+ break;
2794
+ }
2795
+ case "chevron": {
2796
+ const innerApex = onAxis(headLen * .5);
2797
+ headRing = [
2798
+ tip,
2799
+ cornerL,
2800
+ off(base, headHalf * .45),
2801
+ innerApex,
2802
+ off(base, -headHalf * .45),
2803
+ cornerR
2804
+ ];
2805
+ shaftEnd = innerApex;
2806
+ break;
2807
+ }
2808
+ case "tee": {
2809
+ const barBack = onAxis(headLen * .4);
2810
+ headRing = [
2811
+ off(tip, headHalf),
2812
+ off(tip, -headHalf),
2813
+ off(barBack, -headHalf),
2814
+ off(barBack, headHalf)
2815
+ ];
2816
+ shaftEnd = barBack;
2817
+ break;
2818
+ }
2819
+ case "open":
2820
+ headRing = [
2821
+ cornerL,
2822
+ tip,
2823
+ cornerR
2824
+ ];
2825
+ headFilled = false;
2826
+ shaftEnd = tip;
2827
+ break;
2828
+ default:
2829
+ headRing = [
2830
+ tip,
2831
+ cornerL,
2832
+ cornerR
2833
+ ];
2834
+ shaftEnd = base;
2835
+ break;
2836
+ }
2837
+ const shaftVertices = [...pts.slice(0, -1), shaftEnd];
2838
+ const shaftPts = smooth ? catmullRom(shaftVertices, 12) : shaftVertices;
2839
+ const headFeature = headFilled ? {
2840
+ type: "Feature",
2841
+ properties: {
2842
+ part: "head",
2843
+ fill: true
2844
+ },
2845
+ geometry: {
2846
+ type: "Polygon",
2847
+ coordinates: [[...headRing, headRing[0]].map(ll)]
2848
+ }
2849
+ } : {
2850
+ type: "Feature",
2851
+ properties: { part: "head" },
2852
+ geometry: {
2853
+ type: "LineString",
2854
+ coordinates: headRing.map(ll)
2855
+ }
2856
+ };
2857
+ return {
2858
+ type: "FeatureCollection",
2859
+ features: [{
2860
+ type: "Feature",
2861
+ properties: { part: "shaft" },
2862
+ geometry: {
2863
+ type: "LineString",
2864
+ coordinates: shaftPts.map(ll)
2865
+ }
2866
+ }, headFeature]
2867
+ };
2868
+ }
2869
+ const CLASSIC_ARROW = defineControlMeasure({
2870
+ metadata: CLASSIC_ARROW_METADATA,
2871
+ generator: createClassicArrow,
2872
+ defaultOptions: DEFAULT_CLASSIC_ARROW_OPTIONS,
2873
+ rule: line1DrawRule
2874
+ });
2875
+ //#endregion
2326
2876
  //#region src/generators/cm34-mission-tasks/clear.ts
2327
2877
  /**
2328
2878
  * Default options for the CLEAR symbol.
@@ -4712,6 +5262,8 @@ const DEFINITIONS = {
4712
5262
  "search-area": SEARCH_AREA,
4713
5263
  "main-attack": MAIN_ATTACK,
4714
5264
  "supporting-attack": SUPPORTING_ATTACK,
5265
+ "classic-arrow": CLASSIC_ARROW,
5266
+ "block-arrow": BLOCK_ARROW,
4715
5267
  "airborne-attack": AIRBORNE_ATTACK,
4716
5268
  "attack-helicopter": ATTACK_HELICOPTER,
4717
5269
  "support-by-fire": SUPPORT_BY_FIRE,
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Airborne Attack"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M74.60 35.40 L59.40 35.40 L40.40 12.60 L40.40 5.00 L21.40 24.00 L40.40 43.00 L40.40 35.40 L59.40 12.60 L74.60 12.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Ambush"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M51.17 20.83 L29.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M34.70 41.10 L29.00 43.00 L30.90 37.30" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M67.00 30.33 L65.75 29.90 L64.52 29.43 L63.33 28.94 L62.16 28.42 L61.02 27.87 L59.91 27.29 L58.82 26.69 L57.77 26.06 L56.74 25.39 L55.74 24.70 L54.77 23.98 L53.83 23.24 L52.91 22.46 L52.02 21.66 L51.17 20.83 L50.34 19.98 L49.54 19.09 L48.76 18.17 L48.02 17.23 L47.30 16.26 L46.61 15.26 L45.94 14.23 L45.31 13.18 L44.71 12.09 L44.13 10.98 L43.58 9.84 L43.06 8.67 L42.57 7.48 L42.10 6.25 L41.67 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M61.02 27.87 L64.82 24.07" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M55.74 24.70 L59.54 20.90" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M51.17 20.83 L54.97 17.03" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M47.30 16.26 L51.10 12.46" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M44.13 10.98 L47.93 7.18" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Ditch - Completed"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 25.93 L7.26 22.07 L9.52 25.93 L5.00 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M9.52 25.93 L11.79 22.07 L14.06 25.93 L9.52 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M14.06 25.93 L16.32 22.07 L18.58 25.93 L14.06 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M18.58 25.93 L20.84 22.07 L23.10 25.93 L18.58 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M23.10 25.93 L25.36 22.07 L27.64 25.93 L23.10 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M27.64 25.93 L29.90 22.07 L32.16 25.93 L27.64 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M32.16 25.93 L34.42 22.07 L36.68 25.93 L32.16 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M36.68 25.93 L38.94 22.07 L41.21 25.93 L36.68 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M41.21 25.93 L43.48 22.07 L45.74 25.93 L41.21 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M45.74 25.93 L48.00 22.07 L50.26 25.93 L45.74 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M50.26 25.93 L52.52 22.07 L54.79 25.93 L50.26 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M54.79 25.93 L57.06 22.07 L59.32 25.93 L54.79 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M59.32 25.93 L61.58 22.07 L63.84 25.93 L59.32 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M63.84 25.93 L66.10 22.07 L68.36 25.93 L63.84 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M68.36 25.93 L70.64 22.07 L72.90 25.93 L68.36 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M72.90 25.93 L75.16 22.07 L77.42 25.93 L72.90 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M77.42 25.93 L79.68 22.07 L81.94 25.93 L77.42 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M81.94 25.93 L84.21 22.07 L86.48 25.93 L81.94 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M86.48 25.93 L88.74 22.07 L91.00 25.93 L86.48 25.93 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Ditch - Under Construction"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 25.93 L91.00 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M5.00 25.93 L7.26 22.07 L9.52 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M9.52 25.93 L11.79 22.07 L14.06 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M14.06 25.93 L16.32 22.07 L18.58 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M18.58 25.93 L20.84 22.07 L23.10 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M23.10 25.93 L25.36 22.07 L27.64 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M27.64 25.93 L29.90 22.07 L32.16 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M32.16 25.93 L34.42 22.07 L36.68 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M36.68 25.93 L38.94 22.07 L41.21 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M41.21 25.93 L43.48 22.07 L45.74 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M45.74 25.93 L48.00 22.07 L50.26 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M50.26 25.93 L52.52 22.07 L54.79 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M54.79 25.93 L57.06 22.07 L59.32 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M59.32 25.93 L61.58 22.07 L63.84 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M63.84 25.93 L66.10 22.07 L68.36 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M68.36 25.93 L70.64 22.07 L72.90 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M72.90 25.93 L75.16 22.07 L77.42 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M77.42 25.93 L79.68 22.07 L81.94 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M81.94 25.93 L84.21 22.07 L86.48 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M86.48 25.93 L88.74 22.07 L91.00 25.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Antitank Wall"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 22.07 L7.47 22.07 L9.30 25.93 L11.13 22.07 L16.07 22.07 L17.90 25.93 L19.73 22.07 L24.67 22.07 L26.50 25.93 L28.33 22.07 L33.27 22.07 L35.10 25.93 L36.93 22.07 L41.87 22.07 L43.70 25.93 L45.53 22.07 L50.47 22.07 L52.30 25.93 L54.13 22.07 L59.07 22.07 L60.90 25.93 L62.73 22.07 L67.67 22.07 L69.50 25.93 L71.33 22.07 L76.27 22.07 L78.10 25.93 L79.93 22.07 L84.87 22.07 L86.70 25.93 L88.53 22.07 L91.00 22.07" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Attack by Fire"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M58.62 33.13 L67.00 33.13" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M38.87 13.38 L38.87 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M48.75 23.25 L29.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M58.62 33.13 L38.87 13.38" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M37.89 40.04 L29.00 43.00 L31.96 34.11" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Attack Helicopter"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M74.60 35.40 L59.40 35.40 L40.40 12.60 L40.40 5.00 L21.40 24.00 L40.40 43.00 L40.40 35.40 L59.40 12.60 L74.60 12.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M49.90 36.83 L49.90 6.90" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M49.90 41.10 L53.32 36.83 L46.48 36.83 L49.90 41.10 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M44.77 6.90 L55.03 6.90" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M49.90 24.00 L46.48 26.05 L46.48 21.95 L49.90 24.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M49.90 24.00 L53.32 26.05 L53.32 21.95 L49.90 24.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Block Arrow"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 21.42 L72.08 21.42 L72.08 16.26 L91.00 24.00 L72.08 31.74 L72.08 26.58 L5.00 26.58 L5.00 21.42 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Block"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M76.50 5.00 L76.50 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M76.50 24.00 L51.80 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M44.20 24.00 L19.50 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><text x="48" y="24" fill="#1e293b" font-size="11" font-weight="600" text-anchor="middle" dominant-baseline="central" transform="rotate(0.000 48.000 24.000)">B</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Block"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M76.50 5.00 L76.50 43.00 L76.50 24.00 L19.50 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Breach"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M17.28 35.72 L10.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M13.64 39.36 L13.64 5.00 L41.13 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M54.87 5.00 L82.36 5.00 L82.36 39.36" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M78.72 35.72 L86.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><text x="48" y="5" fill="#1e293b" font-size="11" font-weight="600" text-anchor="middle" dominant-baseline="central" transform="rotate(-270.000 48.000 5.000)">B</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Bypass"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M14.56 33.88 L10.00 43.00 L5.44 33.88" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M10.00 43.00 L10.00 5.00 L40.40 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M55.60 5.00 L86.00 5.00 L86.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M90.56 33.88 L86.00 43.00 L81.44 33.88" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><text x="48" y="5" fill="#1e293b" font-size="11" font-weight="600" text-anchor="middle" dominant-baseline="central" transform="rotate(-270.000 48.000 5.000)">B</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Canalize"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M17.28 43.00 L10.00 35.72" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M13.64 39.36 L13.64 5.00 L41.13 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M54.87 5.00 L82.36 5.00 L82.36 39.36" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M78.72 43.00 L86.00 35.72" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><text x="48" y="5" fill="#1e293b" font-size="11" font-weight="600" text-anchor="middle" dominant-baseline="central" transform="rotate(-270.000 48.000 5.000)">C</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Classic Arrow"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 24.00 L73.80 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M91.00 24.00 L73.80 17.98 L73.80 30.02 L91.00 24.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Clear"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M10.00 43.00 L86.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M21.40 5.00 L21.40 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M29.00 31.60 L21.40 43.00 L13.80 31.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M48.00 5.00 L48.00 20.20" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M48.00 27.80 L48.00 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M55.60 31.60 L48.00 43.00 L40.40 31.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M74.60 5.00 L74.60 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M82.20 31.60 L74.60 43.00 L67.00 31.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><text x="48" y="24" fill="#1e293b" font-size="11" font-weight="600" text-anchor="middle" dominant-baseline="central" transform="rotate(-270.000 48.000 24.000)">C</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Delay"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.59 38.93 L33.41 38.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M45.62 38.93 L73.45 38.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M13.73 43.00 L5.59 38.93 L13.73 34.86" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M73.45 38.93 L75.11 38.85 L76.76 38.60 L78.37 38.20 L79.94 37.64 L81.44 36.93 L82.87 36.07 L84.21 35.08 L85.44 33.96 L86.56 32.73 L87.55 31.39 L88.41 29.96 L89.12 28.46 L89.68 26.89 L90.09 25.28 L90.33 23.63 L90.41 21.96 L90.33 20.30 L90.09 18.65 L89.68 17.04 L89.12 15.47 L88.41 13.97 L87.55 12.54 L86.56 11.20 L85.44 9.97 L84.21 8.85 L82.87 7.86 L81.44 7.00 L79.94 6.29 L78.37 5.73 L76.76 5.33 L75.11 5.08 L73.45 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><text x="39.51785714285714" y="38.92857142857143" fill="#1e293b" font-size="11" font-weight="600" text-anchor="middle" dominant-baseline="central" transform="rotate(0.000 39.518 38.929)">D</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Disrupt"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M40.33 9.38 L40.33 38.62" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M40.33 9.38 L75.40 9.38" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M40.33 24.00 L60.06 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M40.33 38.62 L51.29 38.62" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M11.83 24.00 L40.33 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M75.40 5.00 L84.17 9.38 L75.40 13.77 L75.40 5.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M60.06 19.62 L68.83 24.00 L60.06 28.38 L60.06 19.62 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M51.29 34.23 L60.06 38.62 L51.29 43.00 L51.29 34.23 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Final Protective Fire (Left)"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M48.00 43.00 L10.00 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M10.87 9.44 L10.00 5.00 L14.45 5.88" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M48.00 43.00 L86.00 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M81.55 5.88 L86.00 5.00 L85.13 9.44" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M44.20 39.20 L13.80 8.80 L14.94 7.66 L45.34 38.06 L44.20 39.20 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Final Protective Fire (Right)"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M48.00 43.00 L10.00 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M10.87 9.44 L10.00 5.00 L14.45 5.88" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M48.00 43.00 L86.00 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M81.55 5.88 L86.00 5.00 L85.13 9.44" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M51.80 39.20 L82.20 8.80 L81.06 7.66 L50.66 38.06 L51.80 39.20 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
package/media/fix.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Fix"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 24.00 L22.20 24.00 L32.52 31.89 L48.00 16.11 L63.48 31.89 L73.80 24.00 L91.00 24.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M91.00 24.00 L79.73 31.89" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M91.00 24.00 L79.73 16.11" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
package/media/flot.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Forward line of own troops (FLOT)"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 27.86 L5.15 26.35 L5.58 24.90 L6.31 23.56 L7.27 22.39 L8.44 21.45 L9.76 20.72 L11.23 20.28 L12.72 20.14 L14.24 20.28 L15.68 20.72 L17.02 21.45 L18.19 22.39 L19.16 23.56 L19.86 24.90 L20.31 26.35 L20.45 27.86 L20.60 26.35 L21.05 24.90 L21.75 23.56 L22.72 22.39 L23.89 21.45 L25.23 20.72 L26.67 20.28 L28.17 20.14 L29.68 20.28 L31.13 20.72 L32.47 21.45 L33.64 22.39 L34.60 23.56 L35.31 24.90 L35.75 26.35 L35.91 27.86 L36.05 26.35 L36.49 24.90 L37.20 23.56 L38.16 22.39 L39.33 21.45 L40.67 20.72 L42.12 20.28 L43.63 20.14 L45.13 20.28 L46.59 20.72 L47.91 21.45 L49.08 22.39 L50.05 23.56 L50.77 24.90 L51.20 26.35 L51.35 27.86 L51.51 26.35 L51.94 24.90 L52.66 23.56 L53.62 22.39 L54.79 21.45 L56.12 20.72 L57.56 20.28 L59.08 20.14 L60.59 20.28 L62.04 20.72 L63.38 21.45 L64.55 22.39 L65.51 23.56 L66.21 24.90 L66.66 26.35 L66.80 27.86 L66.95 26.35 L67.38 24.90 L68.11 23.56 L69.07 22.39 L70.24 21.45 L71.58 20.72 L73.03 20.28 L74.52 20.14 L76.04 20.28 L77.48 20.72 L78.82 21.45 L79.99 22.39 L80.96 23.56 L81.66 24.90 L82.11 26.35 L82.26 27.86 L82.52 26.37 L83.28 25.06 L84.45 24.08 L85.87 23.56 L87.39 23.56 L88.82 24.08 L89.99 25.06 L90.74 26.37 L91.00 27.86" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Fortified Area"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M10.00 43.00 L11.73 43.00 L11.73 39.59 L15.18 39.59 L15.18 43.00 L16.91 43.00 L18.63 43.00 L18.63 39.59 L22.09 39.59 L22.09 43.00 L23.82 43.00 L25.54 43.00 L25.54 39.59 L29.00 39.59 L29.00 43.00 L30.73 43.00 L32.46 43.00 L32.46 39.59 L35.91 39.59 L35.91 43.00 L37.63 43.00 L39.37 43.00 L39.37 39.59 L42.82 39.59 L42.82 43.00 L44.54 43.00 L46.27 43.00 L46.27 39.59 L49.73 39.59 L49.73 43.00 L51.46 43.00 L53.18 43.00 L53.18 39.59 L56.63 39.59 L56.63 43.00 L58.37 43.00 L60.09 43.00 L60.09 39.59 L63.54 39.59 L63.54 43.00 L65.27 43.00 L67.00 43.00 L67.00 39.59 L70.46 39.59 L70.46 43.00 L72.18 43.00 L73.91 43.00 L73.91 39.59 L77.37 39.59 L77.37 43.00 L79.09 43.00 L80.82 43.00 L80.82 39.59 L84.27 39.59 L84.27 43.00 L86.00 43.00 L86.00 41.42 L82.59 41.42 L82.59 38.25 L86.00 38.25 L86.00 36.67 L86.00 35.08 L82.59 35.08 L82.59 31.92 L86.00 31.92 L86.00 30.33 L86.00 28.75 L82.59 28.75 L82.59 25.58 L86.00 25.58 L86.00 24.00 L86.00 22.42 L82.59 22.42 L82.59 19.25 L86.00 19.25 L86.00 17.67 L86.00 16.08 L82.59 16.08 L82.59 12.92 L86.00 12.92 L86.00 11.33 L86.00 9.75 L82.59 9.75 L82.59 6.58 L86.00 6.58 L86.00 5.00 L84.27 5.00 L84.27 8.41 L80.82 8.41 L80.82 5.00 L79.09 5.00 L77.37 5.00 L77.37 8.41 L73.91 8.41 L73.91 5.00 L72.18 5.00 L70.46 5.00 L70.46 8.41 L67.00 8.41 L67.00 5.00 L65.27 5.00 L63.54 5.00 L63.54 8.41 L60.09 8.41 L60.09 5.00 L58.37 5.00 L56.63 5.00 L56.63 8.41 L53.18 8.41 L53.18 5.00 L51.46 5.00 L49.73 5.00 L49.73 8.41 L46.27 8.41 L46.27 5.00 L44.54 5.00 L42.82 5.00 L42.82 8.41 L39.37 8.41 L39.37 5.00 L37.63 5.00 L35.91 5.00 L35.91 8.41 L32.46 8.41 L32.46 5.00 L30.73 5.00 L29.00 5.00 L29.00 8.41 L25.54 8.41 L25.54 5.00 L23.82 5.00 L22.09 5.00 L22.09 8.41 L18.63 8.41 L18.63 5.00 L16.91 5.00 L15.18 5.00 L15.18 8.41 L11.73 8.41 L11.73 5.00 L10.00 5.00 L10.00 6.58 L13.41 6.58 L13.41 9.75 L10.00 9.75 L10.00 11.33 L10.00 12.92 L13.41 12.92 L13.41 16.08 L10.00 16.08 L10.00 17.67 L10.00 19.25 L13.41 19.25 L13.41 22.42 L10.00 22.42 L10.00 24.00 L10.00 25.58 L13.41 25.58 L13.41 28.75 L10.00 28.75 L10.00 30.33 L10.00 31.92 L13.41 31.92 L13.41 35.08 L10.00 35.08 L10.00 36.67 L10.00 38.25 L13.41 38.25 L13.41 41.42 L10.00 41.42 L10.00 43.00 Z" fill="#1e293b" fill-opacity="0" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Fortified Line"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M5.00 27.86 L8.58 27.86 L8.58 20.14 L15.75 20.14 L15.75 27.86 L19.33 27.86 L22.92 27.86 L22.92 20.14 L30.08 20.14 L30.08 27.86 L33.67 27.86 L37.25 27.86 L37.25 20.14 L44.42 20.14 L44.42 27.86 L48.00 27.86 L51.58 27.86 L51.58 20.14 L58.75 20.14 L58.75 27.86 L62.33 27.86 L65.92 27.86 L65.92 20.14 L73.08 20.14 L73.08 27.86 L76.67 27.86 L80.25 27.86 L80.25 20.14 L87.42 20.14 L87.42 27.86 L91.00 27.86" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Isolate"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M67.00 24.00 L66.91 25.85 L66.64 27.69 L66.19 29.48 L65.57 31.23 L64.78 32.91 L63.83 34.50 L62.73 36.00 L61.49 37.38 L60.12 38.63 L58.64 39.74 L57.06 40.70 L55.39 41.50 L53.65 42.14 L51.85 42.60 L50.02 42.89 L48.17 43.00 L46.31 42.93 L44.48 42.67 L42.67 42.24 L40.92 41.63 L39.24 40.86 L37.63 39.93 L36.13 38.84 L34.74 37.61 L33.48 36.26 L32.35 34.78 L31.38 33.21 L30.56 31.54 L29.91 29.81 L29.43 28.02 L29.12 26.19 L29.00 24.34 L29.06 22.48 L29.30 20.64 L29.71 18.84 L30.30 17.08 L31.06 15.39 L31.98 13.78 L33.05 12.27 L34.27 10.87 L35.61 9.59 L37.08 8.45 L38.64 7.46 L40.30 6.63 L42.03 5.96 L43.82 5.46 L45.64 5.14 L47.49 5.01 L49.35 5.05 L51.19 5.27 L53.00 5.67 L54.76 6.24 L56.46 6.99 L58.08 7.89 L59.60 8.95 L61.01 10.15 L62.30 11.49 L63.45 12.94 L64.45 14.50" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M64.22 10.68 L64.45 14.50 L61.26 12.39" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M62.94 35.74 L61.07 32.48 L64.80 32.87" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M54.13 41.98 L54.33 38.23 L57.26 40.59" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M43.35 42.42 L45.56 39.39 L46.73 42.96" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M34.06 36.92 L37.57 35.58 L36.61 39.21" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M29.28 27.24 L32.95 28.03 L30.16 30.55" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M30.53 16.52 L33.18 19.18 L29.47 19.78" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M37.42 8.22 L38.19 11.89 L34.76 10.37" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M47.72 5.00 L46.37 8.50 L44.32 5.36" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M58.11 7.91 L55.07 10.12 L55.07 6.36" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Main Attack"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M74.60 35.40 L59.40 35.40 L40.40 35.40" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M74.60 12.60 L59.40 12.60 L40.40 12.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M40.40 43.00 L21.40 24.00 L40.40 5.00 L40.40 12.60 L29.00 24.00 L40.40 35.40 L40.40 43.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Obstacle Bypass Difficult"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M24.25 8.17 L28.05 10.80 L24.25 13.45 L28.05 16.08 L24.25 18.72 L28.05 21.36 L24.25 24.00 L28.05 26.64 L24.25 29.28 L28.05 31.92 L24.25 34.55 L28.05 37.20 L24.25 39.83" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M24.25 8.17 L65.42 8.17" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M24.25 39.83 L65.42 39.83" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M65.42 5.00 L71.75 8.17 L65.42 11.33 L65.42 5.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M65.42 36.67 L71.75 39.83 L65.42 43.00 L65.42 36.67 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Obstacle Bypass Easy"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M24.25 8.17 L24.25 39.83" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M24.25 8.17 L65.42 8.17" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M24.25 39.83 L65.42 39.83" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M65.42 5.00 L71.75 8.17 L65.42 11.33 L65.42 5.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M65.42 36.67 L71.75 39.83 L65.42 43.00 L65.42 36.67 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Obstacle Bypass Impossible"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M25.20 8.17 L25.20 22.73" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M25.20 25.27 L25.20 39.83" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M25.20 8.17 L66.37 8.17" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M25.20 39.83 L66.37 39.83" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M27.10 22.73 L23.30 22.73" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M27.10 25.27 L23.30 25.27" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M66.37 5.00 L72.70 8.17 L66.37 11.33 L66.37 5.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M66.37 36.67 L72.70 39.83 L66.37 43.00 L66.37 36.67 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Principal Direction of Fire"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M48.00 43.00 L10.00 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M10.87 9.44 L10.00 5.00 L14.45 5.88" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M48.00 43.00 L86.00 5.00" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M81.55 5.88 L86.00 5.00 L85.13 9.44" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Search Area"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M14.67 38.33 L51.33 43.00 L44.67 33.67 L73.40 37.32" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M14.67 38.33 L30.67 17.67 L32.00 25.67 L44.54 9.47" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M81.33 38.33 L73.90 33.35 L72.89 41.29 L81.33 38.33 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/><path d="M48.00 5.00 L42.30 7.74 L46.77 11.21 L48.00 5.00 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Support By Fire"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M26.50 33.78 L17.38 42.89" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M69.50 33.78 L78.62 42.89" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M26.50 33.78 L5.00 5.11" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M69.50 33.78 L91.00 5.11" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M26.50 33.78 L69.50 33.78" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M7.15 16.93 L5.00 5.11 L15.75 10.48" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M80.25 10.48 L91.00 5.11 L88.85 16.93" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Supporting Attack"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M74.60 35.40 L59.40 35.40 L40.40 35.40 L40.40 43.00 L21.40 24.00 L40.40 5.00 L40.40 12.60 L59.40 12.60 L74.60 12.60" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/></svg>
package/media/turn.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 48" width="96" height="48" role="img" aria-label="Turn"><rect width="96" height="48" rx="6" fill="#f8fafc" stroke="#e2e8f0"/><path d="M91.00 13.89 L90.17 14.91 L89.31 15.91 L88.44 16.89 L87.53 17.84 L86.61 18.77 L85.66 19.68 L84.69 20.57 L83.69 21.43 L82.68 22.26 L81.64 23.08 L80.59 23.86 L79.52 24.62 L78.43 25.36 L77.32 26.06 L76.20 26.74 L75.06 27.39 L73.89 28.02 L72.72 28.61 L71.54 29.18 L70.34 29.71 L69.13 30.22 L67.90 30.69 L66.67 31.14 L65.42 31.56 L64.17 31.94 L62.90 32.30 L61.62 32.62 L60.34 32.91 L59.05 33.17 L57.76 33.40 L56.46 33.60 L55.16 33.76 L53.85 33.89 L52.54 33.99 L51.23 34.06 L49.92 34.10 L48.60 34.11 L47.29 34.07 L45.97 34.01 L44.66 33.92 L43.36 33.80 L42.05 33.64 L40.75 33.45 L39.45 33.23 L38.16 32.98 L36.88 32.69 L35.61 32.38 L34.34 32.02 L33.08 31.65 L31.83 31.24 L30.59 30.80 L29.36 30.33 L28.15 29.83 L26.95 29.31 L25.76 28.75 L24.58 28.16 L23.42 27.54 L22.28 26.90 L21.15 26.23 L20.03 25.53 L18.94 24.80 L17.87 24.04 L16.81 23.27 L15.77 22.46" fill="none" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round" stroke-linecap="round"/><path d="M11.48 27.84 L5.00 13.89 L20.05 17.08 L11.48 27.84 Z" fill="#1e293b" fill-opacity="1" stroke="#1e293b" stroke-width="1.25" stroke-linejoin="round"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/control-measures",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.2.0-alpha.1",
4
4
  "description": "Library for drawing tactical graphics and control measures according to MIL-STD-2525 and APP-6 standards.",
5
5
  "license": "MIT",
6
6
  "author": "Orbat Mapper",
@@ -28,6 +28,7 @@
28
28
  },
29
29
  "files": [
30
30
  "dist",
31
+ "media",
31
32
  "README.md",
32
33
  "LICENSE"
33
34
  ],
@@ -39,6 +40,7 @@
39
40
  "@types/geojson": "^7946.0.16",
40
41
  "publint": "^0.3.21",
41
42
  "tsdown": "^0.22.2",
43
+ "tsx": "^4.22.4",
42
44
  "typescript": "~5.9.3",
43
45
  "vite": "^8.0.16",
44
46
  "vitest": "^4.1.8"
@@ -46,6 +48,9 @@
46
48
  "scripts": {
47
49
  "build": "tsdown",
48
50
  "test": "vitest run",
49
- "lint": "eslint . --fix"
51
+ "lint": "eslint . --fix",
52
+ "generate-previews": "tsx scripts/generate-previews.ts",
53
+ "generate-readme-table": "tsx scripts/generate-readme-table.ts",
54
+ "generate-docs": "pnpm generate-previews && pnpm generate-readme-table"
50
55
  }
51
56
  }