@johnmorrisdotca/dougu-sports 0.2.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@ All notable changes to this project are written here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.1] - 2026-10-14
10
+
11
+ Eleven balls and implements drawn to the rulebook's proportions, flat emblems turning with the hand, and the gallery as one page per language.
12
+
13
+ ### Fixed
14
+
15
+ - **Eleven balls and implements were drawn out of proportion to the rulebook, the puck by half.** The puck was drawn 1 : 6 (thickness to diameter) where IIHF Rule 13 makes it 1 : 3, because the half thickness was divided by two twice; the curling stone's body was 0.200 of its diameter where the rules (a 914 mm circumference, a height of at least 114 mm) make it 0.392, and its handle now stands on the taller body; the feather shuttlecocks' feathers were 50 mm long where the Law gives 62 to 70 mm (now 66 mm); the discus was 2.2% too thin; the American, Canadian and IFAF footballs were 9% too slim and the rugby ball 6% too slim, with their outlines 4 to 7% too short; the Australian football was 12% too fat. The footballs and the rugby ball now take their width from the girth (the circumference over pi) and their `point` from the long girth, and the Australian football is an ellipsoid of its two girths. Nothing in the `size` metadata changed. A new test, `test/proportions.test.ts`, draws every ball and implement edge-on, measures the drawing itself (not the constants it was made from) and holds its ratio, size and, for the pointed balls, outline to the figures in `size` within 2%; it also checks that only a ball or an implement claims a real size, and that `lineup` at the true scale gives every piece the same pixels per millimetre. The ball contact sheets in `docs/pieces` are redrawn.
16
+
17
+ - **A flat thing turned the other way from a ball and a cup.** A ball rolls and a cup slides its light so that a positive yaw carries the front toward the right (and a drag to the right, or the Right arrow, is a positive yaw), but the coin turn of a medal, a crest, a pictogram, a piece of gear and a pennant showed its rim on the right, as if the front went left, so that dragging a medal to the right turned it against the hand. The rim is now on the left for a positive yaw (and on the right for a negative one), the edge that comes toward the eye, in the still and the line style alike. Only the side of the rim changes: the squeeze, the back and every angle are as before, `yaw` and `setAngles` mean the same, and `bindTurn` keeps its signs (right is more yaw, up is more pitch). A unit test holds the rim's side for every flat emblem and the ball's roll, and the demo's tests drag a ball, a cup and a medal to the right and measure which way each goes.
18
+
19
+ ### Changed
20
+
21
+ - **The gallery is two pages, one language each.** `docs/gallery.md` (English) and `docs/gallery.ja.md` (日本語) are both written by `scripts/make-gallery.mjs`, linked to each other at the top, with the columns Id, name, kind, source and description. The Constant and Import columns are gone (the long paths made GitHub squeeze the Japanese column to one character wide, so the names stacked): each page states once that the constant is the id in capitals (`trophy:lidded-cup` becomes `TROPHY_LIDDED_CUP`) and that it is imported from `@johnmorrisdotca/dougu-sports/trophy/lidded-cup`, and the generator stops if the rule fails for any emblem. `pnpm test:readme` checks both pages and accepts the rule as naming each emblem's constant.
22
+
9
23
  ## [0.2.0] - 2026-10-14
10
24
 
11
25
  The package is now 167 emblems (24 trophies, 5 awards, 6 medals, 4 crests, 38 sports, 47 balls, 42 gear and a pennant) and every one is its own drawing: the copied art and its licences are gone, the five Japanese sports and budo (sumo, kendo, judo, kyudo, karate) are in, text can be engraved on medals, plaques, cups, a ring, a banner and a pennant, every emblem turns through one coin-flip turn, and there are labels in two languages, data addresses, sprites, a podium, an award case, `sportEquipment` for all 38 sports and `bindTurn` to turn an emblem with a drag, the keys or a clock.
@@ -75,7 +89,8 @@ First preview: the name, the API and two sample emblems. The full set of trophie
75
89
  - Tests for the drawing, the ids, the turns and the bundle size of one item; a package check that installs the packed tarball and bundles one item from it.
76
90
  - The demo (a playground with a turn slider, and a gallery page), `docs/layout.md`, `docs/turning.md`, `docs/gallery.md`, the family header and footer, the CI, Pages and Release workflows, and `LICENSES/` and `NOTICE.md` ready for third-party art.
77
91
 
78
- [Unreleased]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.2.0...HEAD
92
+ [Unreleased]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.2.1...HEAD
93
+ [0.2.1]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.2.0...v0.2.1
79
94
  [0.2.0]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.1.0...v0.2.0
80
95
  [0.1.0]: https://github.com/johnmorrisdotca/dougu-sports/compare/v0.0.1...v0.1.0
81
96
  [0.0.1]: https://github.com/johnmorrisdotca/dougu-sports/releases/tag/v0.0.1
package/README.md CHANGED
@@ -83,7 +83,7 @@ Put the string in a page (`element.innerHTML = svg`), write it to a file, or sen
83
83
  </tr>
84
84
  </table>
85
85
 
86
- Every emblem, with its id in both styles and a button that copies it, is on the [gallery page](https://johnmorrisdotca.github.io/dougu-sports/gallery.html); the same list as text, with its names, kind, source and the import that takes it alone, is [docs/gallery.md](./docs/gallery.md), made from the built package. [docs/emblems.md](./docs/emblems.md) says how the art is drawn.
86
+ Every emblem, with its id in both styles and a button that copies it, is on the [gallery page](https://johnmorrisdotca.github.io/dougu-sports/gallery.html); the same list as text, with its name, kind, source and description, is [docs/gallery.md](./docs/gallery.md) ([日本語](./docs/gallery.ja.md)), made from the built package. [docs/emblems.md](./docs/emblems.md) says how the art is drawn.
87
87
 
88
88
  ## Use it in your project
89
89
 
@@ -424,7 +424,7 @@ The thirty-eight sports the ball, gear and sport emblems are made for are `SPORT
424
424
  | `emblemInfo(id)`, `emblemName(id, language?)`, `emblemModel(id)` | What an emblem is: its short names (which are its accessible names), kind and source, a longer `description` where it has one, and the data it is drawn from |
425
425
  | `EMBLEMS` | Every emblem as markup in the metal style at 64 pixels, by id |
426
426
  | `EMBLEM_TABLE` | Every emblem object, by id |
427
- | `TROPHY_HOCKEY_CUP`, `MEDAL_GOLD`, `CREST_SHIELD`, `SPORT_NHL`, `BALL_SOCCER`, `GEAR_HURLEY`... | Each emblem as a constant, named by its id in capitals (`trophy:hockey-cup`); [docs/gallery.md](./docs/gallery.md) lists every constant with its id and the module that takes it alone |
427
+ | `TROPHY_HOCKEY_CUP`, `MEDAL_GOLD`, `CREST_SHIELD`, `SPORT_NHL`, `BALL_SOCCER`, `GEAR_HURLEY`... | Each emblem as a constant, named by its id in capitals (`trophy:hockey-cup`); [docs/gallery.md](./docs/gallery.md) ([日本語](./docs/gallery.ja.md)) lists every emblem and states the rule for its constant and the module that takes it alone |
428
428
  | `EMBLEM_IDS`, `EMBLEM_GROUPS`, `EMBLEM_FOLDERS`, `EMBLEM_ALIASES` | Every id, the groups, the folder of each group's art, and the short names (`cup` for `trophy:cup`) |
429
429
  | `AWARD_IDS`, `TROPHY_IDS`, `MEDAL_IDS`, `CREST_IDS`, `BALL_IDS`, `GEAR_IDS`, `SPORT_IDS`, `PENNANT_IDS` | The ids of each group |
430
430
  | `MEDAL_KINDS`, `CREST_SHAPES` | The kinds of medal, and the shapes of crest |
@@ -523,7 +523,7 @@ Every emblem is drawn for this package, and nothing is copied from another proje
523
523
  ### The family
524
524
 
525
525
  <!-- family:start (made by scripts/family-readme.mjs from scripts/family-template.mjs; change those, not this) -->
526
- Dougu Sports is one of twenty-seven packages, each made for the same site, each at
526
+ Dougu Sports is one of twenty-eight packages, each made for the same site, each at
527
527
  [github.com/johnmorrisdotca](https://github.com/johnmorrisdotca). The code of every one is MIT.
528
528
 
529
529
  - [Korokoro](https://github.com/johnmorrisdotca/korokoro) (コロコロ): dice, with notation, exact odds, real sounds and the dice of many games. [Demo](https://johnmorrisdotca.github.io/korokoro/).
@@ -553,8 +553,9 @@ Dougu Sports is one of twenty-seven packages, each made for the same site, each
553
553
  - [Kyogijo Stadiums](https://github.com/johnmorrisdotca/kyogijo-stadiums) (競技場): sports fields, rinks, courts and ballparks drawn to scale as SVG, with each team and the score on them. [Demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/).
554
554
  - [Taisen Brackets](https://github.com/johnmorrisdotca/taisen-brackets) (対戦): a sport-agnostic tournament bracket, drawn as SVG, from a list of ties. [Demo](https://johnmorrisdotca.github.io/taisen-brackets/).
555
555
  - [Dougu Sports](https://github.com/johnmorrisdotca/dougu-sports) (道具): sports emblems as SVG: trophies, medals, crests, and the ball and gear of every sport, with optional 3D turning. [Demo](https://johnmorrisdotca.github.io/dougu-sports/).
556
+ - [Senshu Jerseys](https://github.com/johnmorrisdotca/senshu-jerseys) (選手): sports jerseys, sweaters, kits, caps, helmets and retired-number banners as SVG, front and back, with optional 2.5D turning. [Demo](https://johnmorrisdotca.github.io/senshu-jerseys/).
556
557
 
557
- **This package is Dougu Sports.** The demos of all twenty-seven share one header and footer, so each links the rest.
558
+ **This package is Dougu Sports.** The demos of all twenty-eight share one header and footer, so each links the rest.
558
559
  <!-- family:end -->
559
560
 
560
561
  ## Development
@@ -564,7 +565,7 @@ pnpm install
564
565
  pnpm check # lint, types, tests, the build and the packed package
565
566
  pnpm dev # the demo on http://127.0.0.1:7200/
566
567
  pnpm test:demo # the demo in a real browser
567
- pnpm test:readme # the README's and the docs' examples, and docs/gallery.md
568
+ pnpm test:readme # the README's and the docs' examples, and docs/gallery.md and docs/gallery.ja.md
568
569
  pnpm screenshots # retake the README's pictures
569
570
  ```
570
571
 
@@ -576,7 +577,7 @@ Bug reports and ideas go in the [issues](https://github.com/johnmorrisdotca/doug
576
577
 
577
578
  ## Changes
578
579
 
579
- See [CHANGELOG.md](./CHANGELOG.md). 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
580
+ See [CHANGELOG.md](./CHANGELOG.md). 0.2.1 draws every ball and implement to the rulebook's proportions; 0.2.0 is 167 emblems, every one drawn here, with the Japanese sports, engraving, a turn for every emblem and the compose helpers; 0.1.0 brought the first full set of 148; 0.0.1 was the first preview.
580
581
 
581
582
  ## Credits
582
583
 
@@ -9,7 +9,8 @@ export const BALL_AFL = /* @__PURE__ */ defineBall({
9
9
  ja: "オージーボール",
10
10
  size: { extentMm: 280, lengthMm: 280, circumferenceMm: 550.0, longCircumferenceMm: 725.0, rule: "Laws of Australian Football 2026, Law 4 The Football, 4.1 Specifications", note: "The Laws give the two circumferences (720-730 mm and 545-555 mm) and no length: 280 mm is the length of an ellipse of those two girths." },
11
11
  spec: {
12
- axes: [1, 0.7, 0.7],
12
+ // An ellipsoid of the Laws' two girths: 550 mm round the middle makes it 175 mm across, 280 mm long.
13
+ axes: [1, 0.6253, 0.6253],
13
14
  view: { yaw: -30, pitch: 20, roll: -25 },
14
15
  base: "#b4202b",
15
16
  mat: { gloss: 0.55, shade: "#2a0408", grain: { f: 1.0, a: 0.34 } },
@@ -1,3 +1,5 @@
1
1
  export declare const BALL_CURLING_STONE: import("./ball.core.ts").Piece<{
2
2
  slices: number;
3
+ } & {
4
+ view: import("./ball.object.ts").ObjectView;
3
5
  }>;
@@ -7,6 +7,10 @@ import { num } from "./ball.core.js";
7
7
  const GRANITE = "#8d929a";
8
8
  const POLISHED = "#4d525b";
9
9
  const HANDLE = "#d4202c";
10
+ // The stack's y is in units of the radius, so the half height is the height of the stone (114 mm at least) over its largest diameter (291 mm, a 914 mm circumference): 0.392, and the stone runs from -H to H. The slices
11
+ // below are where the profile changes, as a fraction of H.
12
+ const H = 114 / 290.9;
13
+ const y = (t) => t * H;
10
14
  export const BALL_CURLING_STONE = /* @__PURE__ */ defineObject({
11
15
  id: "ball:curling-stone",
12
16
  en: "Curling stone",
@@ -16,11 +20,11 @@ export const BALL_CURLING_STONE = /* @__PURE__ */ defineObject({
16
20
  model: { slices: 6 },
17
21
  draw: (c) => drawStack(c, {
18
22
  slices: [
19
- { y0: -0.2, y1: -0.15, r: [0.74, 0.8], colour: POLISHED, outline: true },
20
- { y0: -0.15, y1: -0.04, r: [0.8, 0.94], colour: GRANITE },
21
- { y0: -0.04, y1: 0.08, r: 1, colour: POLISHED, outline: true },
22
- { y0: 0.08, y1: 0.15, r: [1, 0.9], colour: GRANITE },
23
- { y0: 0.15, y1: 0.2, r: [0.9, 0.78], colour: GRANITE, outline: true },
23
+ { y0: y(-1), y1: y(-0.75), r: [0.74, 0.8], colour: POLISHED, outline: true },
24
+ { y0: y(-0.75), y1: y(-0.2), r: [0.8, 0.94], colour: GRANITE },
25
+ { y0: y(-0.2), y1: y(0.4), r: 1, colour: POLISHED, outline: true },
26
+ { y0: y(0.4), y1: y(0.75), r: [1, 0.9], colour: GRANITE },
27
+ { y0: y(0.75), y1: y(1), r: [0.9, 0.78], colour: GRANITE, outline: true },
24
28
  ],
25
29
  top: "#9a9fa7",
26
30
  bottom: "#6a6f78",
@@ -30,8 +34,8 @@ export const BALL_CURLING_STONE = /* @__PURE__ */ defineObject({
30
34
  dish: { r: 0.74, colour: "#555a63" },
31
35
  extra: (ctx) => {
32
36
  // The handle: a post and a bar, a capsule in 3D (a thick line with round ends).
33
- const post0 = ctx.at([0, 0.2, 0]), post1 = ctx.at([0, 0.34, 0]);
34
- const a = [-0.42, 0.4, 0], b = [0.42, 0.4, 0];
37
+ const post0 = ctx.at([0, H, 0]), post1 = ctx.at([0, H + 0.14, 0]);
38
+ const a = [-0.42, H + 0.2, 0], b = [0.42, H + 0.2, 0];
35
39
  const p = ctx.at(a), q = ctx.at(b);
36
40
  const colour = ctx.opts.style === "line" ? "currentColor" : (ctx.opts.tint ?? HANDLE);
37
41
  const line = ctx.opts.style === "line";
@@ -7,7 +7,7 @@ export const BALL_DISCUS = /* @__PURE__ */ defineBall({
7
7
  ja: "円盤",
8
8
  size: { extentMm: 220, diameterMm: 220, thicknessMm: 45, massG: [2000, 2000], rule: "World Athletics Technical Rules (C2.1), Rule 34 Discus Throw - 'The discus shall conform to the following specifications'", note: "Men's discus, 219 to 221 mm across the rim and 44 to 46 mm thick at the middle; 2 kg is a minimum." },
9
9
  spec: {
10
- axes: [1, 0.2, 1],
10
+ axes: [1, 45 / 220, 1],
11
11
  view: { yaw: 25, pitch: 52, roll: -12 },
12
12
  base: "#3b3f48",
13
13
  mat: { gloss: 0.7, shade: "#050608", grain: { f: 1.4, a: 0.12 } },
@@ -14,18 +14,19 @@ const upperArc = (ring) => {
14
14
  return run;
15
15
  };
16
16
  const BROWN = "#8b4a21";
17
- const AXES = [1, 0.55, 0.55];
17
+ // The shape is worked out from the rule's figures (see test/proportions.test.ts): the width over the length is the girth over pi over the length, and `point` is chosen so that the outline, tip round to tip,
18
+ // is the long girth.
18
19
  const VIEW = { yaw: -20, pitch: 35, roll: -35 };
19
20
  const MAT = { gloss: 0.5, shade: "#2a1006", grain: { f: 0.95, a: 0.42 } };
20
- function football(id, en, ja, size, stripes, colour) {
21
+ function football(id, en, ja, size, shape, stripes, colour) {
21
22
  return defineBall({
22
23
  id,
23
24
  en,
24
25
  ja,
25
26
  size,
26
27
  spec: {
27
- axes: AXES,
28
- point: 0.85,
28
+ axes: [1, shape.width, shape.width],
29
+ point: shape.point,
29
30
  view: VIEW,
30
31
  base: colour,
31
32
  mat: MAT,
@@ -45,7 +46,8 @@ function football(id, en, ja, size, stripes, colour) {
45
46
  },
46
47
  });
47
48
  }
49
+ const NFL_SHAPE = { width: 0.6044, point: 0.541 };
48
50
  const SIZE = { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "NFL Rulebook, Rule 2 The Ball, Section 1 (Dimensions)" };
49
- export const BALL_FOOTBALL = /* @__PURE__ */ football("ball:football", "Football (NFL)", "アメフトボール(NFL)", SIZE, "none", BROWN);
50
- export const BALL_FOOTBALL_CFL = /* @__PURE__ */ football("ball:football-cfl", "Football (CFL)", "フットボール(CFL)", { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "CFL Official Playing Rules, Rule 1 Conduct of the Game, Section 2 The Ball" }, "ring", BROWN);
51
- export const BALL_FOOTBALL_IFAF = /* @__PURE__ */ football("ball:football-ifaf", "Football (IFAF)", "アメフトボール(IFAF)", { extentMm: 283.4, lengthMm: 283.4, circumferenceMm: 533.4, longCircumferenceMm: 714.38, massG: [396.89, 425.24], rule: "IFAF Football Rules and Interpretations 2026, Rule 1 (The Game, Field, Players and Equipment), Section 3 The Ball, Article 1 a-i", note: "The IFAF figures are in a diagram of the rules, read from the rendered page." }, "top", "#a8693a");
51
+ export const BALL_FOOTBALL = /* @__PURE__ */ football("ball:football", "Football (NFL)", "アメフトボール(NFL)", SIZE, NFL_SHAPE, "none", BROWN);
52
+ export const BALL_FOOTBALL_CFL = /* @__PURE__ */ football("ball:football-cfl", "Football (CFL)", "フットボール(CFL)", { extentMm: 282.6, lengthMm: 282.6, circumferenceMm: 536.58, longCircumferenceMm: 717.55, massG: [396.89, 425.24], rule: "CFL Official Playing Rules, Rule 1 Conduct of the Game, Section 2 The Ball" }, NFL_SHAPE, "ring", BROWN);
53
+ export const BALL_FOOTBALL_IFAF = /* @__PURE__ */ football("ball:football-ifaf", "Football (IFAF)", "アメフトボール(IFAF)", { extentMm: 283.4, lengthMm: 283.4, circumferenceMm: 533.4, longCircumferenceMm: 714.38, massG: [396.89, 425.24], rule: "IFAF Football Rules and Interpretations 2026, Rule 1 (The Game, Field, Players and Equipment), Section 3 The Ball, Article 1 a-i", note: "The IFAF figures are in a diagram of the rules, read from the rendered page." }, { width: 0.599, point: 0.567 }, "top", "#a8693a");
@@ -18,7 +18,7 @@ export interface ObjectContext {
18
18
  /** A point of the object in its own space to the box: x, y and depth (positive toward you); one unit is 46 box units. */
19
19
  at(p: V3): [number, number, number];
20
20
  }
21
- export interface ObjectDef<Model> {
21
+ export interface ObjectDef<Model extends object> {
22
22
  id: string;
23
23
  en: string;
24
24
  ja: string;
@@ -35,4 +35,7 @@ export interface ObjectDef<Model> {
35
35
  body: string;
36
36
  };
37
37
  }
38
- export declare function defineObject<Model>(def: ObjectDef<Model>): Piece<Model>;
38
+ /** An object as a piece. Its `model` is the def's model with the default `view` added, so that a view of it (or a test) can turn it to any aspect from there. */
39
+ export declare function defineObject<Model extends object>(def: ObjectDef<Model>): Piece<Model & {
40
+ view: ObjectView;
41
+ }>;
@@ -2,6 +2,7 @@
2
2
  import { escape, hashOf, labelled, radians, resolveOptions, wrapPiece } from "./ball.core.js";
3
3
  import { apply, rotation } from "./ball.math.js";
4
4
  const finite = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
5
+ /** An object as a piece. Its `model` is the def's model with the default `view` added, so that a view of it (or a test) can turn it to any aspect from there. */
5
6
  export function defineObject(def) {
6
7
  const render = (o, turning) => {
7
8
  const opts = resolveOptions(o);
@@ -16,5 +17,5 @@ export function defineObject(def) {
16
17
  const { defs, body } = def.draw({ rot, opts, turning, id, minWidth: 80 / opts.size, at });
17
18
  return wrapPiece({ title: escape(def.en), size: opts.size, theme: opts.theme, defs, body });
18
19
  };
19
- return { id: def.id, en: def.en, ja: def.ja, source: "generated", kind: "ball", model: def.model, size: def.size, svg: labelled(def.en, def.ja, (o) => render(o, false)), frame: labelled(def.en, def.ja, (o) => render(o, true)) };
20
+ return { id: def.id, en: def.en, ja: def.ja, source: "generated", kind: "ball", model: { ...def.model, view: def.view }, size: def.size, svg: labelled(def.en, def.ja, (o) => render(o, false)), frame: labelled(def.en, def.ja, (o) => render(o, true)) };
20
21
  }
@@ -1,3 +1,5 @@
1
1
  export declare const BALL_PUCK: import("./ball.core.ts").Piece<{
2
2
  slices: number;
3
+ } & {
4
+ view: import("./ball.object.ts").ObjectView;
3
5
  }>;
@@ -1,7 +1,7 @@
1
- // Ice hockey puck: vulcanised black rubber, 76.2 mm across and 25.4 mm thick (so 1 : 3), with a ridged edge. Drawn as a cylinder in true 3D, so it turns.
1
+ // Ice hockey puck: vulcanised black rubber, 76.2 mm across and 25.4 mm thick (so 1 : 3), with a ridged edge. Drawn as a cylinder in true 3D, so it turns. The stack's y is in units of the radius, so the half thickness is 12.7 / 38.1, one third.
2
2
  import { drawStack } from "./ball.stack.js";
3
3
  import { defineObject } from "./ball.object.js";
4
- const H = 25.4 / 76.2 / 2;
4
+ const H = 25.4 / 76.2;
5
5
  export const BALL_PUCK = /* @__PURE__ */ defineObject({
6
6
  id: "ball:puck",
7
7
  en: "Hockey puck",
@@ -13,8 +13,9 @@ export const BALL_RUGBY = /* @__PURE__ */ defineBall({
13
13
  ja: "ラグビーボール",
14
14
  size: { extentMm: 290.0, lengthMm: 290.0, circumferenceMm: 600.0, longCircumferenceMm: 755.0, massG: [410, 460], rule: "World Rugby Laws of the Game (Rugby Union), Law 2 The ball, clauses 1-7" },
15
15
  spec: {
16
- axes: [1, 0.62, 0.62],
17
- point: 0.72,
16
+ // Worked out from the Laws' 600 mm girth, 290 mm length and 755 mm long girth.
17
+ axes: [1, 0.6586, 0.6586],
18
+ point: 0.561,
18
19
  view: { yaw: -30, pitch: 20, roll: -30 },
19
20
  base: "#1f4e9c",
20
21
  ground: WHITE,
@@ -1,6 +1,10 @@
1
1
  export declare const BALL_SHUTTLECOCK_FEATHER: import("./ball.core.ts").Piece<{
2
2
  feathers: number;
3
+ } & {
4
+ view: import("./ball.object.ts").ObjectView;
3
5
  }>;
4
6
  export declare const BALL_SHUTTLECOCK_NYLON: import("./ball.core.ts").Piece<{
5
7
  feathers: number;
8
+ } & {
9
+ view: import("./ball.object.ts").ObjectView;
6
10
  }>;
@@ -4,12 +4,13 @@ import { defineObject } from "./ball.object.js";
4
4
  import {} from "./ball.math.js";
5
5
  import { blend, mix, num } from "./ball.core.js";
6
6
  const FEATHERS = 16;
7
- // One unit is 32 mm: the skirt is 2 units across at the top, the base 0.84 across and the whole 2.06 long.
7
+ // One unit is 32 mm (the scale below makes 92 box units 150 mm): the skirt is 2 units across at the tips (63 mm), the base 0.84 across (27 mm) and the feathers 2.05 long from the tips to the top of the base
8
+ // (66 mm; the Law measures a feather's length that way, 62 to 70 mm).
8
9
  const ROOT_R = 0.42;
9
10
  const TIP_R = 1;
10
- const ROOT_Y = -0.56;
11
+ const ROOT_Y = -1.05;
11
12
  const TIP_Y = 1.0;
12
- const SLOPE = 0.4;
13
+ const SLOPE = 0.56;
13
14
  const drawShuttle = (c, p) => {
14
15
  const line = c.opts.style === "line";
15
16
  const tip = (a, r, y, k = 0) => [Math.cos(a) * r, y, Math.sin(a) * r + k];
@@ -25,7 +25,8 @@ export function flipSvg(svg, o) {
25
25
  const sx = Math.max(Math.abs(across), 0.02);
26
26
  const sy = Math.max(Math.abs(tilt), 0.02);
27
27
  const thick = w * (o.thick ?? 0.035);
28
- const dx = thick * Math.sin(o.yaw * RAD);
28
+ // A turn to a positive yaw carries the front toward the right, as it does for a ball and a cup (and for a drag to the right), so the rim is seen on the left, on the edge that comes toward the eye.
29
+ const dx = -thick * Math.sin(o.yaw * RAD);
29
30
  const dy = -thick * Math.sin(o.pitch * RAD) * 0.6;
30
31
  const mirror = showBack && own === undefined;
31
32
  const cx = x0 + w / 2;
@@ -96,7 +96,8 @@ export function defineFlat(id, en, ja, source, model) {
96
96
  const shown = across >= 0 ? model.front : (model.back ?? model.front.filter((x) => x[0] === "m" || x[0] === "o" || x[0] === "s"));
97
97
  const sx = Math.max(Math.abs(across), 0.02);
98
98
  const sy = Math.max(Math.abs(tilt), 0.02);
99
- const rim = (model.rim ?? 0) * Math.sin((yaw * Math.PI) / 180);
99
+ // The rim is on the left for a positive yaw, which carries the front toward the right (as it does for a ball, a cup and a drag to the right).
100
+ const rim = -(model.rim ?? 0) * Math.sin((yaw * Math.PI) / 180);
100
101
  const [x0, y0, w, h] = (model.box ?? "0 0 64 64").split(" ").map(Number);
101
102
  const thick = rim === 0 ? "" : `<g transform="translate(${n2(rim)} 0)">${shapesMarkup(model.front.filter((x) => x[0] === "m" || x[0] === "o").map((x) => ["s", x[1]]), ctx)}</g>`;
102
103
  return document(en, model, r, ctx, `<g transform="translate(${n2(x0 + w / 2)} ${n2(y0 + h / 2)}) scale(${n2(sx)} ${n2(sy)}) translate(${n2(-x0 - w / 2)} ${n2(-y0 - h / 2)})">${thick}${face(model, shown, ctx, engraved(model, ctx, across >= 0 ? "front" : "back"))}</g>`);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.0";
1
+ export declare const VERSION = "0.2.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.2.0";
1
+ export const VERSION = "0.2.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/dougu-sports",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Dougu Sports 道具, sports emblems as SVG strings: trophies, medals, crests, sport icons, and the ball and gear of every sport, in metal, line or flat colour, with optional 3D turning. Light and dark, accessible, tree-shakeable. Zero dependencies.",
5
5
  "keywords": [
6
6
  "sports",