@diffenderfer-games/hub 2.0.0-next.7 → 2.0.0-next.9

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 (82) hide show
  1. package/CHANGELOG.md +43 -4
  2. package/dist/auto.d.ts +3 -3
  3. package/dist/auto.js +2 -2
  4. package/dist/avatar-pixi.d.ts +19 -1
  5. package/dist/avatar-pixi.d.ts.map +1 -1
  6. package/dist/avatar-pixi.js +37 -18
  7. package/dist/avatar-pixi.js.map +1 -1
  8. package/dist/avatar-svg.d.ts +1 -1
  9. package/dist/avatar-svg.js +1 -1
  10. package/dist/avatar-three.d.ts +1 -1
  11. package/dist/avatar.d.ts +3 -3
  12. package/dist/avatar.js +20 -7
  13. package/dist/avatar.js.map +1 -1
  14. package/dist/doctor/cli.mjs +58 -4
  15. package/dist/doctor/cli.mjs.map +1 -1
  16. package/dist/index.d.ts +3 -3
  17. package/dist/index.js +2 -2
  18. package/dist/shared/animator.d.ts +1 -1
  19. package/dist/shared/art.js +1 -1
  20. package/dist/shared/atlas.d.ts +5 -1
  21. package/dist/shared/atlas.d.ts.map +1 -1
  22. package/dist/shared/avatar.d.ts +10 -4
  23. package/dist/shared/avatar.d.ts.map +1 -1
  24. package/dist/shared/bridge.js +2 -2
  25. package/dist/shared/bridge.js.map +1 -1
  26. package/dist/shared/client-version.gen.js +1 -1
  27. package/dist/shared/daily.js +1 -1
  28. package/dist/shared/describe.d.ts +1 -1
  29. package/dist/shared/describe.d.ts.map +1 -1
  30. package/dist/shared/describe.js +1 -1
  31. package/dist/shared/index.d.ts +88 -11
  32. package/dist/shared/index.d.ts.map +1 -1
  33. package/dist/shared/live-canvas.js +1 -1
  34. package/dist/shared/overlays.js +1 -1
  35. package/dist/shared/overlays.js.map +1 -1
  36. package/dist/shared/pose-key.js +2 -2
  37. package/dist/shared/pose-key.js.map +1 -1
  38. package/dist/shared/social.d.ts +19 -4
  39. package/dist/shared/social.d.ts.map +1 -1
  40. package/dist/shared/src.js +178 -28
  41. package/dist/shared/src.js.map +1 -1
  42. package/dist/shared/svg-markup.js +430 -21
  43. package/dist/shared/svg-markup.js.map +1 -1
  44. package/dist/shared/svg.js +2 -2
  45. package/dist/shared/svg.js.map +1 -1
  46. package/dist/shared/types.d.ts +9 -1
  47. package/dist/shared/types.d.ts.map +1 -1
  48. package/dist/social.d.ts +2 -2
  49. package/dist/turns-rules.d.ts +2 -0
  50. package/dist/turns-rules.js +1 -0
  51. package/dist/turns.d.ts +23 -1
  52. package/dist/turns.d.ts.map +1 -1
  53. package/dist/turns.js +79 -13
  54. package/dist/turns.js.map +1 -1
  55. package/package.json +8 -2
  56. package/src/avatar/adapters/pixi-crowd.ts +45 -18
  57. package/src/avatar/atlas.ts +16 -6
  58. package/src/avatar/layout.ts +1 -1
  59. package/src/avatar/outputs/canvas.ts +2 -2
  60. package/src/avatar/outputs/svg.ts +2 -2
  61. package/src/avatar/render/body.ts +2 -2
  62. package/src/avatar/render/compose.ts +25 -8
  63. package/src/avatar/render/frame.ts +15 -2
  64. package/src/avatar/render/types.ts +9 -0
  65. package/src/avatar/render/walker-parts.ts +121 -0
  66. package/src/avatar/render/walker.ts +199 -0
  67. package/src/avatar.ts +2 -1
  68. package/src/core/rest/routes.gen.ts +1 -0
  69. package/src/core/turns/hub-turns.ts +25 -4
  70. package/src/core/turns/turn-game.ts +143 -21
  71. package/src/core/turns/your-turn-buzz.ts +69 -0
  72. package/src/core/version/client-version.gen.ts +1 -1
  73. package/src/doctor/checks.ts +2 -2
  74. package/src/doctor/cli.ts +5 -2
  75. package/src/doctor/world-check.ts +61 -0
  76. package/src/index.ts +2 -1
  77. package/src/realtime/runtime-api.ts +5 -2
  78. package/src/realtime/types.ts +1 -0
  79. package/src/realtime/world-types.ts +20 -0
  80. package/src/turns-kit/check-rules.ts +116 -17
  81. package/src/turns-rules.ts +12 -0
  82. package/src/turns.ts +4 -3
@@ -232,6 +232,21 @@ const FRAMES = {
232
232
  206
233
233
  ]
234
234
  };
235
+ /** The walker's full frame: a smaller head, and room for feet hanging below a seat. */
236
+ const WALKER_FULL = [
237
+ -60,
238
+ -176,
239
+ 120,
240
+ 214
241
+ ];
242
+ /** True when options draw the walker figure (full frames only). */
243
+ function isWalker(opts) {
244
+ return opts.figure === "walker" && frameOf(opts) === "full";
245
+ }
246
+ /** The box options draw, in rig units. */
247
+ function frameBox(opts) {
248
+ return isWalker(opts) ? WALKER_FULL : FRAMES[frameOf(opts)];
249
+ }
235
250
  /** The site panel colour behind badges. */
236
251
  const PANEL = "#11141c";
237
252
  /** The frame an options object asks for (default `head`). */
@@ -240,7 +255,7 @@ function frameOf(opts) {
240
255
  }
241
256
  /** Output pixel size for a frame (the longer edge is `size`). */
242
257
  function pixelSize(opts) {
243
- const [, , w, h] = FRAMES[frameOf(opts)];
258
+ const [, , w, h] = frameBox(opts);
244
259
  const scale = (opts.size ?? 48) / Math.max(w, h);
245
260
  return {
246
261
  width: w * scale,
@@ -254,7 +269,7 @@ function ringColour(look) {
254
269
  }
255
270
  /** Wraps the avatar's nodes in the frame's background (a badge clips to the rounded panel). */
256
271
  function framed(nodes, look, opts) {
257
- const [left, top, w, h] = FRAMES[frameOf(opts)];
272
+ const [left, top, w, h] = frameBox(opts);
258
273
  const background = opts.background ?? (frameOf(opts) === "head" ? "badge" : "none");
259
274
  if (background === "badge") {
260
275
  const r = w * .24;
@@ -991,7 +1006,7 @@ const STOOL = [
991
1006
  sw: 3
992
1007
  }, 5.5)
993
1008
  ];
994
- const hand = (c, cx, cy) => ellipse(cx, cy, 8, 8, {
1009
+ const hand$1 = (c, cx, cy) => ellipse(cx, cy, 8, 8, {
995
1010
  fill: c.body,
996
1011
  stroke: c.bodyInk,
997
1012
  sw: LINE
@@ -1034,13 +1049,13 @@ function bodyLayout(c, pose, phi, phase, hasStool) {
1034
1049
  tf = [rot(-6)];
1035
1050
  headDx = -5;
1036
1051
  tilt = -7;
1037
- front.push(hand(c, 44, -58), path("M56 -70Q61 -64 58 -56M62 -76Q69 -66 64 -54", line("#e8e6df", 2.2, "round", .8)));
1052
+ front.push(hand$1(c, 44, -58), path("M56 -70Q61 -64 58 -56M62 -76Q69 -66 64 -54", line("#e8e6df", 2.2, "round", .8)));
1038
1053
  break;
1039
1054
  case "cheer":
1040
1055
  lift = hop * 10;
1041
1056
  tf = [sc(1 - hop * .04, 1 + hop * .05)];
1042
1057
  headDy = -4 * hop;
1043
- front.push(hand(c, -42, -66 - hop * 4), hand(c, 42, -66 - hop * 4));
1058
+ front.push(hand$1(c, -42, -66 - hop * 4), hand$1(c, 42, -66 - hop * 4));
1044
1059
  break;
1045
1060
  case "jump":
1046
1061
  lift = hop * 18;
@@ -1085,9 +1100,9 @@ function bodyLayout(c, pose, phi, phase, hasStool) {
1085
1100
  };
1086
1101
  }
1087
1102
  /** The soft ground shadow at the feet (shrinks while lifted). */
1088
- function groundShadow(lift) {
1103
+ function groundShadow(lift, width = 36) {
1089
1104
  const k = Math.max(.5, 1 - lift / 40);
1090
- return ellipse(0, 0, 36 * k, 8 * k, {
1105
+ return ellipse(0, 0, width * k, width / 4.5 * k, {
1091
1106
  fill: "#000000",
1092
1107
  opacity: .3
1093
1108
  });
@@ -1573,6 +1588,366 @@ function handProp(prop, atX, atY, body, bodyInk) {
1573
1588
  }
1574
1589
  }
1575
1590
  //#endregion
1591
+ //#region src/avatar/render/walker-parts.ts
1592
+ /**
1593
+ * The walker's drawing parts (`figure: 'walker'`): limbs as outlined round
1594
+ * strokes, the shirt, trousers and shoes. Colours come from the look: the
1595
+ * shirt is the body colour, the trousers and shoes are picked from the body
1596
+ * colour so they always stand apart from the shirt.
1597
+ */
1598
+ const TROUSERS = [
1599
+ "#34466e",
1600
+ "#2e323f",
1601
+ "#6a5642",
1602
+ "#4a3a60",
1603
+ "#3c5a4c",
1604
+ "#5a3a3a"
1605
+ ];
1606
+ const SHOES = [
1607
+ "#2c2430",
1608
+ "#f1ece2",
1609
+ "#5a3a2a",
1610
+ "#2c2430"
1611
+ ];
1612
+ /** A small stable hash of a colour string. */
1613
+ function hashOf(text) {
1614
+ let hash = 7;
1615
+ for (let i = 0; i < text.length; i++) hash = hash * 31 + text.charCodeAt(i) >>> 0;
1616
+ return hash;
1617
+ }
1618
+ /** Trousers and shoes for a shirt colour: a trouser tone at least a little lighter or darker than the shirt. */
1619
+ function outfitFor(c) {
1620
+ const hash = hashOf(c.body);
1621
+ const shirt = lightness(c.body);
1622
+ let trousers = TROUSERS[hash % TROUSERS.length] ?? TROUSERS[0];
1623
+ for (let i = 1; i < TROUSERS.length && Math.abs(lightness(trousers) - shirt) < .12; i++) trousers = TROUSERS[(hash + i) % TROUSERS.length] ?? trousers;
1624
+ const shoes = SHOES[(hash >>> 4) % SHOES.length] ?? SHOES[0];
1625
+ return {
1626
+ trousers,
1627
+ trousersInk: outline(trousers),
1628
+ shoes,
1629
+ shoesInk: outline(shoes)
1630
+ };
1631
+ }
1632
+ /** A polyline as a path `d`. */
1633
+ function polyline(points) {
1634
+ const line = new Path();
1635
+ points.forEach(([px, py], i) => i === 0 ? line.m(px, py) : line.l(px, py));
1636
+ return line.d();
1637
+ }
1638
+ /** A limb: a round stroke with a tonal outline under it. */
1639
+ function limb(points, width, fill, ink) {
1640
+ const d = polyline(points);
1641
+ return [path(d, {
1642
+ fill: "none",
1643
+ stroke: ink,
1644
+ sw: width + LINE * 1.3,
1645
+ cap: "round"
1646
+ }), path(d, {
1647
+ fill: "none",
1648
+ stroke: fill,
1649
+ sw: width,
1650
+ cap: "round"
1651
+ })];
1652
+ }
1653
+ /** A shoe at a foot point, nudged along the facing. */
1654
+ function shoe(at, forward, o) {
1655
+ return ellipse(at[0] + forward[0] * 2.4, at[1] + forward[1] * 1.6 - 1.5, 6.2 + Math.abs(forward[0]) * 1.4, 3.8, {
1656
+ fill: o.shoes,
1657
+ stroke: o.shoesInk,
1658
+ sw: LINE * .8
1659
+ });
1660
+ }
1661
+ /** A hand. */
1662
+ function hand(c, at) {
1663
+ return ellipse(at[0], at[1], 4.3, 4.3, {
1664
+ fill: c.skin,
1665
+ stroke: c.skinInk,
1666
+ sw: LINE * .8
1667
+ });
1668
+ }
1669
+ /** The torso's outline: shoulders at `top`, the hem at `hem`, `w` its width factor (narrower when turned). */
1670
+ function torsoPath(w, top, hem) {
1671
+ const shoulder = 17.5 * w;
1672
+ const waist = 14.5 * w;
1673
+ const hip = 15.5 * w;
1674
+ const mid = (top + hem) / 2 + 4;
1675
+ return new Path().m(-shoulder, top + 6).c(-shoulder, top - 1, -shoulder + 5, top - 3, -shoulder * .35, top - 3).l(shoulder * .35, top - 3).c(shoulder - 5, top - 3, shoulder, top - 1, shoulder, top + 6).l(waist, mid).l(hip, hem).q(0, hem + 3, -hip, hem).l(-waist, mid).z().d();
1676
+ }
1677
+ /** The shirt: a shade base, the lit front, the outline, and the neckline (front) or collar (back). */
1678
+ function shirt(c, w, top, hem, lean, isBack) {
1679
+ const d = torsoPath(w, top, hem);
1680
+ const cy = (top + hem) / 2;
1681
+ const nodes = [
1682
+ path(d, { fill: c.bodyShade }),
1683
+ g([path(d, { fill: c.body })], { tf: [
1684
+ tr(-1.5 - lean * 2, cy - 1.5),
1685
+ sc(.9, .94),
1686
+ tr(0, -cy)
1687
+ ] }),
1688
+ path(`M${String(-15 * w)} ${String(hem - 2.5)}Q0 ${String(hem + .5)} ${String(15 * w)} ${String(hem - 2.5)}`, {
1689
+ fill: "none",
1690
+ stroke: shade(c.bodyShade),
1691
+ sw: 2.2,
1692
+ opacity: .7
1693
+ }),
1694
+ path(d, {
1695
+ fill: "none",
1696
+ stroke: c.bodyInk,
1697
+ sw: LINE
1698
+ })
1699
+ ];
1700
+ const neckX = lean * 7;
1701
+ if (isBack) nodes.push(path(`M${String(neckX - 6)} ${String(top - 2)}Q${String(neckX)} ${String(top + 1.5)} ${String(neckX + 6)} ${String(top - 2)}`, {
1702
+ fill: "none",
1703
+ stroke: c.bodyShade,
1704
+ sw: 3
1705
+ }));
1706
+ else nodes.push(path(`M${String(neckX - 6 * w)} ${String(top - 2.6)}Q${String(neckX)} ${String(top + 6)} ${String(neckX + 6 * w)} ${String(top - 2.6)}Z`, {
1707
+ fill: c.skin,
1708
+ stroke: c.bodyInk,
1709
+ sw: LINE * .8
1710
+ }));
1711
+ return nodes;
1712
+ }
1713
+ /** The seat of the trousers joining the legs under the hem. */
1714
+ function hips(o, w, hipY) {
1715
+ const half = 12 * w;
1716
+ return path(new Path().m(-half, hipY - 4).l(half, hipY - 4).l(half, hipY + 4).q(0, hipY + 9, -half, hipY + 4).z().d(), {
1717
+ fill: o.trousers,
1718
+ stroke: o.trousersInk,
1719
+ sw: LINE * .8
1720
+ });
1721
+ }
1722
+ /** The neck under the head. */
1723
+ function neck(c, top, dx) {
1724
+ return path(new Path().m(dx - 4.5, top - 10).l(dx + 4.5, top - 10).l(dx + 4.5, top).l(dx - 4.5, top).z().d(), {
1725
+ fill: c.skinShade,
1726
+ stroke: c.skinInk,
1727
+ sw: LINE * .6
1728
+ });
1729
+ }
1730
+ //#endregion
1731
+ //#region src/avatar/render/walker.ts
1732
+ /**
1733
+ * The world walker (`figure: 'walker'`): a small full-body character for
1734
+ * maps and crowds, with legs, arms, a shirt, trousers and shoes, and the
1735
+ * head drawn smaller on top ({@link WALKER_HEAD_SCALE}). It takes the blob's
1736
+ * pose names: `walk` and `run` swing the legs and arms along the facing,
1737
+ * `sit` folds the legs over a seat, `idle` breathes, `wave` and `cheer` lift
1738
+ * the arms.
1739
+ *
1740
+ * Standing, the origin is the feet. Sitting (`sit`, `play`), the origin is
1741
+ * the seat: the hips rest on it and the feet hang {@link WALKER_SEAT_DROP}
1742
+ * units below, on the floor in front of it.
1743
+ */
1744
+ /** The head's scale on a walker (the blob draws it at 1). */
1745
+ const WALKER_HEAD_SCALE = .58;
1746
+ /** How far below the seat a sitter's feet reach (rig units). */
1747
+ const WALKER_SEAT_DROP = 26;
1748
+ const STANDING = {
1749
+ hip: -35,
1750
+ shoulder: -72,
1751
+ head: -103
1752
+ };
1753
+ const SEATED = {
1754
+ hip: -3,
1755
+ shoulder: -40,
1756
+ head: -71
1757
+ };
1758
+ const LEG = 32;
1759
+ const UPPER_ARM = 13;
1760
+ const FOREARM = 12;
1761
+ const LEG_W = 10.5;
1762
+ const SLEEVE_W = 10;
1763
+ const ARM_W = 7.5;
1764
+ const DEG$1 = Math.PI / 180;
1765
+ function viewOf(angleDeg) {
1766
+ const side = Math.sin(angleDeg * DEG$1);
1767
+ const front = Math.cos(angleDeg * DEG$1);
1768
+ return {
1769
+ side,
1770
+ front,
1771
+ forward: [side, front * .45],
1772
+ isBack: angleDeg > 90,
1773
+ width: 1 - .36 * Math.abs(side)
1774
+ };
1775
+ }
1776
+ /** A body side's screen x for a lateral distance (sides overlap when turned). */
1777
+ const lateralX = (v, sideOf, distance) => sideOf * distance * v.front;
1778
+ /** How near the viewer a side is (> 0: drawn in front when turned). */
1779
+ const nearness = (v, sideOf) => -sideOf * v.side;
1780
+ const add = (a, b, k = 1) => [a[0] + b[0] * k, a[1] + b[1] * k];
1781
+ function standingLimbs(v, h, pose, phase, bob) {
1782
+ const isRun = pose === "run";
1783
+ const isWalking = pose === "walk" || isRun || pose === "dance";
1784
+ const stride = isRun ? 38 : 27;
1785
+ const swingOf = (sideOf) => isWalking ? Math.sin((phase + (sideOf === 1 ? 0 : .5)) * Math.PI * 2) : 0;
1786
+ return {
1787
+ leg: (sideOf) => {
1788
+ const hip = [lateralX(v, sideOf, 6), h.hip + bob];
1789
+ const swing = swingOf(sideOf);
1790
+ const lift = isWalking ? Math.max(0, Math.cos((phase + (sideOf === 1 ? 0 : .5)) * Math.PI * 2)) * (isRun ? 7 : 4.5) : 0;
1791
+ const reach = Math.sin(stride * DEG$1) * LEG * swing;
1792
+ const foot = [hip[0] + v.forward[0] * reach, -bob * .2 + v.forward[1] * reach - lift];
1793
+ return [
1794
+ hip,
1795
+ [(hip[0] + foot[0]) / 2 + v.forward[0] * lift * .9, (hip[1] + foot[1]) / 2 + v.forward[1] * lift * .9],
1796
+ foot
1797
+ ];
1798
+ },
1799
+ arm: (sideOf) => {
1800
+ const shoulder = [lateralX(v, sideOf, 15.5 * v.width) + v.forward[0] * 1.5, h.shoulder + 4 + bob];
1801
+ const out = sideOf * v.front * 2.2;
1802
+ if (pose === "cheer" || pose === "wave" && sideOf === -1) {
1803
+ const wave = pose === "wave" ? Math.sin(phase * Math.PI * 2) * 4 : 0;
1804
+ const elbow = [shoulder[0] + out * 3, shoulder[1] - 10];
1805
+ return [
1806
+ shoulder,
1807
+ elbow,
1808
+ [elbow[0] + out * 1.5 + wave, elbow[1] - 12]
1809
+ ];
1810
+ }
1811
+ const swing = isWalking ? -swingOf(sideOf) * (isRun ? 1.3 : 1) : Math.sin(phase * Math.PI * 2) * .06;
1812
+ const reach = Math.sin(24 * DEG$1) * swing;
1813
+ const elbow = [shoulder[0] + out + v.forward[0] * UPPER_ARM * reach * .6, shoulder[1] + UPPER_ARM + v.forward[1] * UPPER_ARM * reach];
1814
+ return [
1815
+ shoulder,
1816
+ elbow,
1817
+ [elbow[0] + out * .4 + v.forward[0] * FOREARM * reach * 1.6, elbow[1] + FOREARM - Math.abs(reach) * 3 + v.forward[1] * FOREARM * reach]
1818
+ ];
1819
+ }
1820
+ };
1821
+ }
1822
+ function seatedLimbs(v, h, pose) {
1823
+ const thigh = [v.forward[0] * 15, v.forward[1] * 16];
1824
+ return {
1825
+ leg: (sideOf) => {
1826
+ const hip = [lateralX(v, sideOf, 6), h.hip];
1827
+ const knee = add(hip, thigh);
1828
+ return [
1829
+ hip,
1830
+ knee,
1831
+ [knee[0] + v.forward[0] * 3, 24]
1832
+ ];
1833
+ },
1834
+ arm: (sideOf) => {
1835
+ const shoulder = [lateralX(v, sideOf, 15.5 * v.width) + v.forward[0] * 1.5, h.shoulder + 4];
1836
+ return [
1837
+ shoulder,
1838
+ [shoulder[0] + sideOf * v.front * 2 + v.forward[0] * 3, shoulder[1] + UPPER_ARM],
1839
+ add([lateralX(v, sideOf, pose === "play" ? 4 : 7), h.hip - 4], thigh, pose === "play" ? .9 : .6)
1840
+ ];
1841
+ }
1842
+ };
1843
+ }
1844
+ /** The walker's drawing for a pose, at a facing angle (0 = S, 180 = N; west facings are mirrored by the caller). */
1845
+ function walkerLayout(c, pose, angleDeg, phase, outfit = outfitFor(c)) {
1846
+ const v = viewOf(angleDeg);
1847
+ const isSitting = pose === "sit" || pose === "play";
1848
+ const h = isSitting ? SEATED : STANDING;
1849
+ const cycle = Math.sin(phase * Math.PI * 2);
1850
+ const isWalking = pose === "walk" || pose === "run" || pose === "dance";
1851
+ const hop = pose === "jump" || pose === "cheer" ? Math.abs(Math.sin(phase * Math.PI)) * (pose === "jump" ? 16 : 8) : 0;
1852
+ const bob = isWalking ? -2.2 * (1 - Math.abs(cycle)) : pose === "idle" ? cycle * .6 : 0;
1853
+ const limbs = isSitting ? seatedLimbs(v, h, pose) : standingLimbs(v, h, pose, phase, bob);
1854
+ const o = outfit;
1855
+ const [farSide, nearSide] = nearness(v, 1) > nearness(v, -1) ? [-1, 1] : [1, -1];
1856
+ const isTurned = Math.abs(v.side) > .3;
1857
+ const lean = v.side * (isSitting ? .6 : 1);
1858
+ const armNodes = (sideOf) => {
1859
+ const [shoulder, elbow, wrist] = limbs.arm(sideOf);
1860
+ return [
1861
+ ...limb([
1862
+ shoulder,
1863
+ elbow,
1864
+ wrist
1865
+ ], ARM_W, c.skin, c.skinInk),
1866
+ ...limb([shoulder, add(shoulder, [elbow[0] - shoulder[0], elbow[1] - shoulder[1]], .62)], SLEEVE_W, c.body, c.bodyInk),
1867
+ hand(c, wrist)
1868
+ ];
1869
+ };
1870
+ const legNodes = (sideOf) => {
1871
+ const points = limbs.leg(sideOf);
1872
+ const foot = points[2];
1873
+ return [...limb(points, LEG_W, o.trousers, o.trousersInk), shoe(foot, v.forward, o)];
1874
+ };
1875
+ const torso = shirt(c, v.width, h.shoulder + (isWalking ? bob : 0), h.hip + 3 + (isWalking ? bob : 0), lean, v.isBack);
1876
+ const isTucked = isSitting && v.isBack;
1877
+ const behind = isTucked ? [...armNodes(farSide), ...armNodes(nearSide)] : isTurned ? armNodes(farSide) : [];
1878
+ const isLegsFirst = v.isBack || !isSitting;
1879
+ const legs = isTucked ? [] : [...legNodes(farSide), ...legNodes(nearSide)];
1880
+ const body = [
1881
+ ...isLegsFirst ? legs : [],
1882
+ hips(o, v.width, h.hip + (isWalking ? bob : 0)),
1883
+ neck(c, h.shoulder + bob, v.forward[0] * 2),
1884
+ ...torso,
1885
+ ...isLegsFirst ? [] : legs,
1886
+ ...isTucked ? [] : isTurned ? armNodes(nearSide) : [...armNodes(-1), ...armNodes(1)]
1887
+ ];
1888
+ const prop = add([0, h.hip - 12], v.forward, 8);
1889
+ return {
1890
+ behind,
1891
+ body,
1892
+ front: [],
1893
+ headDx: v.forward[0] * 1.5,
1894
+ headDy: 0,
1895
+ headY: h.head + bob * 1.1,
1896
+ headScale: WALKER_HEAD_SCALE,
1897
+ tilt: isWalking ? v.side * 2 : 0,
1898
+ lift: hop,
1899
+ prop: {
1900
+ x: prop[0],
1901
+ y: prop[1]
1902
+ }
1903
+ };
1904
+ }
1905
+ const GREY = "#6f7787";
1906
+ const GREY_INK = "#5c6474";
1907
+ /** Every colour slot in the silhouette grey. */
1908
+ const GREY_COLOURS = {
1909
+ skin: GREY,
1910
+ skinShade: GREY,
1911
+ skinLight: GREY,
1912
+ skinInk: GREY_INK,
1913
+ eyeRim: GREY,
1914
+ hair: GREY,
1915
+ hairShade: GREY,
1916
+ hairLight: GREY,
1917
+ hairInk: GREY_INK,
1918
+ hairBuzz: GREY,
1919
+ brow: GREY,
1920
+ beard: GREY,
1921
+ beardShade: GREY,
1922
+ beardInk: GREY_INK,
1923
+ stubble: GREY,
1924
+ eye: GREY,
1925
+ eyeDark: GREY,
1926
+ body: GREY,
1927
+ bodyShade: GREY,
1928
+ bodyLight: GREY,
1929
+ bodyInk: GREY_INK
1930
+ };
1931
+ /** The grey walker shown before the art loads (or for a hidden player): its nodes, head centre and top. */
1932
+ function walkerSilhouette() {
1933
+ const b = walkerLayout(GREY_COLOURS, "idle", 0, 0, {
1934
+ trousers: GREY,
1935
+ trousersInk: GREY_INK,
1936
+ shoes: GREY,
1937
+ shoesInk: GREY_INK
1938
+ });
1939
+ const r = 46 * WALKER_HEAD_SCALE;
1940
+ return {
1941
+ nodes: [
1942
+ ...b.behind,
1943
+ ...b.body,
1944
+ ellipse(0, b.headY, r, r * 1.05, { fill: GREY })
1945
+ ],
1946
+ headY: b.headY,
1947
+ top: b.headY - r - 4
1948
+ };
1949
+ }
1950
+ //#endregion
1576
1951
  //#region src/avatar/render/compose.ts
1577
1952
  /**
1578
1953
  * Composes a look and a pose into a scene in rig space: the feet at (0, 0),
@@ -1781,6 +2156,23 @@ function anchorsFor(headX, headY, top, prop, mirror) {
1781
2156
  pod: at(0, 14)
1782
2157
  };
1783
2158
  }
2159
+ /** The body for a figure: its layout, where the head centre goes, and the head's scale. */
2160
+ function figureLayout(c, opts, pose, phi, angle, phase) {
2161
+ if (isWalker(opts)) {
2162
+ const walker = walkerLayout(c, pose, angle, phase);
2163
+ return {
2164
+ b: walker,
2165
+ headY: walker.headY,
2166
+ headScale: walker.headScale
2167
+ };
2168
+ }
2169
+ const b = bodyLayout(c, pose, phi, phase, opts.stool === true);
2170
+ return {
2171
+ b,
2172
+ headY: -96 + b.headDy,
2173
+ headScale: 1
2174
+ };
2175
+ }
1784
2176
  /** Composes a look (or `null` for the silhouette) with a pose. */
1785
2177
  function composeAvatar(look, opts = {}) {
1786
2178
  const art = currentArt();
@@ -1788,13 +2180,25 @@ function composeAvatar(look, opts = {}) {
1788
2180
  const isMirrored = MIRROR[facingIn] !== void 0;
1789
2181
  const facing = MIRROR[facingIn] ?? (facingIn === "S" || facingIn === "SE" || facingIn === "E" || facingIn === "NE" ? facingIn : "N");
1790
2182
  const hasShadow = opts.shadow ?? opts.frame === "full";
1791
- if (!look || !art) return {
1792
- nodes: silhouette(hasShadow),
1793
- anchors: anchorsFor(0, -96, -156, {
1794
- x: 0,
1795
- y: -30
1796
- }, 1)
1797
- };
2183
+ if (!look || !art) {
2184
+ if (isWalker(opts)) {
2185
+ const shape = walkerSilhouette();
2186
+ return {
2187
+ nodes: [...hasShadow ? [groundShadow(0, 18)] : [], ...shape.nodes],
2188
+ anchors: anchorsFor(0, shape.headY, shape.top, {
2189
+ x: 0,
2190
+ y: -40
2191
+ }, 1)
2192
+ };
2193
+ }
2194
+ return {
2195
+ nodes: silhouette(hasShadow),
2196
+ anchors: anchorsFor(0, -96, -156, {
2197
+ x: 0,
2198
+ y: -30
2199
+ }, 1)
2200
+ };
2201
+ }
1798
2202
  const pose = opts.pose ?? {};
1799
2203
  const bodyPose = pose.body ?? "idle";
1800
2204
  const status = pose.status ?? (bodyPose === "play" ? "goggles" : null);
@@ -1816,20 +2220,25 @@ function composeAvatar(look, opts = {}) {
1816
2220
  blink: pose.blink ?? 0,
1817
2221
  talk: pose.talk ?? 0
1818
2222
  };
1819
- const b = bodyLayout(parts.c, bodyPose, ctx.phi, pose.phase ?? .25, opts.stool === true);
2223
+ const { b, headY: bodyHeadY, headScale } = figureLayout(parts.c, opts, bodyPose, ctx.phi, angle, pose.phase ?? .25);
1820
2224
  const headX = b.headDx + (pose.bob?.x ?? 0);
1821
- const headY = -96 + b.headDy + (pose.bob?.y ?? 0);
2225
+ const headY = bodyHeadY + (pose.bob?.y ?? 0);
1822
2226
  const tilt = face.headTilt + b.tilt + (pose.tilt ?? 0);
1823
2227
  const sq = face.squash + (pose.squash ?? 0);
1824
2228
  const drawn = isBack ? backHead(ctx, parts, isPlaying, status, angle) : frontHead(ctx, parts, isPlaying, status, angle, gaze.x);
1825
2229
  const headNodes = drawn.head;
1826
- const top = headY - 50 - Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0);
2230
+ const top = headY - (50 + Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)) * headScale;
1827
2231
  if (status === "crown") headNodes.push(...crown(Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)));
1828
- const handProps = status && !isBack ? handProp(status, b.prop.x, b.prop.y, parts.c.body, parts.c.bodyInk) : [];
2232
+ const props = status && !isBack ? handProp(status, b.prop.x, b.prop.y, parts.c.body, parts.c.bodyInk) : [];
2233
+ const handProps = headScale === 1 || props.length === 0 ? props : [g(props, { tf: [
2234
+ tr(b.prop.x, b.prop.y),
2235
+ sc(headScale * 1.1),
2236
+ tr(-b.prop.x, -b.prop.y)
2237
+ ] })];
1829
2238
  const headTf = [
1830
2239
  tr(headX, headY),
1831
2240
  rot(tilt),
1832
- sc(1 + sq, 1 - sq)
2241
+ sc((1 + sq) * headScale, (1 - sq) * headScale)
1833
2242
  ];
1834
2243
  const character = [
1835
2244
  ...b.behind,
@@ -1841,7 +2250,7 @@ function composeAvatar(look, opts = {}) {
1841
2250
  ];
1842
2251
  const lifted = b.lift > 0 ? [g(character, { tf: [tr(0, -b.lift)] })] : character;
1843
2252
  const rimmed = opts.rim ? [g(lifted, { rim: true })] : lifted;
1844
- const all = [...hasShadow ? [groundShadow(b.lift)] : [], ...rimmed];
2253
+ const all = [...hasShadow ? [groundShadow(b.lift, headScale === 1 ? 36 : 18)] : [], ...rimmed];
1845
2254
  const mirror = isMirrored ? -1 : 1;
1846
2255
  return {
1847
2256
  nodes: isMirrored ? [g(all, { tf: [sc(-1, 1)] })] : all,
@@ -1940,6 +2349,6 @@ function sceneToSvg(nodes, prefix) {
1940
2349
  };
1941
2350
  }
1942
2351
  //#endregion
1943
- export { tr as A, pixelSize as C, path as D, line as E, toLook as F, deltaE as M, lightness as N, rect as O, toCode as P, framed as S, g as T, mix as _, composeAvatar as a, FRAMES as b, blob as c, scallop as d, EXPRESSIONS as f, isExpression as g, faceOf as h, HEAD_Y as i, contrast as j, rot as k, n as l, expressionFromEmote as m, escapeXml as n, Path as o, EXPRESSION_NAMES as p, sceneToSvg as r, arc as s, RIM_COLOUR as t, roundedRect as u, onArtReady as v, ellipse as w, frameOf as x, ready as y };
2352
+ export { rect as A, frameBox as C, g as D, ellipse as E, lightness as F, toCode as I, toLook as L, tr as M, contrast as N, line as O, deltaE as P, WALKER_FULL as S, pixelSize as T, faceOf as _, composeAvatar as a, onArtReady as b, Path as c, n as d, roundedRect as f, expressionFromEmote as g, EXPRESSION_NAMES as h, HEAD_Y as i, rot as j, path as k, arc as l, EXPRESSIONS as m, escapeXml as n, WALKER_HEAD_SCALE as o, scallop as p, sceneToSvg as r, WALKER_SEAT_DROP as s, RIM_COLOUR as t, blob as u, isExpression as v, framed as w, ready as x, mix as y };
1944
2353
 
1945
2354
  //# sourceMappingURL=svg-markup.js.map