@johnmorrisdotca/kyogijo-stadiums 0.19.0 → 0.21.0

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/NOTICE.md +1 -0
  3. package/README.md +43 -15
  4. package/dist/budo/cage.d.ts +46 -0
  5. package/dist/budo/cage.js +91 -0
  6. package/dist/budo/describe.js +22 -0
  7. package/dist/budo/looks.d.ts +2 -0
  8. package/dist/budo/looks.js +14 -0
  9. package/dist/budo/plan.d.ts +12 -2
  10. package/dist/budo/plan.js +23 -1
  11. package/dist/budo/ring.d.ts +47 -0
  12. package/dist/budo/ring.js +80 -0
  13. package/dist/budo/types.d.ts +13 -3
  14. package/dist/budo-entry.d.ts +1 -1
  15. package/dist/budo-entry.js +1 -1
  16. package/dist/budo.style.js +15 -3
  17. package/dist/crowd.js +4 -0
  18. package/dist/draw.js +9 -7
  19. package/dist/features.js +18 -0
  20. package/dist/fields.types.d.ts +2 -2
  21. package/dist/heat.zones.js +1 -1
  22. package/dist/homeside.js +4 -0
  23. package/dist/leagues/budo.d.ts +3 -2
  24. package/dist/leagues/budo.js +2 -1
  25. package/dist/leagues/combat.d.ts +102 -0
  26. package/dist/leagues/combat.js +64 -0
  27. package/dist/leagues/sources.js +10 -0
  28. package/dist/leagues/types.d.ts +1 -1
  29. package/dist/leagues.d.ts +1 -0
  30. package/dist/leagues.js +40 -1
  31. package/dist/outline.js +2 -0
  32. package/dist/ranges.names.js +2 -0
  33. package/dist/score.js +6 -0
  34. package/dist/strings.d.ts +13 -1
  35. package/dist/strings.js +34 -10
  36. package/dist/surfaces/budo.js +3 -2
  37. package/dist/turn.kit.d.ts +1 -1
  38. package/dist/turn.style.js +13 -3
  39. package/dist/types.d.ts +4 -2
  40. package/dist/units.js +4 -0
  41. package/dist/uprights.budo.js +7 -2
  42. package/dist/uprights.cage.d.ts +4 -0
  43. package/dist/uprights.cage.js +91 -0
  44. package/dist/uprights.js +3 -3
  45. package/dist/uprights.ring.d.ts +12 -0
  46. package/dist/uprights.ring.js +76 -0
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +5 -3
@@ -0,0 +1,80 @@
1
+ // A ring: the raised square platform with its canvas, the ropes held by a post at each corner (with the corner pads in the boxers' colours and two neutral ones), the apron beyond the ropes and the steps up to it, from the
2
+ // rules a commission or a federation writes (the Nevada Rules of Unarmed Combat 2.050, Wisconsin's SPS 192.38, the WBC Amateur rules, Ontario's amateur rules). Boxing draws it with its own figures, and the professional
3
+ // wrestling ring will with its own. In metres, the origin at the top left of the platform, north at the top. Nothing here draws SVG.
4
+ import { box, line } from "../fields.kit.js";
5
+ import { rectArea, within } from "./kit.js";
6
+ import { emptyParts, grow, union } from "./parts.js";
7
+ /** The size the options ask for, held to what the rules allow. */
8
+ export const ringSize = (asked, fig, rule) => (asked === undefined ? fig.size.drawn : within(asked, fig.size.min, fig.size.max, "The ring", rule));
9
+ /** The platform's height the options ask for, held to what the rules allow. */
10
+ export const ringHeight = (asked, fig, rule) => (asked === undefined ? fig.platform.height.drawn : within(asked, 0, fig.platform.height.max, "The ring's platform", rule));
11
+ /** The colours of the four corners, clockwise from the top left: neutral, blue, neutral, red (the red corner at the left and near the table). */
12
+ const COLOURS = ["white", "blue", "white", "red"];
13
+ const rd = (v) => Math.round(v * 1000) / 1000;
14
+ /** The plan of the ring the options ask for. */
15
+ export function ringParts(fig, o) {
16
+ const out = fig.post.fromRopes;
17
+ // The apron reaches at least to the far side of the posts and their pads.
18
+ const apron = Math.max(fig.apron.drawn, out + fig.post.pad / 2 + 0.02);
19
+ const side = o.size + 2 * apron;
20
+ const platform = { x0: 0, y0: 0, x1: side, y1: side };
21
+ const ring = { x0: apron, y0: apron, x1: apron + o.size, y1: apron + o.size };
22
+ const parts = emptyParts();
23
+ parts.areas.push(rectArea("platform", "canvas", "ky-bud-t1", platform));
24
+ const rope = (i) => [i === 0 || i === 3 ? ring.x0 : ring.x1, i === 0 || i === 1 ? ring.y0 : ring.y1];
25
+ const corners = [0, 1, 2, 3].map((i) => {
26
+ const [rx, ry] = rope(i);
27
+ const sx = i === 0 || i === 3 ? -1 : 1;
28
+ const sy = i === 0 || i === 1 ? -1 : 1;
29
+ return { index: i, colour: COLOURS[i], post: [rd(rx + sx * out), rd(ry + sy * out)], rope: [rx, ry] };
30
+ });
31
+ // The steps: one in each boxer's corner (the red and the blue) and one in a neutral corner, on the apron's edge beside the corner.
32
+ const steps = [];
33
+ const s = fig.platform.steps;
34
+ if (o.steps && o.height > 0) {
35
+ const depth = s.treads * s.tread;
36
+ const at = (corner) => {
37
+ const c = corners[corner];
38
+ const north = corner === 0 || corner === 1;
39
+ // Beside the post, a metre and a half along the edge from the corner, towards the middle.
40
+ const cx = c.post[0] < side / 2 ? c.post[0] + 1.3 : c.post[0] - 1.3;
41
+ const b = north ? { x0: cx - s.width / 2, y0: -depth, x1: cx + s.width / 2, y1: 0 } : { x0: cx - s.width / 2, y0: side, x1: cx + s.width / 2, y1: side + depth };
42
+ steps.push({ edge: north ? "north" : "south", corner, box: b });
43
+ for (let t = 0; t < s.treads; t += 1) {
44
+ const y0 = north ? -depth + t * s.tread : side + t * s.tread;
45
+ parts.areas.push(rectArea(`step-${corner}-${t + 1}`, "steps", "ky-bud-step", { x0: b.x0, y0, x1: b.x1, y1: y0 + s.tread }));
46
+ }
47
+ };
48
+ // The red corner (bottom left), the blue corner (top right) and the neutral corner nearest the table (bottom right).
49
+ for (const corner of [3, 1, 2])
50
+ at(corner);
51
+ }
52
+ // The ropes seen from above are one line each side; each turns to its post by the extension.
53
+ const w = Math.max(fig.ropes.diameter.drawn, 0.03);
54
+ parts.shapes.push(box("ropes", "ropes", ring.x0, ring.y0, ring.x1, ring.y1, { w, cls: "ky-bud-rope", least: w }));
55
+ for (const c of corners)
56
+ parts.shapes.push(line(`rope-extension-${c.index + 1}`, "ropes", c.rope[0], c.rope[1], c.post[0], c.post[1], { w, cls: "ky-bud-rope", least: w }));
57
+ // The posts with their pads, in the corner colours (or plain when the corners are not coloured).
58
+ for (const c of corners) {
59
+ const pad = fig.post.pad;
60
+ parts.areas.push(rectArea(`corner-${c.index + 1}`, "corners", o.corners ? `ky-bud-corner-${c.colour}` : "ky-bud-pad", { x0: c.post[0] - pad / 2, y0: c.post[1] - pad / 2, x1: c.post[0] + pad / 2, y1: c.post[1] + pad / 2 }));
61
+ }
62
+ const extent = grow(union([platform, ...steps.map((x) => x.box)]), 0.6);
63
+ const mid = side / 2;
64
+ const ranges = { ropes: grow(ring, 0.3), corner: { x0: -0.4, y0: side - apron - 2.4, x1: apron + 3, y1: side + 0.6 } };
65
+ const red = corners[3];
66
+ const blue = corners[1];
67
+ const marks = [
68
+ { id: "ring-canvas", x: mid - o.size * 0.2, y: mid + o.size * 0.15, side: "bottom" },
69
+ { id: "ring-ropes", x: ring.x1, y: mid - o.size * 0.2, side: "bottom" },
70
+ { id: "ring-post", x: corners[0].post[0], y: corners[0].post[1], side: "top" },
71
+ { id: "ring-red-corner", x: red.post[0], y: red.post[1], side: "bottom" },
72
+ { id: "ring-blue-corner", x: blue.post[0], y: blue.post[1], side: "top" },
73
+ { id: "ring-apron", x: mid, y: apron / 2, side: "top" },
74
+ ];
75
+ if (steps.length > 0) {
76
+ const last = steps[steps.length - 1];
77
+ marks.push({ id: "ring-steps", x: (last.box.x0 + last.box.x1) / 2, y: side + (last.box.y1 - last.box.y0) / 2, side: "bottom" });
78
+ }
79
+ return { parts, platform, extent, centre: [mid, mid], ring, size: o.size, apron, corners, steps, height: o.height, marks, ranges };
80
+ }
@@ -1,5 +1,7 @@
1
1
  /** The sports of the budō family. */
2
- export type BudoKind = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";
2
+ export type BudoKind = "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo" | CombatKind;
3
+ /** The combat sports the engine of the budō also draws: the MMA cage. */
4
+ export type CombatKind = "mma" | "boxing";
3
5
  /** The sides of a hall, as a plan has them: north is the top of the drawing. `top`, `bottom`, `left` and `right` say the same. */
4
6
  export type Compass = "north" | "south" | "east" | "west";
5
7
  export type Side4 = Compass | "top" | "bottom" | "left" | "right";
@@ -19,7 +21,7 @@ export interface BudoStyle {
19
21
  hall?: "budokan";
20
22
  /** Judo: `shiai` (the contest area, the default) or `kata` (the IJF's kata area, `league: "ijf-kata"`: a centre tape, two white tapes 3 m from it either side, a safety area of 2 m; with `officials`, the five judges' seats on the shomen side and the coach's chair opposite). */
21
23
  style?: "shiai" | "kata";
22
- /** Judo: the contest area's side, 8 m to 10 m. Kendo: the shiai-jo's side, 9 m to 11 m. In metres. */
24
+ /** Judo: the contest area's side, 8 m to 10 m. Kendo: the shiai-jo's side, 9 m to 11 m. MMA cage: the width across the flats, 6.096 m to 9.754 m (20 to 32 feet), 9.144 m (30 feet) by default; 7.62 m is the smaller cage of 25 feet. Boxing ring: the side inside the ropes, within what the league allows (20 feet for the Nevada rule, 16 to 20 feet for Wisconsin, 4.5 to 6.1 m for the WBC Amateur rules). In metres. */
23
25
  size?: number;
24
26
  /** Judo: the width of the safety area, 3 m or more (4 m by default). In metres. */
25
27
  safety?: number;
@@ -37,12 +39,20 @@ export interface BudoStyle {
37
39
  texture?: "weave" | "none";
38
40
  /** Karate: the referee's and the judges' places. On by default. */
39
41
  judges?: boolean;
40
- /** Sumo: the height of the clay square in metres, 0.34 to 0.60. 0.55 by default. Seen in 3D. */
42
+ /** Sumo: the height of the clay square in metres, 0.34 to 0.60. 0.55 by default. MMA cage: the platform's height over the floor of the building, 0 to 1.219 m (4 feet), 1.219 m by default. Boxing ring: the same, 0 to what the league allows (5 feet in Nevada, 4 feet in Wisconsin, 1.2 m for the amateurs). Seen in 3D. */
41
43
  height?: number;
42
44
  /** Sumo: the four tassels of the roof, in the colours of the four quarters (green east, red south, white west, black north). Off by default. */
43
45
  tassels?: boolean;
44
46
  /** Sumo: the tawara, the shikiri-sen and the sand of the jano-me. On by default. */
45
47
  tawara?: boolean;
48
+ /** MMA cage: the fence's height over the canvas, 1.524 m to 2.134 m (5 to 7 feet), 1.829 m (6 feet) by default. Seen in 3D. */
49
+ fence?: number;
50
+ /** MMA cage: the two gates, opposite each other (the fence is whole without them). On by default. */
51
+ gates?: boolean;
52
+ /** MMA cage: the steps up to the platform at each gate, three treads. Boxing ring: the three sets of steps, one in each boxer's corner and one in a neutral corner. On by default. */
53
+ steps?: boolean;
54
+ /** Boxing ring: the posts and their pads in the corner colours (the red and the blue corner of the boxers, two neutral ones in white). On by default. */
55
+ corners?: boolean;
46
56
  /** Kyūdō: the number of shooters in a line, 1 to 12 (5 by default). */
47
57
  shooters?: number;
48
58
  /** Kyūdō: the kind of range, `kinteki` (28 m, the default) or `enteki` (60 m); `league` says it too. */
@@ -3,7 +3,7 @@ export type { Dojo, Tatami } from "./budo/dojo.api.ts";
3
3
  export { DOJO_SIZES, floorOf, shapeOfJo } from "./budo/dojo.ts";
4
4
  export { dojoGallery, dojoRoom, ROOM_HEIGHTS } from "./budo/room.ts";
5
5
  export type { DojoGallery, DojoRoom, RoomBalcony, RoomBasis, RoomOpening, RoomWall } from "./budo/room.ts";
6
- export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, isBudoSport } from "./budo/plan.ts";
6
+ export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, COMBAT_SPORTS, isBudoSport, isCombatSport, isMatSport } from "./budo/plan.ts";
7
7
  export type { BudoPlan, MBox, MMark, MRange } from "./budo/plan.ts";
8
8
  export { checker, fourCorners, kindOf, lay, matCount, tint } from "./budo/tatami.ts";
9
9
  export type { MatSize, Piece, PieceKind } from "./budo/tatami.ts";
@@ -2,7 +2,7 @@
2
2
  export { DOJO_STANDARDS, dojo, drawDojo } from "./budo/dojo.api.js";
3
3
  export { DOJO_SIZES, floorOf, shapeOfJo } from "./budo/dojo.js";
4
4
  export { dojoGallery, dojoRoom, ROOM_HEIGHTS } from "./budo/room.js";
5
- export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, isBudoSport } from "./budo/plan.js";
5
+ export { budoLeague, budoPlan, BUDO_SPORTS, BUDOKAN_ARENA, COMBAT_SPORTS, isBudoSport, isCombatSport, isMatSport } from "./budo/plan.js";
6
6
  export { checker, fourCorners, kindOf, lay, matCount, tint } from "./budo/tatami.js";
7
7
  export { TATAMI } from "./budo/judo.js";
8
8
  export { KIMARITE, KIMARITE_COUNT, KIMARITE_OTHERS, kimariteName, kimariteOf } from "./budo/kimarite.js";
@@ -37,8 +37,20 @@ const CLASSES = `
37
37
  .kyogijo .ky-bud-tassel-s{fill:#d23b32;stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.03}
38
38
  .kyogijo .ky-bud-tassel-w{fill:#f4f1e8;stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.03}
39
39
  .kyogijo .ky-bud-tassel-n{fill:#232323;stroke:var(--ky-ink);stroke-opacity:.5;stroke-width:.03}`.trim();
40
+ /** The rules of the combat sports' classes, added to a combat drawing only, so that the budō drawings are the text they were: the steps up to a platform and the padding of a post. */
41
+ const COMBAT_CLASSES = `
42
+ .kyogijo .ky-bud-step{fill:color-mix(in srgb,var(--ky-bud-floor,#43484e) 76%,#fff);stroke:var(--ky-ink);stroke-opacity:.35;stroke-width:.03}
43
+ .kyogijo .ky-bud-pad{fill:var(--ky-ink);fill-opacity:.82}`.trim();
44
+ /** The rules of a ring's own classes: the ropes and the corner pads in the boxers' colours. */
45
+ const RING_CLASSES = `
46
+ .kyogijo .ky-bud-rope{stroke:var(--ky-bud-rope,#f4f1e8);fill:none;stroke-linecap:round}
47
+ .kyogijo .ky-bud-corner-red{fill:var(--ky-red)}
48
+ .kyogijo .ky-bud-corner-blue{fill:var(--ky-blue,#2a5db0)}
49
+ .kyogijo .ky-bud-corner-white{fill:#f4f1e8;stroke:var(--ky-ink);stroke-opacity:.45;stroke-width:.03}`.trim();
50
+ const COMBAT = ["mma", "boxing"];
51
+ const RING = ["boxing"];
40
52
  /** The custom properties of a look. */
41
- const vars = (l) => `--ky-surface:${l.surface};--ky-surface-alt:${l.alt};--ky-red:${l.red};--ky-apron:${l.apron};--ky-free:${l.free};--ky-bud-floor:${l.floor};--ky-boards:${l.boards}${l.dirt === undefined ? "" : `;--ky-dirt:${l.dirt}`}${l.sand === undefined ? "" : `;--ky-sand:${l.sand}`}${l.straw === undefined ? "" : `;--ky-bud-straw:${l.straw}`}`;
53
+ const vars = (l) => `--ky-surface:${l.surface};--ky-surface-alt:${l.alt};--ky-red:${l.red};--ky-apron:${l.apron};--ky-free:${l.free};--ky-bud-floor:${l.floor};--ky-boards:${l.boards}${l.dirt === undefined ? "" : `;--ky-dirt:${l.dirt}`}${l.sand === undefined ? "" : `;--ky-sand:${l.sand}`}${l.straw === undefined ? "" : `;--ky-bud-straw:${l.straw}`}${l.rope === undefined ? "" : `;--ky-bud-rope:${l.rope}`}`;
42
54
  /** The three rules of one look: light, dark by the device's setting, and dark by the page's `data-theme`. */
43
55
  function lookRules(sport, name) {
44
56
  const pair = BUDO_LOOKS[sport][name];
@@ -64,7 +76,7 @@ export function budoStyle(sport, look) {
64
76
  const kind = sport;
65
77
  if (BUDO_LOOKS[kind] === undefined)
66
78
  return "";
67
- return `${CLASSES}\n${look === undefined ? lookRules(kind, Object.keys(BUDO_LOOKS[kind])[0]) : lookRules(kind, look)}`;
79
+ return `${CLASSES}${COMBAT.includes(sport) ? `\n${COMBAT_CLASSES}` : ""}${RING.includes(sport) ? `\n${RING_CLASSES}` : ""}\n${look === undefined ? lookRules(kind, Object.keys(BUDO_LOOKS[kind])[0]) : lookRules(kind, look)}`;
68
80
  }
69
81
  /** Every budō sport's rules and every look, for a page that draws with `style: "none"` and supplies the stylesheet itself. */
70
- export const KYOGIJO_BUDO_STYLE = `${CLASSES}${KYOGIJO_BUDO_GALLERY_STYLE}\n${Object.keys(BUDO_LOOKS).flatMap((sport) => Object.keys(BUDO_LOOKS[sport]).map((name) => lookRules(sport, name))).join("\n")}`;
82
+ export const KYOGIJO_BUDO_STYLE = `${CLASSES}\n${COMBAT_CLASSES}\n${RING_CLASSES}${KYOGIJO_BUDO_GALLERY_STYLE}\n${Object.keys(BUDO_LOOKS).flatMap((sport) => Object.keys(BUDO_LOOKS[sport]).map((name) => lookRules(sport, name))).join("\n")}`;
package/dist/crowd.js CHANGED
@@ -100,6 +100,10 @@ const PROFILES = {
100
100
  sumo: { shape: "bowl", side: 58, end: 58, bench: 0, corner: 56, tiers: [{ id: "lower", from: 0, to: 54, height: [1, 8], fill: 1 }, upper(62, 128, 26, 62)], away: "corner" },
101
101
  kyudo: { shape: "sides", side: 10, end: 8, bench: 0, corner: 6, tiers: [lower(24, 2, 8), upper(30, 52, 12, 22)], away: "corner" },
102
102
  dojo: { shape: "bowl", side: 5, end: 5, bench: 0, corner: 8, tiers: [lower(26, 2, 14), upper(32, 58, 20, 36)], away: "corner" },
103
+ // An MMA cage stands in the middle of an arena, with the seats rising from the ring of the floor.
104
+ mma: { shape: "bowl", side: 40, end: 40, bench: 0, corner: 56, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner" },
105
+ // A boxing ring stands in the middle of an arena in the same way.
106
+ boxing: { shape: "bowl", side: 40, end: 40, bench: 0, corner: 56, tiers: [lower(60, 3, 24), upper(67, 120, 32, 60)], away: "corner" },
103
107
  };
104
108
  /** The grounds of the package that are the home of an NFL or a CFL club, where a soccer pitch stands inside a gridiron's stands. From each ground's article. */
105
109
  export const GRIDIRON_GROUNDS = Object.freeze({
package/dist/draw.js CHANGED
@@ -17,7 +17,7 @@ import { athleticsPlan } from "./athletics/plan.js";
17
17
  import { skatingPlan } from "./skating/plan.js";
18
18
  import { skatingAbout, skatingSentences } from "./skating/describe.js";
19
19
  import { skatingStyle } from "./skating.style.js";
20
- import { budoPlan, isBudoSport } from "./budo/plan.js";
20
+ import { budoPlan, isBudoSport, isMatSport } from "./budo/plan.js";
21
21
  import { dojoSpecOf } from "./budo/dojo.js";
22
22
  import { dojoGallery } from "./budo/room.js";
23
23
  import { budoAbout, budoScoreSentences, budoSentences } from "./budo/describe.js";
@@ -91,7 +91,7 @@ import { abbreviate, capitals } from "./text.js";
91
91
  import { VERSION } from "./version.js";
92
92
  export { isPortrait };
93
93
  /** The sports that can be drawn, in the order a menu lists them. */
94
- export const SPORTS = ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "athletics", "skating", "judo", "karate", "kendo", "sumo", "kyudo", "dojo", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball"];
94
+ export const SPORTS = ["nfl", "cfl", "ifaf", "nhl", "nba", "mlb", "soccer", "curling", "tennis", "volleyball", "badminton", "pickleball", "tabletennis", "padel", "squash", "racquetball", "sepaktakraw", "teqball", "roundnet", "netball", "swimming", "waterpolo", "diving", "artisticswimming", "athletics", "skating", "judo", "karate", "kendo", "sumo", "kyudo", "dojo", "mma", "boxing", "lacrosse", "rugby", "afl", "gaelic", "cricket", "fieldhockey", "handball"];
95
95
  /** The CSS custom property each colour of `Colors` sets. */
96
96
  export const COLOR_PROPERTIES = {
97
97
  apron: "--ky-apron",
@@ -265,7 +265,7 @@ function buildSkating(o, common) {
265
265
  /** The budō surface: its plan, drawn as it lies. A hall, a kyūdōjō and a range of mats have little ground round them. */
266
266
  function buildBudo(o, common) {
267
267
  const plan = budoPlan({ ...o, detail: common.detail });
268
- return budoSurface(common, plan, common.margin ?? (o.range === undefined ? 12 : 4));
268
+ return budoSurface(common, plan, common.margin ?? (o.range === undefined ? (plan.mma === undefined && plan.boxing === undefined ? 12 : 5) : 4));
269
269
  }
270
270
  /** One text for both ends or sides, or a pair of them. */
271
271
  const pair = (text) => (text === undefined ? undefined : typeof text === "string" ? [text, text] : text);
@@ -373,6 +373,8 @@ function buildWhole(o, portrait, id, score, scored, extra) {
373
373
  case "sumo":
374
374
  case "kyudo":
375
375
  case "dojo":
376
+ case "mma":
377
+ case "boxing":
376
378
  return buildBudo({ ...o, sport }, common);
377
379
  default:
378
380
  throw new Error(`Unknown sport "${String(o.sport)}": use one of ${SPORTS.join(", ")}`);
@@ -608,7 +610,7 @@ function leagueAbout(league, year, language, sheet) {
608
610
  const words = KYOGIJO_STRINGS[language].league;
609
611
  if (league.family === "skating")
610
612
  return skatingAbout({ sport: "skating", league: league.id }, language);
611
- if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo")
613
+ if (league.family === "judo" || league.family === "karate" || league.family === "kendo" || league.family === "sumo" || league.family === "kyudo" || league.family === "dojo" || league.family === "mma" || league.family === "boxing")
612
614
  return budoAbout({ sport: league.family, league: league.id }, language);
613
615
  if (league.family === "athletics")
614
616
  return athleticsAbout({ sport: "athletics", league: league.id, athletics: sheet === undefined ? undefined : undefined }, language);
@@ -697,8 +699,8 @@ export function describe(o) {
697
699
  const poolLine = pools === undefined ? "" : pools.after.map((line) => `${gap}${line}`).join("");
698
700
  // An arena of a size of its own says what it is at that size.
699
701
  const arenaAbout = arenaSize === undefined ? undefined : fill(sport === "nhl" ? words.said.arenaRinkAbout : words.said.arenaCourtAbout, { length: metres(arenaSize.length), width: metres(arenaSize.width), lengthM: metres(arenaSize.length * 0.3048), widthM: metres(arenaSize.width * 0.3048) });
700
- const aboutText = arenaAbout !== undefined ? arenaAbout : pools !== undefined ? pools.about : isBudoSport(sport) ? budoAbout(o, language) : sport === "skating" ? skatingAbout(o, language) : sport === "athletics" ? athleticsAbout(o, language) : field !== undefined ? leagueAbout(league ?? LEAGUES[sport][0], o.year, language, { length: field.length, width: field.width }) : plan !== undefined ? leagueAbout(plan.league, o.year, language, plan.spec) : league !== undefined ? leagueAbout(league, o.year, language) : pitch === undefined ? words.about[sport] : fill(pitch.format === "futsal" ? words.soccer.aboutFutsal : words.about.soccer, { length: metres(pitch.length), width: metres(pitch.width) });
701
- const about = `${words.said.on}${colon}${aboutText}${stop}${sport === "athletics" ? athleticsSentences(o, language) : sport === "skating" ? skatingSentences(o, language) : isBudoSport(sport) ? budoSentences(o, language) : ""}`;
702
+ const aboutText = arenaAbout !== undefined ? arenaAbout : pools !== undefined ? pools.about : isMatSport(sport) ? budoAbout(o, language) : sport === "skating" ? skatingAbout(o, language) : sport === "athletics" ? athleticsAbout(o, language) : field !== undefined ? leagueAbout(league ?? LEAGUES[sport][0], o.year, language, { length: field.length, width: field.width }) : plan !== undefined ? leagueAbout(plan.league, o.year, language, plan.spec) : league !== undefined ? leagueAbout(league, o.year, language) : pitch === undefined ? words.about[sport] : fill(pitch.format === "futsal" ? words.soccer.aboutFutsal : words.about.soccer, { length: metres(pitch.length), width: metres(pitch.width) });
703
+ const about = `${words.said.on}${colon}${aboutText}${stop}${sport === "athletics" ? athleticsSentences(o, language) : sport === "skating" ? skatingSentences(o, language) : isMatSport(sport) ? budoSentences(o, language) : ""}`;
702
704
  const halfLine = pitch !== undefined && !isScore(o) && (o.half === "left" || o.half === "right") ? `${gap}${words.soccer.half}` : "";
703
705
  // What the ground says about the size: that it is the published one, or that the ground has none and the international pitch is drawn.
704
706
  const year = typeof o.year === "number" && Number.isFinite(o.year) ? o.year : undefined;
@@ -889,7 +891,7 @@ function drawnKyogijo(o) {
889
891
  const crowded = crowdOf(o) !== undefined;
890
892
  const style = colourStyle(o, { left: leftTeam, right: rightTeam, home: isScore(o) ? o.home : undefined, away: isScore(o) ? o.away : undefined }, featured);
891
893
  const palette = isLook(o.look) ? o.look : undefined;
892
- const budoLook = isBudoSport(sport) ? budoLookOf(sport, o.budo?.look ?? (sport === "dojo" ? dojoSpecOf(o.dojo).look : undefined)) : undefined;
894
+ const budoLook = isMatSport(sport) ? budoLookOf(sport, o.budo?.look ?? (sport === "dojo" ? dojoSpecOf(o.dojo).look : undefined)) : undefined;
893
895
  let notes;
894
896
  const theme = o.theme === "light" || o.theme === "dark" ? o.theme : undefined;
895
897
  let viewBox;
package/dist/features.js CHANGED
@@ -128,6 +128,22 @@ const SUMO = [
128
128
  f("tawara", "--ky-bud-straw", "The tawara of the ring."),
129
129
  f("shikiri-sen", "--ky-line", "The two shikiri-sen."),
130
130
  ];
131
+ const MMA = [
132
+ f("apron", "--ky-apron", "The floor of the hall round the platform."),
133
+ f("platform", "--ky-bud-floor", "The platform the cage stands on."),
134
+ f("steps", "--ky-bud-floor", "The steps up to the platform."),
135
+ f("canvas", "--ky-surface", "The canvas inside the fence."),
136
+ f("fence", "--ky-ink", "The fence, one line on each flat."),
137
+ f("gates", "--ky-ink", "The swing of the two gates."),
138
+ f("posts", "--ky-ink", "The eight padded posts."),
139
+ ];
140
+ const BOXING = [
141
+ f("apron", "--ky-apron", "The floor of the hall round the platform."),
142
+ f("canvas", "--ky-surface", "The canvas of the platform, ring and apron."),
143
+ f("ropes", "--ky-bud-rope", "The ropes, and their extensions to the posts."),
144
+ f("steps", "--ky-bud-floor", "The steps up to the platform."),
145
+ f("corners", "--ky-ink", "The four corner posts with their pads."),
146
+ ];
131
147
  const KYUDO = [
132
148
  f("apron", "--ky-apron", "The ground round the range."),
133
149
  f("shajo", "--ky-bud-floor", "The floor of the shajō."),
@@ -435,6 +451,8 @@ export const FEATURE_INFO = Object.freeze({
435
451
  sumo: SUMO,
436
452
  kyudo: KYUDO,
437
453
  dojo: DOJO,
454
+ mma: MMA,
455
+ boxing: BOXING,
438
456
  });
439
457
  /**
440
458
  * The names of the features of each sport: the keys `featureColors` takes. Football's three fields have the same, and so have the five net sports.
@@ -25,7 +25,7 @@ export type SkatingMark = "ky-ska-b" | "ky-ska-r" | "ky-ska-k" | "ky-ska-blocks"
25
25
  /** The coloured marks of a budō surface: the white line, the blue, white and red tape, the seam between mats. */
26
26
  /** The fills of a budō surface's tape: blue, white, red and ink. */
27
27
  export type BudoFill = "ky-bud-blue-fill" | "ky-bud-white-fill" | "ky-bud-red-fill" | "ky-bud-ink-fill";
28
- export type BudoMark = "ky-bud-line" | "ky-bud-blue" | "ky-bud-white" | "ky-bud-red" | "ky-bud-seam" | "ky-bud-ink";
28
+ export type BudoMark = "ky-bud-line" | "ky-bud-blue" | "ky-bud-white" | "ky-bud-red" | "ky-bud-seam" | "ky-bud-ink" | "ky-bud-rope";
29
29
  /** A line from one point to another. */
30
30
  export interface LineShape extends Stroked {
31
31
  kind: "line";
@@ -95,7 +95,7 @@ export type Shape = LineShape | ArcShape | RingShape | BoxShape | PolyShape | Do
95
95
  /** The areas of a skating rink: the ice, the floor of the infield, the warm-up lane, the padding and the furniture. */
96
96
  export type SkatingArea = "ky-ska-ice" | "ky-ska-floor" | "ky-ska-warm" | "ky-ska-pad" | "ky-ska-desk" | "ky-ska-kiss";
97
97
  /** The areas of a budō surface: the mats in three tones, the heri of a mat, the floor, the walls, the platform and the furniture, the clay and the sand and the straw of a dohyō, the azuchi and the yamichi. */
98
- export type BudoArea = "ky-bud-t1" | "ky-bud-t2" | "ky-bud-t3" | "ky-bud-heri" | "ky-bud-floor" | "ky-bud-wall" | "ky-bud-window" | "ky-bud-door" | "ky-bud-platform" | "ky-bud-free" | "ky-bud-desk" | "ky-bud-clay" | "ky-bud-sand" | "ky-bud-tawara" | "ky-bud-azuchi" | "ky-bud-roof" | "ky-bud-yamichi" | "ky-bud-shrine" | "ky-bud-dais" | "ky-bud-banner" | "ky-bud-lane" | "ky-bud-mato" | "ky-bud-tassel-e" | "ky-bud-tassel-s" | "ky-bud-tassel-w" | "ky-bud-tassel-n" | "ky-bud-seat";
98
+ export type BudoArea = "ky-bud-t1" | "ky-bud-t2" | "ky-bud-t3" | "ky-bud-heri" | "ky-bud-floor" | "ky-bud-wall" | "ky-bud-window" | "ky-bud-door" | "ky-bud-platform" | "ky-bud-free" | "ky-bud-desk" | "ky-bud-clay" | "ky-bud-sand" | "ky-bud-tawara" | "ky-bud-azuchi" | "ky-bud-roof" | "ky-bud-yamichi" | "ky-bud-shrine" | "ky-bud-dais" | "ky-bud-banner" | "ky-bud-lane" | "ky-bud-mato" | "ky-bud-tassel-e" | "ky-bud-tassel-s" | "ky-bud-tassel-w" | "ky-bud-tassel-n" | "ky-bud-seat" | "ky-bud-step" | "ky-bud-pad" | "ky-bud-corner-red" | "ky-bud-corner-blue" | "ky-bud-corner-white";
99
99
  export type AthleticsArea = "ky-ath-track" | "ky-ath-grass" | "ky-ath-sand" | "ky-ath-mat" | "ky-ath-water" | "ky-ath-concrete" | "ky-ath-pad" | "ky-ath-board" | "ky-ath-bank";
100
100
  /** A painted area, under the lines: a rectangle or an ellipse filled with a class of the stylesheet (`ky-area`, the tint of a goal area, or `ky-zone`). */
101
101
  export interface Area extends Named {
@@ -37,7 +37,7 @@ export const ZONE_SETS = Object.freeze({
37
37
  "pool-lanes": { sports: ["swimming"], title: "Lanes: each lane of the pool, the whole length of it", source: "docs/sources.md#zone-sets" },
38
38
  "water-polo-areas": { sports: ["waterpolo"], title: "The goal area, the ground from 2 to 6 m, and the rest of the field, at each end", source: "docs/sources.md#zone-sets" },
39
39
  "pool-thirds": { sports: ["diving", "artisticswimming"], title: "Thirds along the pool, from the wall the boards or the judges stand at", source: "docs/sources.md#zone-sets" },
40
- "budo-areas": { sports: ["judo", "karate", "kendo", "sumo", "kyudo", "dojo"], title: "The surface in its areas: the contest and the safety areas, the boundary band, the clay and the ring of a dohyō, the shajō, the yamichi and the azuchi, a dojo's floor", source: "docs/sources.md#zone-sets" },
40
+ "budo-areas": { sports: ["judo", "karate", "kendo", "sumo", "kyudo", "dojo", "mma", "boxing"], title: "The surface in its areas: the contest and the safety areas, the boundary band, the clay and the ring of a dohyō, the shajō, the yamichi and the azuchi, a dojo's floor", source: "docs/sources.md#zone-sets" },
41
41
  "field-thirds": { sports: ["lacrosse", "rugby", "afl", "gaelic", "fieldhockey", "handball", "netball"], title: "Thirds: defensive, middle and attacking", source: "docs/sources.md#zone-sets" },
42
42
  "skating-track": { sports: ["skating"], title: "The rink in parts: the finish straight, the first curve, the crossing straight, the second curve and the infield (a figure skating rink in thirds)", source: "docs/sources.md#zone-sets" },
43
43
  "athletics-track": { sports: ["athletics"], title: "The track in parts: the finish straight, the first bend, the back straight, the second bend, and the infield", source: "docs/sources.md#zone-sets" },
package/dist/homeside.js CHANGED
@@ -49,6 +49,10 @@ export const HOME_FIRST = Object.freeze({
49
49
  sumo: false,
50
50
  kyudo: false,
51
51
  dojo: false,
52
+ // A cage fight is between a red corner and a blue corner: no home side.
53
+ mma: false,
54
+ // A bout is between a red corner and a blue corner: no home side.
55
+ boxing: false,
52
56
  });
53
57
  /** The leagues whose convention is not their sport's: the international federation's lacrosse lists the home team first. */
54
58
  export const HOME_FIRST_LEAGUES = Object.freeze({
@@ -1,4 +1,5 @@
1
1
  import { type Span } from "./fields.ts";
2
+ import { type CombatFigures, type CombatLeague } from "./combat.ts";
2
3
  import type { LeagueBase } from "./types.ts";
3
4
  /** A mat: its length and width in metres. */
4
5
  export interface Mat {
@@ -167,7 +168,7 @@ export interface DojoFigures {
167
168
  };
168
169
  }
169
170
  /** The figures of a budō league. */
170
- export type BudoFigures = JudoFigures | KarateFigures | KendoFigures | SumoFigures | KyudoFigures | DojoFigures;
171
+ export type BudoFigures = JudoFigures | KarateFigures | KendoFigures | SumoFigures | KyudoFigures | DojoFigures | CombatFigures;
171
172
  /** The leagues of each budō sport. */
172
173
  export type JudoLeague = LeagueBase<"judo", JudoFigures>;
173
174
  export type KarateLeague = LeagueBase<"karate", KarateFigures>;
@@ -176,7 +177,7 @@ export type SumoLeague = LeagueBase<"sumo", SumoFigures>;
176
177
  export type KyudoLeague = LeagueBase<"kyudo", KyudoFigures>;
177
178
  export type DojoLeague = LeagueBase<"dojo", DojoFigures>;
178
179
  /** A league with a budō surface of its own. */
179
- export type BudoLeague = JudoLeague | KarateLeague | KendoLeague | SumoLeague | KyudoLeague | DojoLeague;
180
+ export type BudoLeague = JudoLeague | KarateLeague | KendoLeague | SumoLeague | KyudoLeague | DojoLeague | CombatLeague;
180
181
  /** The IJF's contest area (Sport and Organisation Rules 8.7, version of 21 January 2026) as drawn for the Games, the Worlds and the Masters. */
181
182
  export declare const IJF_CONTEST: JudoFigures;
182
183
  /** The All Japan Judo Federation's: the tatami of 182 cm by 91 cm, the red tatami of the danger zone, and the red and white tape 4 m (2 ken) apart (Tournament Management Guidebook 2023, 2-1, 2-3, 2-4). */
@@ -1,6 +1,7 @@
1
1
  // The surfaces of the budō (武道): the contest areas of judo and karate, the shiai-jo of kendo, the dohyō of sumo, the kyūdōjō, and the tatami of a dojo, each from the rulebook or the
2
2
  // federation's own page it was read in, with every figure in metres. A figure that no rule gives is said so in its comment (ours). Nothing here draws.
3
3
  import { span } from "./fields.js";
4
+ import { COMBAT_LEAGUES } from "./combat.js";
4
5
  /** The IJF's contest area (Sport and Organisation Rules 8.7, version of 21 January 2026) as drawn for the Games, the Worlds and the Masters. */
5
6
  export const IJF_CONTEST = {
6
7
  kind: "judo",
@@ -110,4 +111,4 @@ export const DOJO_LEAGUES = [
110
111
  { id: "judo-2x1", family: "dojo", name: { en: "Judo tatami, 2 m by 1 m", ja: "柔道畳(2m×1m)" }, unit: "m", dimensions: mat(2, 1, "the IJF's standard", "国際柔道連盟の標準"), sources: ["ijf-sor", "ajjf-guide"] },
111
112
  ];
112
113
  /** Every budō league, once each. */
113
- export const BUDO_LEAGUES = Object.freeze([...JUDO_LEAGUES, ...KARATE_LEAGUES, ...KENDO_LEAGUES, ...SUMO_LEAGUES, ...KYUDO_LEAGUES, ...DOJO_LEAGUES]);
114
+ export const BUDO_LEAGUES = Object.freeze([...JUDO_LEAGUES, ...KARATE_LEAGUES, ...KENDO_LEAGUES, ...SUMO_LEAGUES, ...KYUDO_LEAGUES, ...DOJO_LEAGUES, ...COMBAT_LEAGUES]);
@@ -0,0 +1,102 @@
1
+ import { type Span } from "./fields.ts";
2
+ import type { LeagueBase } from "./types.ts";
3
+ /** The figures of an MMA cage (a fenced area), in metres. */
4
+ export interface MmaFigures {
5
+ kind: "mma";
6
+ /**
7
+ * The width of the fenced area: no smaller than 20 feet and no larger than 32 feet (NRUC 5.010 3(a); IMMAF 1: 20 by 20 to 32 by 32 feet). A cage with eight sides is measured across the flats, as a cage is called "30 feet across". Two sizes
8
+ * are common (typical, not a rule): 30 feet, the larger, which is drawn, and 25 feet, the smaller one that promotions use in smaller venues.
9
+ */
10
+ width: Span;
11
+ /** The sides: the fenced area is round or has at least six equal sides (NRUC 5.010 3(a)); eight is the common cage and is drawn (typical). */
12
+ sides: number;
13
+ /** The platform: not more than 4 feet above the floor of the building, with suitable steps (NRUC 5.010 3(c)). 4 feet is the usual height of a cage on a stage and is drawn (typical). */
14
+ platform: {
15
+ height: Span;
16
+ steps: {
17
+ treads: number;
18
+ tread: number;
19
+ width: number;
20
+ };
21
+ };
22
+ /** The width of the platform beyond the fence, the apron: the rules give none, so 1.2 m, four feet of walkway (ours). */
23
+ apron: number;
24
+ /** The floor: at least a 1 inch layer of closed-cell foam under a canvas, duck or vinyl cover (NRUC 5.010 3(b)). */
25
+ foam: number;
26
+ /** The fence: posts of metal, not more than 6 inches across, run from the floor of the building to between 5 and 7 feet above the floor of the fenced area (NRUC 5.010 3(d)); the fence itself is 6 feet in the usual cage and is drawn (typical). */
27
+ fence: Span;
28
+ /** The width of a post, 6 inches at most (NRUC 5.010 3(d)), and the padding on it and on the rails: the rules say "properly padded" and give no size (ours: a pad 0.3 m across at the post, a rail pad 0.12 m deep). */
29
+ post: {
30
+ diameter: number;
31
+ pad: number;
32
+ rail: number;
33
+ };
34
+ /** The entrances: two, with secure locking mechanisms (NRUC 5.010 3(g)); the IMMAF asks for them on opposite sides and opening outward. Their width, 0.9 m, and height, 1.5 m, are typical and not a rule. */
35
+ gates: {
36
+ count: number;
37
+ width: number;
38
+ height: number;
39
+ };
40
+ }
41
+ /** A cage league: a regulation's cage. */
42
+ export type MmaLeague = LeagueBase<"mma", MmaFigures>;
43
+ /** The fenced area of the Nevada Rules of Unarmed Combat (NRUC 5.010 3), which most commissions copy. */
44
+ export declare const NRUC_CAGE: MmaFigures;
45
+ /** The amateur fenced area of the IMMAF's Unified Rules for amateur competition: the same sizes, with 40 mm of foam and a vinyl cover. */
46
+ export declare const IMMAF_CAGE: MmaFigures;
47
+ /** The leagues of each combat sport: for each the rulebook's own first. */
48
+ export declare const MMA_LEAGUES: readonly MmaLeague[];
49
+ /**
50
+ * The figures of a ring, in metres: the boxing ring and, with its own figures, the professional wrestling ring. A ring is a raised square platform whose canvas is bounded by ropes held by a post at each corner, with the apron
51
+ * beyond the ropes and steps up to it.
52
+ */
53
+ export interface RingFigures {
54
+ /** The ring, a square, measured inside the ropes. */
55
+ size: Span;
56
+ /** The platform: the most it may stand over the floor of the building, and the least it is drawn (0 puts the ring on the floor). */
57
+ platform: {
58
+ height: Span;
59
+ steps: {
60
+ sets: number;
61
+ treads: number;
62
+ tread: number;
63
+ width: number;
64
+ };
65
+ };
66
+ /** The apron, the canvas beyond the ropes: the least the rules ask (the drawn width is the least unless said), and the thickness of the padding under the canvas. */
67
+ apron: Span;
68
+ foam: Span;
69
+ /** The ropes: how many, their heights over the ring floor, and their thickness. */
70
+ ropes: {
71
+ count: number;
72
+ heights: readonly number[];
73
+ diameter: Span;
74
+ };
75
+ /** The corner posts: their height over the ring floor, their thickness, and how far each stands from the ropes (the ropes reach it by an extension). */
76
+ post: {
77
+ height: number;
78
+ diameter: number;
79
+ fromRopes: number;
80
+ pad: number;
81
+ };
82
+ }
83
+ /** The figures of a boxing ring, in metres. */
84
+ export interface BoxingFigures extends RingFigures {
85
+ kind: "boxing";
86
+ }
87
+ /** A boxing league: a regulation's ring. */
88
+ export type BoxingLeague = LeagueBase<"boxing", BoxingFigures>;
89
+ /** The ring of the Nevada Rules of Unarmed Combat (NRUC 2.050): 20 feet within the ropes, a floor 18 inches beyond them, a platform of at most 5 feet, posts of 58 inches over the ring floor at least 18 inches from the ropes, at least four ropes of at least 1 inch, the lowest 18 inches up. */
90
+ export declare const NRUC_RING: BoxingFigures;
91
+ /** The ring of Wisconsin's rule (SPS 192.38): 16 to 20 feet within the ropes, an apron of at least 18 inches, a platform of at most 4 feet with three sets of steps, ropes at 18, 30, 42 and 54 inches, posts at least 3 inches across and 58 inches high. */
92
+ export declare const WISCONSIN_RING: BoxingFigures;
93
+ /** The amateur ring of the WBC Amateur rules (Rule 1, 2026): 4.50 to 6.10 m within the ropes, a platform of 0.90 to 1.20 m, an apron of at least 0.50 m, ropes of 3 to 5 cm at 41, 71, 102 and 132 cm, three sets of steps. Ontario's amateur rules agree (4.9 to 6.1 m, ropes at 40, 70, 100 and 130 cm, an apron of at least 45 cm, a platform of at most 1.2 m). */
94
+ export declare const WBC_AMATEUR_RING: BoxingFigures;
95
+ /** The boxing leagues: the Nevada commission's first. */
96
+ export declare const BOXING_LEAGUES: readonly BoxingLeague[];
97
+ /** The figures of a combat league. */
98
+ export type CombatFigures = MmaFigures | BoxingFigures;
99
+ /** A league with a combat surface of its own. */
100
+ export type CombatLeague = MmaLeague | BoxingLeague;
101
+ /** Every combat league, once each. */
102
+ export declare const COMBAT_LEAGUES: readonly CombatLeague[];
@@ -0,0 +1,64 @@
1
+ // The surfaces of the combat sports: the MMA cage and, as they are added, the boxing ring, the wrestling mats and the professional wrestling ring, each from the rulebook or the regulation it was read in, with every
2
+ // figure in metres. A figure that no rule gives is said so in its comment (ours), and a figure that is common practice and not a rule is said so (typical). Nothing here draws.
3
+ import { span } from "./fields.js";
4
+ /** One foot, and one inch, in metres: the rules are written in feet and inches. */
5
+ const FT = 0.3048;
6
+ const IN = 0.0254;
7
+ /** The fenced area of the Nevada Rules of Unarmed Combat (NRUC 5.010 3), which most commissions copy. */
8
+ export const NRUC_CAGE = {
9
+ kind: "mma",
10
+ width: span(20 * FT, 32 * FT, 30 * FT),
11
+ sides: 8,
12
+ platform: { height: span(0, 4 * FT, 4 * FT), steps: { treads: 3, tread: 0.3, width: 1.2 } },
13
+ apron: 1.2,
14
+ foam: 1 * IN,
15
+ fence: span(5 * FT, 7 * FT, 6 * FT),
16
+ post: { diameter: 6 * IN, pad: 0.3, rail: 0.12 },
17
+ gates: { count: 2, width: 0.9, height: 1.5 },
18
+ };
19
+ /** The amateur fenced area of the IMMAF's Unified Rules for amateur competition: the same sizes, with 40 mm of foam and a vinyl cover. */
20
+ export const IMMAF_CAGE = { ...NRUC_CAGE, foam: 0.04 };
21
+ /** The leagues of each combat sport: for each the rulebook's own first. */
22
+ export const MMA_LEAGUES = [
23
+ { id: "nruc", family: "mma", name: { en: "MMA cage, Nevada Rules of Unarmed Combat", ja: "総合格闘技のケージ(ネバダ州の規則)" }, unit: "ft", figuresUnit: "m", dimensions: NRUC_CAGE, sources: ["nruc", "oar-230-070-0025", "wp-mma-cage"], builtIn: true },
24
+ { id: "immaf", family: "mma", name: { en: "MMA cage, IMMAF amateur rules", ja: "総合格闘技のケージ(IMMAFのアマチュア規則)" }, unit: "ft", figuresUnit: "m", dimensions: IMMAF_CAGE, sources: ["immaf-amateur", "nruc"] },
25
+ ];
26
+ const STEPS = { sets: 3, treads: 3, tread: 0.3, width: 1.2 };
27
+ /** The ring of the Nevada Rules of Unarmed Combat (NRUC 2.050): 20 feet within the ropes, a floor 18 inches beyond them, a platform of at most 5 feet, posts of 58 inches over the ring floor at least 18 inches from the ropes, at least four ropes of at least 1 inch, the lowest 18 inches up. */
28
+ export const NRUC_RING = {
29
+ kind: "boxing",
30
+ size: span(20 * FT, 20 * FT, 20 * FT),
31
+ platform: { height: span(0, 5 * FT, 4 * FT), steps: STEPS },
32
+ apron: span(18 * IN, 4 * FT, 2 * FT),
33
+ foam: span(1 * IN, 1.5 * IN, 1 * IN),
34
+ ropes: { count: 4, heights: [18 * IN, 30 * IN, 42 * IN, 54 * IN], diameter: span(1 * IN, 2 * IN, 1 * IN) },
35
+ post: { height: 58 * IN, diameter: 3 * IN, fromRopes: 18 * IN, pad: 0.3 },
36
+ };
37
+ /** The ring of Wisconsin's rule (SPS 192.38): 16 to 20 feet within the ropes, an apron of at least 18 inches, a platform of at most 4 feet with three sets of steps, ropes at 18, 30, 42 and 54 inches, posts at least 3 inches across and 58 inches high. */
38
+ export const WISCONSIN_RING = {
39
+ kind: "boxing",
40
+ size: span(16 * FT, 20 * FT, 20 * FT),
41
+ platform: { height: span(0, 4 * FT, 4 * FT), steps: STEPS },
42
+ apron: span(18 * IN, 4 * FT, 2 * FT),
43
+ foam: span(1 * IN, 1 * IN, 1 * IN),
44
+ ropes: { count: 4, heights: [18 * IN, 30 * IN, 42 * IN, 54 * IN], diameter: span(1 * IN, 2 * IN, 1 * IN) },
45
+ post: { height: 58 * IN, diameter: 3 * IN, fromRopes: 18 * IN, pad: 0.3 },
46
+ };
47
+ /** The amateur ring of the WBC Amateur rules (Rule 1, 2026): 4.50 to 6.10 m within the ropes, a platform of 0.90 to 1.20 m, an apron of at least 0.50 m, ropes of 3 to 5 cm at 41, 71, 102 and 132 cm, three sets of steps. Ontario's amateur rules agree (4.9 to 6.1 m, ropes at 40, 70, 100 and 130 cm, an apron of at least 45 cm, a platform of at most 1.2 m). */
48
+ export const WBC_AMATEUR_RING = {
49
+ kind: "boxing",
50
+ size: span(4.5, 6.1, 6.1),
51
+ platform: { height: span(0.9, 1.2, 1.2), steps: STEPS },
52
+ apron: span(0.5, 1.5, 0.5),
53
+ foam: span(0.01, 0.025, 0.02),
54
+ ropes: { count: 4, heights: [0.41, 0.71, 1.02, 1.32], diameter: span(0.03, 0.05, 0.04) },
55
+ post: { height: 1.5, diameter: 0.076, fromRopes: 0.45, pad: 0.3 },
56
+ };
57
+ /** The boxing leagues: the Nevada commission's first. */
58
+ export const BOXING_LEAGUES = [
59
+ { id: "nruc-ring", family: "boxing", name: { en: "Boxing ring, Nevada Rules of Unarmed Combat", ja: "ボクシングのリング(ネバダ州の規則)" }, unit: "ft", figuresUnit: "m", dimensions: NRUC_RING, sources: ["nruc", "wp-boxing-ring"], builtIn: true },
60
+ { id: "wisconsin", family: "boxing", name: { en: "Boxing ring, Wisconsin", ja: "ボクシングのリング(ウィスコンシン州の規則)" }, unit: "ft", figuresUnit: "m", dimensions: WISCONSIN_RING, sources: ["wi-sps-192", "wp-boxing-ring"] },
61
+ { id: "wbc-amateur", family: "boxing", name: { en: "Boxing ring, WBC Amateur rules", ja: "ボクシングのリング(WBCアマチュア規則)" }, unit: "m", dimensions: WBC_AMATEUR_RING, sources: ["wbc-amateur", "ontario-amateur-boxing"] },
62
+ ];
63
+ /** Every combat league, once each. */
64
+ export const COMBAT_LEAGUES = Object.freeze([...MMA_LEAGUES, ...BOXING_LEAGUES]);
@@ -3,6 +3,7 @@
3
3
  const source = (s) => [s.id, Object.freeze(s)];
4
4
  const READ = "2026-10-13";
5
5
  const BUDO_READ = "2026-10-14";
6
+ const COMBAT_READ = "2026-10-20";
6
7
  /** Every document a league's figures were read from, by id. */
7
8
  export const LEAGUE_SOURCES = Object.freeze(Object.fromEntries([
8
9
  // The leagues the package started with.
@@ -124,6 +125,15 @@ export const LEAGUE_SOURCES = Object.freeze(Object.fromEntries([
124
125
  source({ id: "budokan-overview", title: "日本武道館建物等概要 (Nippon Budokan: buildings and seats), the arena plan: a regular octagon of 55 m across, 60 m corner to corner, 23 m a side, 2,513 m²", by: "公益財団法人日本武道館 (Nippon Budokan)", edition: "the leaflet of October 2021", url: "https://www.nipponbudokan.or.jp/pdf/202110.pdf", read: BUDO_READ, note: "opened 3 October 1964; at most 14,501; the arena as an octagon" }),
125
126
  source({ id: "butokuden-ja", title: "旧武徳殿 (Japanese Wikipedia): built in 1899 by the Dai Nippon Butokukai, designed by Matsumuro Shigemitsu, an Important Cultural Property since 1996, 1,200 seats", by: "Wikipedia contributors (CC BY-SA 4.0)", edition: "the article as read on the date given", url: "https://ja.wikipedia.org/wiki/%E6%97%A7%E6%AD%A6%E5%BE%B3%E6%AE%BF", read: BUDO_READ, note: "the dates, the address and the seats; no mat count was found" }),
126
127
  source({ id: "wikipedia-halls", title: "Wikipedia and Wikidata on the halls: Nippon Budokan, Ryōgoku Kokugikan, Kodokan, Butokuden and the Budokwai (their capacities, opening dates, coordinates and the events held in them)", by: "Wikipedia contributors (CC BY-SA 4.0) and Wikidata (CC0)", edition: "the articles and items as read on the date given", url: "https://www.wikidata.org/wiki/Q386246", read: BUDO_READ, note: "capacity 11,098 (Wikidata P1083 and English Wikipedia) for the Kokugikan; 14,471 for the Budokan; the items Q1165384, Q386246, Q176821, Q1017987 and Q3646473; no text is copied" }),
128
+ // The combat sports.
129
+ source({ id: "nruc", title: "Nevada Rules of Unarmed Combat, NRUC 5.010 (requirements for a ring or fenced area for mixed martial arts: a fenced area of 20 to 32 feet, a platform of at most 4 feet with steps, metal posts of at most 6 inches reaching 5 to 7 feet over the fenced floor, padding, a fence that cannot be broken through, two entrances) and NRUC 2.050 (a boxing ring)", by: "Nevada Athletic Commission", edition: "adopted 20 September 2022, amended 29 October 2024 (the file of 20 November 2024)", url: "https://boxing.nv.gov/uploadedFiles/boxingnvgov/content/faq/sections/2024-11-20-NRUC-OFFICIAL-FINAL.pdf", read: COMBAT_READ, note: "the cage's width, the platform's greatest height, the fence posts' height, the floor's foam, the two gates; the boxing ring's figures are read here too" }),
130
+ source({ id: "oar-230-070-0025", title: "Oregon Administrative Rule 230-070-0025, Mixed Martial Arts Fenced Area or Ring", by: "Oregon Department of Consumer and Business Services", edition: "the rule as read on the date given", url: "https://oregon.public.law/rules/oar_230-070-0025", read: COMBAT_READ, note: "the fenced area is round or has at least six equal sides, 18 to 32 feet wide, a platform of at most 4 feet, at least six posts of 6 to 7 feet, at least two entrances; a second commission's rule beside Nevada's, which is the one drawn" }),
131
+ source({ id: "immaf-amateur", title: "IMMAF Mixed Martial Arts Unified Rules for amateur, junior and senior competition: the fighting area and the fence", by: "International Mixed Martial Arts Federation", edition: "January 2021", url: "https://immaf.org/wp-content/uploads/2025/11/MMA-UNIFIED-RULES-FOR-AMATEUR-JUNIOR-SENIOR-January-2021.pdf", read: COMBAT_READ, note: "a fighting area circular or with equal sides, 20 by 20 to 32 by 32 feet, at least 40 mm of high-density foam under a vinyl cover, two gated entrances that open outward, on opposite sides if possible" }),
132
+ source({ id: "wp-mma-cage", title: "Mixed martial arts cage (English Wikipedia), and Mixed martial arts, Fighting area: the cage called a cage or an eight-sided cage; the larger cage of 30 feet across with a 6 foot fence on a 4 foot platform, the smaller of 25 feet, foam padding along the top of the fence and between its eight sections, two gates opposite each other", by: "Wikipedia contributors (CC BY-SA 4.0), read for facts only", edition: "revision 1377260922 (Mixed martial arts cage) and revision 1381048799 (Mixed martial arts)", url: "https://en.wikipedia.org/w/index.php?title=Mixed_martial_arts_cage&oldid=1377260922", read: COMBAT_READ, note: "what is typical and not a rule: 30 feet and 25 feet across, a 6 foot fence, a 4 foot platform, eight sides, two gates opposite each other; the article says the name of one promotion's cage is a trademark, so the package names the cage for what it is" }),
133
+ source({ id: "wi-sps-192", title: "Wisconsin Administrative Code SPS 192.38, Ring (a square of 16 to 20 feet within the ropes, an apron of at least 18 inches, a platform of at most 4 feet with three sets of steps, ropes at 18, 30, 42 and 54 inches held by two spacer ties a side, posts at least 3 inches across and 58 inches over the ring floor, the ropes reaching them by an extension of at least 18 inches, a floor of 1 inch of padding under canvas)", by: "Wisconsin Department of Safety and Professional Services", edition: "SPS 192 as in force (history CR 17-016, Register No. 743, effective 1 December 2017)", url: "https://docs.legis.wisconsin.gov/document/administrativecode/SPS%20192.38", read: COMBAT_READ, note: "the ring's size range, the apron, the platform, the steps, the ropes' heights, the posts" }),
134
+ source({ id: "wbc-amateur", title: "WBC Amateur competition rules, Rule 1, The Ring (4.50 to 6.10 m inside the ropes, a platform of 0.90 to 1.20 m, an apron of at least 0.50 m, ropes of 3 to 5 cm at 41, 71, 102 and 132 cm, three sets of steps, corner colours)", by: "World Boxing Council Amateur", edition: "updated 2026 (the file of October 2025)", url: "https://wbcboxing.com/amateur/esp/wp-content/uploads/2025/10/WBC-AMATEUR-RULES-UPDATED-2026.pdf", read: COMBAT_READ, note: "the amateur ring's figures; the corner colours (red and blue for the boxers, white neutral) are its alternative colours" }),
135
+ source({ id: "ontario-amateur-boxing", title: "Amateur combative sport rules: Boxing, Part II: Competitive field of play, 1.1 to 1.1.6 (4.9 to 6.1 m inside the ropes, a platform of at most 1.2 m, an apron of at least 45 cm, ropes of 2 to 5 cm at 40, 70, 100 and 130 cm, padding of 2.5 to 3.75 cm under a canvas, the red corner on the left nearest the jury table)", by: "Ontario Ministry of Sport", edition: "published 17 December 2024, updated 22 May 2025", url: "https://www.ontario.ca/document/amateur-combative-sport-rules-boxing/part-ii-competitive-field-play", read: COMBAT_READ, note: "a second amateur rule beside the WBC's; the red corner at the left" }),
136
+ source({ id: "wp-boxing-ring", title: "Boxing ring (English Wikipedia): a square raised platform with a post at each corner, four ropes, a red corner and a blue corner; a standard ring 16 to 24 feet a side between the ropes with 2 feet outside, a platform 3 to 4 feet from the ground, ropes at 18, 30, 42 and 54 inches", by: "Wikipedia contributors (CC BY-SA 4.0), read for facts only", edition: "revision 1370389178", url: "https://en.wikipedia.org/w/index.php?title=Boxing_ring&oldid=1370389178", read: COMBAT_READ, note: "what is typical and not a rule: the 2 foot apron, the 4 foot platform, the heights of the ropes, the red and blue corners" }),
127
137
  // Baseball and softball.
128
138
  source({ id: "obr-2026", title: "Official Baseball Rules, 2.01, 2.03 and 2.04 (the rules Minor League Baseball plays under)", by: "Major League Baseball", edition: "2026", url: "https://mktg.mlbstatic.com/mlb/official-information/2026-official-baseball-rules.pdf", read: READ, note: "bases 18 inches (2.03), the fence at least 325 and 400 feet (2.01 note); Minor League Baseball's own facility standards are not public" }),
129
139
  source({ id: "ncaa-baseball", title: "NCAA Baseball Rules, Rule 1, and the field diagram", by: "NCAA", edition: "2027 and 2028 rules book (printed October 2026)", url: "https://ncaaorg.s3.amazonaws.com/championships/sports/baseball/rules/PRMBA_RulesBook.pdf", read: READ, note: "90-foot square (1-2-a), recommended fence 330, 375 and 400 feet (1-2-b), boxes 4 by 6 feet (1-3-a), backstop 45 feet (1-5-b); diagram: PRMBA_Re-imagedFieldDiagrams.pdf" }),
@@ -5,7 +5,7 @@ export interface Named {
5
5
  ja: string;
6
6
  }
7
7
  /** The families of surface a league is a variant of. */
8
- export type Family = "basketball" | "hockey" | "football" | "baseball" | "curling" | "tennis" | "volleyball" | "badminton" | "pickleball" | "tabletennis" | "padel" | "squash" | "racquetball" | "sepaktakraw" | "teqball" | "roundnet" | "netball" | "lacrosse" | "rugby" | "afl" | "gaelic" | "cricket" | "fieldhockey" | "handball" | "athletics" | "skating" | "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo";
8
+ export type Family = "basketball" | "hockey" | "football" | "baseball" | "curling" | "tennis" | "volleyball" | "badminton" | "pickleball" | "tabletennis" | "padel" | "squash" | "racquetball" | "sepaktakraw" | "teqball" | "roundnet" | "netball" | "lacrosse" | "rugby" | "afl" | "gaelic" | "cricket" | "fieldhockey" | "handball" | "athletics" | "skating" | "judo" | "karate" | "kendo" | "sumo" | "kyudo" | "dojo" | "mma" | "boxing";
9
9
  /** The state of one marking from a season on, in a league's own history: the season it began, what it was, and its sources. */
10
10
  export interface LeagueSpan<T> {
11
11
  /** The first season it was in force, or `null` for the state before the first change that is cited. */
package/dist/leagues.d.ts CHANGED
@@ -36,6 +36,7 @@ export { WA_STANDARD_TRACK, WA_SHORT_TRACK, WA_STAGGER_TABLE, WA_SHORT_STAGGER_T
36
36
  export type { SkatingFigures, SkatingLeague, LongTrackFigures, ShortTrackFigures, FigureRinkFigures } from "./leagues/skating.ts";
37
37
  export { ISU_LONG_TRACK, ISU_SHORT_TRACK, ISU_FIGURE_RINK, ISU_LONG_TRACK_TABLE } from "./leagues/skating.ts";
38
38
  export type { BudoFigures, BudoLeague, DojoFigures, DojoLeague, JudoFigures, JudoLeague, KarateFigures, KarateLeague, KendoFigures, KendoLeague, KyudoFigures, KyudoLeague, Mat, SumoFigures, SumoLeague } from "./leagues/budo.ts";
39
+ export { BOXING_LEAGUES, IMMAF_CAGE, MMA_LEAGUES, NRUC_CAGE, NRUC_RING, WBC_AMATEUR_RING, WISCONSIN_RING } from "./leagues/combat.ts";
39
40
  export { AJJF_CONTEST, BUDO_LEAGUES, ENTEKI, FIK_SHIAI_JO, IJF_CONTEST, IJF_KATA, JSA_DOHYO, KINTEKI, WKF_KATA, WKF_KUMITE } from "./leagues/budo.ts";
40
41
  export type { BadmintonFigures, BadmintonLeague, FreeZone, FreeZones, NetFigures, NetLeague, NetSport, NetballFigures, NetballLeague, PadelFigures, PadelLeague, PickleballFigures, PickleballLeague, RacquetballFigures, RacquetballLeague, RoundnetFigures, RoundnetLeague, SepakTakrawFigures, SepakTakrawLeague, SquashFigures, SquashLeague, TableTennisFigures, TableTennisLeague, TennisFigures, TennisLeague, TeqballFigures, TeqballLeague, VolleyballFigures, VolleyballLeague } from "./leagues/courts.ts";
41
42
  export type { CourtFigures, CourtMarks } from "./leagues/basketball.ts";