@diffenderfer-games/hub 2.0.0-next.1 → 2.0.0-next.11

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 (246) hide show
  1. package/CHANGELOG.md +173 -5
  2. package/README.md +27 -15
  3. package/dist/auto.d.ts +8 -8
  4. package/dist/auto.js +3 -3
  5. package/dist/avatar-pixi.d.ts +165 -0
  6. package/dist/avatar-pixi.d.ts.map +1 -0
  7. package/dist/avatar-pixi.js +299 -0
  8. package/dist/avatar-pixi.js.map +1 -0
  9. package/dist/avatar-svg.d.ts +4 -0
  10. package/dist/avatar-svg.js +5 -0
  11. package/dist/avatar-three.d.ts +69 -0
  12. package/dist/avatar-three.d.ts.map +1 -0
  13. package/dist/avatar-three.js +49 -0
  14. package/dist/avatar-three.js.map +1 -0
  15. package/dist/avatar.d.ts +7 -0
  16. package/dist/avatar.js +500 -0
  17. package/dist/avatar.js.map +1 -0
  18. package/dist/daily.d.ts +2 -2
  19. package/dist/doctor/cli.mjs +134 -10
  20. package/dist/doctor/cli.mjs.map +1 -1
  21. package/dist/index.d.ts +8 -8
  22. package/dist/index.js +3 -3
  23. package/dist/input.d.ts +2 -2
  24. package/dist/input.js +2 -2
  25. package/dist/mp.d.ts +3 -3
  26. package/dist/race.d.ts +1 -1
  27. package/dist/rooms.d.ts +3 -3
  28. package/dist/rooms.js +1 -1
  29. package/dist/shared/animator.d.ts +104 -0
  30. package/dist/shared/animator.d.ts.map +1 -0
  31. package/dist/shared/art.js +1946 -0
  32. package/dist/shared/art.js.map +1 -0
  33. package/dist/shared/atlas.d.ts +138 -0
  34. package/dist/shared/atlas.d.ts.map +1 -0
  35. package/dist/shared/avatar.d.ts +123 -0
  36. package/dist/shared/avatar.d.ts.map +1 -0
  37. package/dist/shared/bridge.js +18 -4
  38. package/dist/shared/bridge.js.map +1 -1
  39. package/dist/shared/client-version.gen.js +1 -1
  40. package/dist/shared/daily.d.ts +145 -19
  41. package/dist/shared/daily.d.ts.map +1 -1
  42. package/dist/shared/daily.js +138 -25
  43. package/dist/shared/daily.js.map +1 -1
  44. package/dist/shared/defineProperty.js +39 -0
  45. package/dist/shared/describe.d.ts +32 -0
  46. package/dist/shared/describe.d.ts.map +1 -0
  47. package/dist/shared/describe.js +109 -0
  48. package/dist/shared/describe.js.map +1 -0
  49. package/dist/shared/globals.js +1 -39
  50. package/dist/shared/globals.js.map +1 -1
  51. package/dist/shared/index.d.ts +1122 -46
  52. package/dist/shared/index.d.ts.map +1 -1
  53. package/dist/shared/input.d.ts +74 -14
  54. package/dist/shared/input.d.ts.map +1 -1
  55. package/dist/shared/input.js +179 -25
  56. package/dist/shared/input.js.map +1 -1
  57. package/dist/shared/json-guards.js +48 -0
  58. package/dist/shared/json-guards.js.map +1 -0
  59. package/dist/shared/latest-state.js +1 -1
  60. package/dist/shared/live-canvas.js +62 -0
  61. package/dist/shared/live-canvas.js.map +1 -0
  62. package/dist/shared/mp.js +10 -2
  63. package/dist/shared/mp.js.map +1 -1
  64. package/dist/shared/multiplayer-types.d.ts +15 -2
  65. package/dist/shared/multiplayer-types.d.ts.map +1 -1
  66. package/dist/shared/overlays.js +800 -0
  67. package/dist/shared/overlays.js.map +1 -0
  68. package/dist/shared/page-stack.js +2 -1
  69. package/dist/shared/page-stack.js.map +1 -1
  70. package/dist/shared/pose-key.js +249 -0
  71. package/dist/shared/pose-key.js.map +1 -0
  72. package/dist/shared/race-types.d.ts +1 -1
  73. package/dist/shared/race.js +1 -1
  74. package/dist/shared/room-types.d.ts +28 -3
  75. package/dist/shared/room-types.d.ts.map +1 -1
  76. package/dist/shared/rooms.d.ts.map +1 -1
  77. package/dist/shared/rooms.js +23 -4
  78. package/dist/shared/rooms.js.map +1 -1
  79. package/dist/shared/runtime-opener.js +1 -1
  80. package/dist/shared/scene.d.ts +90 -0
  81. package/dist/shared/scene.d.ts.map +1 -0
  82. package/dist/shared/social.d.ts +90 -9
  83. package/dist/shared/social.d.ts.map +1 -1
  84. package/dist/shared/social.js +6 -1
  85. package/dist/shared/social.js.map +1 -1
  86. package/dist/shared/src.js +4563 -667
  87. package/dist/shared/src.js.map +1 -1
  88. package/dist/shared/svg-markup.js +2411 -0
  89. package/dist/shared/svg-markup.js.map +1 -0
  90. package/dist/shared/svg.js +27 -0
  91. package/dist/shared/svg.js.map +1 -0
  92. package/dist/shared/types.d.ts +174 -0
  93. package/dist/shared/types.d.ts.map +1 -0
  94. package/dist/shared/wire-types.d.ts +6 -2
  95. package/dist/shared/wire-types.d.ts.map +1 -1
  96. package/dist/social.d.ts +5 -5
  97. package/dist/social.js +2 -2
  98. package/dist/turns-rules.d.ts +2 -0
  99. package/dist/turns-rules.js +1 -0
  100. package/dist/turns.d.ts +61 -0
  101. package/dist/turns.d.ts.map +1 -0
  102. package/dist/turns.js +183 -0
  103. package/dist/turns.js.map +1 -0
  104. package/package.json +39 -3
  105. package/src/avatar/adapters/pixi-avatar.ts +73 -0
  106. package/src/avatar/adapters/pixi-crowd.ts +329 -0
  107. package/src/avatar/adapters/pixi-types.ts +84 -0
  108. package/src/avatar/adapters/three.ts +126 -0
  109. package/src/avatar/animator/animator.ts +285 -0
  110. package/src/avatar/animator/reactions.ts +44 -0
  111. package/src/avatar/animator/schedule.ts +73 -0
  112. package/src/avatar/art/beard.ts +49 -0
  113. package/src/avatar/art/glasses.ts +67 -0
  114. package/src/avatar/art/hair.ts +254 -0
  115. package/src/avatar/art/headwear.ts +75 -0
  116. package/src/avatar/art/index.ts +21 -0
  117. package/src/avatar/art/shape.ts +87 -0
  118. package/src/avatar/art-store.ts +36 -0
  119. package/src/avatar/atlas.ts +246 -0
  120. package/src/avatar/catalog-view.ts +14 -0
  121. package/src/avatar/colour.ts +109 -0
  122. package/src/avatar/describe.ts +79 -0
  123. package/src/avatar/face.ts +187 -0
  124. package/src/avatar/geom.ts +173 -0
  125. package/src/avatar/layout.ts +47 -0
  126. package/src/avatar/look-like.ts +52 -0
  127. package/src/avatar/outputs/bitmap.ts +75 -0
  128. package/src/avatar/outputs/canvas-paint.ts +177 -0
  129. package/src/avatar/outputs/canvas.ts +62 -0
  130. package/src/avatar/outputs/custom-element.ts +140 -0
  131. package/src/avatar/outputs/element.ts +58 -0
  132. package/src/avatar/outputs/live-canvas.ts +57 -0
  133. package/src/avatar/outputs/overlay-svg.ts +22 -0
  134. package/src/avatar/outputs/pose-key.ts +30 -0
  135. package/src/avatar/outputs/svg-markup.ts +113 -0
  136. package/src/avatar/outputs/svg.ts +41 -0
  137. package/src/avatar/overlays.ts +128 -0
  138. package/src/avatar/render/body.ts +116 -0
  139. package/src/avatar/render/colours.ts +81 -0
  140. package/src/avatar/render/compose.ts +346 -0
  141. package/src/avatar/render/context.ts +117 -0
  142. package/src/avatar/render/eyes.ts +78 -0
  143. package/src/avatar/render/eyewear.ts +118 -0
  144. package/src/avatar/render/frame.ts +73 -0
  145. package/src/avatar/render/head.ts +81 -0
  146. package/src/avatar/render/mouth.ts +86 -0
  147. package/src/avatar/render/props.ts +91 -0
  148. package/src/avatar/render/types.ts +93 -0
  149. package/src/avatar/render/walker-parts.ts +124 -0
  150. package/src/avatar/render/walker.ts +239 -0
  151. package/src/avatar/scene.ts +119 -0
  152. package/src/avatar-pixi.ts +20 -0
  153. package/src/avatar-svg.ts +21 -0
  154. package/src/avatar-three.ts +18 -0
  155. package/src/avatar.ts +63 -0
  156. package/src/core/avatar/avatar-cache.ts +95 -0
  157. package/src/core/avatar/avatar-ref.ts +34 -0
  158. package/src/core/avatar/avatar-types.ts +43 -0
  159. package/src/core/avatar/hub-avatar.ts +308 -0
  160. package/src/core/connection.ts +201 -92
  161. package/src/core/errors.ts +15 -2
  162. package/src/core/game-data.ts +11 -9
  163. package/src/core/hub-economy.ts +47 -10
  164. package/src/core/hub-progress.ts +94 -30
  165. package/src/core/hub.ts +228 -7
  166. package/src/core/offline/cloud-saves.ts +62 -0
  167. package/src/core/offline/hub-kinds/avatar-kind.ts +42 -0
  168. package/src/core/offline/hub-kinds/daily-kind.ts +68 -0
  169. package/src/core/offline/hub-kinds/economy-kinds.ts +86 -0
  170. package/src/core/offline/hub-kinds/game-fields.ts +2 -0
  171. package/src/core/offline/hub-kinds/index.ts +52 -0
  172. package/src/core/offline/hub-kinds/legacy-ops.ts +37 -0
  173. package/src/core/offline/hub-kinds/progress-kinds.ts +165 -0
  174. package/src/core/offline/hub-kinds/resume-kind.ts +108 -0
  175. package/src/core/offline/hub-kinds/saved-kind.ts +93 -0
  176. package/src/core/offline/kind-adapters.ts +48 -0
  177. package/src/core/offline/local-inventory.ts +64 -0
  178. package/src/core/offline/local-store.ts +66 -23
  179. package/src/core/offline/offline-api.ts +65 -0
  180. package/src/core/offline/outbox-entry.ts +59 -0
  181. package/src/core/offline/outbox-kinds.ts +78 -0
  182. package/src/core/offline/outbox-merge.ts +39 -0
  183. package/src/core/offline/outbox-replayer.ts +141 -0
  184. package/src/core/offline/outbox-store.ts +85 -0
  185. package/src/core/offline/outbox.ts +232 -0
  186. package/src/core/offline/queue.ts +4 -49
  187. package/src/core/offline/replay-lease.ts +87 -0
  188. package/src/core/offline/replay-policy.ts +52 -0
  189. package/src/core/page-account.ts +121 -0
  190. package/src/core/page.ts +14 -1
  191. package/src/core/rest/rest-client.ts +14 -3
  192. package/src/core/rest/routes.gen.ts +144 -88
  193. package/src/core/resume/hub-resume.ts +286 -0
  194. package/src/core/resume/local-resume.ts +130 -0
  195. package/src/core/resume/resume-list.ts +65 -0
  196. package/src/core/resume/resume-request.ts +52 -0
  197. package/src/core/resume/resume-types.ts +64 -0
  198. package/src/core/saved/daily-saves.ts +196 -0
  199. package/src/core/saved/daily-seed.ts +16 -0
  200. package/src/core/saved/hub-saved.ts +221 -0
  201. package/src/core/saved/local-saved.ts +122 -0
  202. package/src/core/saved/saved-list.ts +129 -0
  203. package/src/core/saved/saved-open.ts +104 -0
  204. package/src/core/saved/saved-order.ts +49 -0
  205. package/src/core/saved/saved-types.ts +24 -0
  206. package/src/core/storage/json-guards.ts +6 -0
  207. package/src/core/turns/hub-turns.ts +161 -0
  208. package/src/core/turns/turn-game.ts +510 -0
  209. package/src/core/turns/your-turn-buzz.ts +69 -0
  210. package/src/core/version/client-version.gen.ts +2 -2
  211. package/src/daily/daily-system.ts +127 -12
  212. package/src/daily/page-daily.ts +2 -2
  213. package/src/daily/types.ts +49 -11
  214. package/src/doctor/checks.ts +12 -4
  215. package/src/doctor/cli.ts +8 -2
  216. package/src/doctor/embed-check.ts +61 -0
  217. package/src/doctor/hub-version-fetch.ts +43 -43
  218. package/src/doctor/world-check.ts +61 -0
  219. package/src/index.ts +20 -2
  220. package/src/input/focus-ring.ts +13 -0
  221. package/src/input/input-registry.ts +9 -2
  222. package/src/input/input-system.ts +14 -1
  223. package/src/input/modality.ts +122 -0
  224. package/src/input/nav-state.ts +13 -4
  225. package/src/input/navigator.ts +11 -0
  226. package/src/input/zero-config.ts +1 -14
  227. package/src/input.ts +4 -0
  228. package/src/mp.ts +1 -1
  229. package/src/overlay/overlay.ts +30 -4
  230. package/src/overlay/styles.ts +4 -3
  231. package/src/realtime/bridge.ts +5 -0
  232. package/src/realtime/multiplayer-forwarder.ts +13 -1
  233. package/src/realtime/multiplayer-types.ts +15 -1
  234. package/src/realtime/room-types.ts +28 -2
  235. package/src/realtime/rooms-forwarder.ts +27 -2
  236. package/src/realtime/runtime-api.ts +33 -8
  237. package/src/realtime/social-forwarder.ts +5 -0
  238. package/src/realtime/social-types.ts +8 -0
  239. package/src/realtime/types.ts +1 -0
  240. package/src/realtime/wire-types.ts +7 -0
  241. package/src/realtime/world-types.ts +20 -0
  242. package/src/rooms.ts +1 -1
  243. package/src/social.ts +1 -0
  244. package/src/turns-kit/check-rules.ts +281 -0
  245. package/src/turns-rules.ts +12 -0
  246. package/src/turns.ts +14 -0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The walker's drawing parts (`figure: 'walker'`): limbs as outlined round
3
+ * strokes, the shirt, trousers and shoes. Colours come from the look: the
4
+ * shirt is the body colour, the trousers and shoes are picked from the body
5
+ * colour so they always stand apart from the shirt.
6
+ */
7
+ import { lightness, outline, shade } from '../colour';
8
+ import { Path } from '../geom';
9
+ import { ellipse, g, path, sc, tr, type SceneNode } from '../scene';
10
+ import type { Colours } from './colours';
11
+ import { LINE } from './context';
12
+
13
+ /** A point in rig units. */
14
+ export type Vec = readonly [number, number];
15
+
16
+ /** The walker's clothes, derived from a look's colours. */
17
+ export interface Outfit {
18
+ readonly trousers: string;
19
+ readonly trousersInk: string;
20
+ readonly shoes: string;
21
+ readonly shoesInk: string;
22
+ }
23
+
24
+ const TROUSERS = ['#34466e', '#2e323f', '#6a5642', '#4a3a60', '#3c5a4c', '#5a3a3a'] as const;
25
+ const SHOES = ['#2c2430', '#f1ece2', '#5a3a2a', '#2c2430'] as const;
26
+
27
+ /** A small stable hash of a colour string. */
28
+ function hashOf(text: string): number {
29
+ let hash = 7;
30
+ for (let i = 0; i < text.length; i++) {
31
+ hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
32
+ }
33
+ return hash;
34
+ }
35
+
36
+ /** Trousers and shoes for a shirt colour: a trouser tone at least a little lighter or darker than the shirt. */
37
+ export function outfitFor(c: Colours): Outfit {
38
+ const hash = hashOf(c.body);
39
+ const shirt = lightness(c.body);
40
+ let trousers: string = TROUSERS[hash % TROUSERS.length] ?? TROUSERS[0];
41
+ for (let i = 1; i < TROUSERS.length && Math.abs(lightness(trousers) - shirt) < 0.12; i++) {
42
+ trousers = TROUSERS[(hash + i) % TROUSERS.length] ?? trousers;
43
+ }
44
+ const shoes: string = SHOES[(hash >>> 4) % SHOES.length] ?? SHOES[0];
45
+ return { trousers, trousersInk: outline(trousers), shoes, shoesInk: outline(shoes) };
46
+ }
47
+
48
+ /** A polyline as a path `d`. */
49
+ function polyline(points: readonly Vec[]): string {
50
+ const line = new Path();
51
+ points.forEach(([px, py], i) => (i === 0 ? line.m(px, py) : line.l(px, py)));
52
+ return line.d();
53
+ }
54
+
55
+ /** A limb: a round stroke with a tonal outline under it. */
56
+ export function limb(points: readonly Vec[], width: number, fill: string, ink: string): SceneNode[] {
57
+ const d = polyline(points);
58
+ return [
59
+ path(d, { fill: 'none', stroke: ink, sw: width + LINE * 1.3, cap: 'round' }),
60
+ path(d, { fill: 'none', stroke: fill, sw: width, cap: 'round' }),
61
+ ];
62
+ }
63
+
64
+ /** A shoe at a foot point, nudged along the facing: round and stubby. */
65
+ export function shoe(at: Vec, forward: Vec, o: Outfit): SceneNode {
66
+ return ellipse(at[0] + forward[0] * 2.4, at[1] + forward[1] * 1.6 - 1.6, 7.2 + Math.abs(forward[0]) * 1.4, 4.1, { fill: o.shoes, stroke: o.shoesInk, sw: LINE * 0.8 });
67
+ }
68
+
69
+ /** A hand: a round mitten. */
70
+ export function hand(c: Colours, at: Vec): SceneNode {
71
+ return ellipse(at[0], at[1], 5.2, 5.2, { fill: c.skin, stroke: c.skinInk, sw: LINE * 0.8 });
72
+ }
73
+
74
+ /** The torso's half-widths (before the turn's width factor): square shoulders over a round belly. */
75
+ const TORSO = { shoulder: 20, belly: 23.5, hip: 21 } as const;
76
+
77
+ /** The torso's outline: shoulders at `top`, the hem at `hem`, `w` its width factor (narrower when turned). */
78
+ export function torsoPath(w: number, top: number, hem: number): string {
79
+ const shoulder = TORSO.shoulder * w;
80
+ const belly = TORSO.belly * w;
81
+ const hip = TORSO.hip * w;
82
+ const mid = (top + hem) / 2 + 2;
83
+ return new Path()
84
+ .m(-shoulder, top + 5)
85
+ .c(-shoulder, top - 1, -shoulder + 6, top - 3, -shoulder * 0.35, top - 3)
86
+ .l(shoulder * 0.35, top - 3)
87
+ .c(shoulder - 6, top - 3, shoulder, top - 1, shoulder, top + 5)
88
+ .c(belly, mid - 4, belly, mid + 4, hip, hem - 1)
89
+ .q(0, hem + 4, -hip, hem - 1)
90
+ .c(-belly, mid + 4, -belly, mid - 4, -shoulder, top + 5)
91
+ .z()
92
+ .d();
93
+ }
94
+
95
+ /** The shirt: a shade base, the lit front, the outline, and the neckline (front) or collar (back). */
96
+ export function shirt(c: Colours, w: number, top: number, hem: number, lean: number, isBack: boolean): SceneNode[] {
97
+ const d = torsoPath(w, top, hem);
98
+ const cy = (top + hem) / 2;
99
+ const hemHalf = (TORSO.hip - 3) * w;
100
+ const nodes: SceneNode[] = [
101
+ path(d, { fill: c.bodyShade }),
102
+ g([path(d, { fill: c.body })], { tf: [tr(-1.5 - lean * 2, cy - 1.5), sc(0.88, 0.9), tr(0, -cy)] }),
103
+ path(`M${String(-hemHalf)} ${String(hem - 2)}Q0 ${String(hem + 1.5)} ${String(hemHalf)} ${String(hem - 2)}`, { fill: 'none', stroke: shade(c.bodyShade), sw: 2.2, opacity: 0.7 }),
104
+ path(d, { fill: 'none', stroke: c.bodyInk, sw: LINE }),
105
+ ];
106
+ const neckX = lean * 7;
107
+ if (isBack) {
108
+ nodes.push(path(`M${String(neckX - 6)} ${String(top - 2)}Q${String(neckX)} ${String(top + 1.5)} ${String(neckX + 6)} ${String(top - 2)}`, { fill: 'none', stroke: c.bodyShade, sw: 3 }));
109
+ } else {
110
+ nodes.push(path(`M${String(neckX - 6 * w)} ${String(top - 2.6)}Q${String(neckX)} ${String(top + 5)} ${String(neckX + 6 * w)} ${String(top - 2.6)}Z`, { fill: c.skin, stroke: c.bodyInk, sw: LINE * 0.8 }));
111
+ }
112
+ return nodes;
113
+ }
114
+
115
+ /** The seat of the trousers joining the legs under the hem. */
116
+ export function hips(o: Outfit, w: number, hipY: number): SceneNode {
117
+ const half = 16 * w;
118
+ 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(), { fill: o.trousers, stroke: o.trousersInk, sw: LINE * 0.8 });
119
+ }
120
+
121
+ /** The neck under the head (a short one: the big head sits almost on the shoulders). */
122
+ export function neck(c: Colours, top: number, dx: number): SceneNode {
123
+ return path(new Path().m(dx - 5, top - 6).l(dx + 5, top - 6).l(dx + 5, top).l(dx - 5, top).z().d(), { fill: c.skinShade, stroke: c.skinInk, sw: LINE * 0.6 });
124
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * The world walker (`figure: 'walker'`): a small, chunky, chibi-like
3
+ * full-body character for maps and crowds: a short round body with stubby
4
+ * legs and arms, a shirt, trousers and shoes, and a big head on top
5
+ * ({@link WALKER_HEAD_SCALE}). It takes the blob's pose names: `walk` and
6
+ * `run` waddle (quick steps that bounce and rock from foot to foot), `sit`
7
+ * folds the legs over a seat, `idle` breathes, `wave` and `cheer` lift the
8
+ * arms.
9
+ *
10
+ * Standing, the origin is the feet. Sitting (`sit`, `play`), the origin is
11
+ * the seat: the hips rest on it and the short legs dangle over its front
12
+ * edge, the feet {@link WALKER_SEAT_DROP} units below it.
13
+ */
14
+ import { ellipse, g, rot, type SceneNode } from '../scene';
15
+ import type { BodyLayout } from './body';
16
+ import type { Colours } from './colours';
17
+ import type { BodyPose } from './types';
18
+ import { hand, hips, limb, neck, outfitFor, shirt, shoe, type Outfit, type Vec } from './walker-parts';
19
+
20
+ /** The head's scale on a walker (the blob draws it at 1). */
21
+ export const WALKER_HEAD_SCALE = 0.62;
22
+
23
+ /** How far below the seat a sitter's dangling feet reach (rig units): short legs swing clear of the floor. */
24
+ export const WALKER_SEAT_DROP = 13;
25
+
26
+ /** Heights of the hips, shoulders and head centre above the origin. */
27
+ export interface WalkerHeights {
28
+ readonly hip: number;
29
+ readonly shoulder: number;
30
+ readonly head: number;
31
+ }
32
+
33
+ /** Standing: a short, round body (half the first walker's 72 units to the shoulders) under the head. */
34
+ export const WALKER_STANDING: WalkerHeights = { hip: -18, shoulder: -38, head: -70 };
35
+ const SEATED: WalkerHeights = { hip: -3, shoulder: -23, head: -55 };
36
+ const LEG = 18;
37
+ const UPPER_ARM = 8;
38
+ const FOREARM = 7;
39
+ const LEG_W = 13;
40
+ const SLEEVE_W = 12.5;
41
+ const ARM_W = 9;
42
+ /** How far out the hips and shoulders sit from the middle. */
43
+ const HIP_X = 7.5;
44
+ const SHOULDER_X = 19;
45
+ /** The seated thigh, along the facing. */
46
+ const THIGH = 9;
47
+ const DEG = Math.PI / 180;
48
+
49
+ /** The contact shadow's half-width under a walker (the round body is wide). */
50
+ export const WALKER_SHADOW = 22;
51
+
52
+ /** A walker's layout: the blob's fields plus where the head goes, its scale and the long-hair split. */
53
+ export interface WalkerLayout extends BodyLayout {
54
+ readonly headY: number;
55
+ readonly headScale: number;
56
+ /** The shoulder line in head space: long hair below it hangs behind the body. */
57
+ readonly shoulderLine: number;
58
+ }
59
+
60
+ /** One side of the body: −1 is the character's right, +1 its left. */
61
+ type Side = -1 | 1;
62
+
63
+ /** The view of a facing: how far it is turned, which way is forward on screen. */
64
+ interface View {
65
+ /** sin of the turn: 0 front or back, ~1 side on. */
66
+ readonly side: number;
67
+ /** cos of the turn: 1 front, −1 back. */
68
+ readonly front: number;
69
+ /** Forward on the floor, in screen units per unit (y is foreshortened). */
70
+ readonly forward: Vec;
71
+ readonly isBack: boolean;
72
+ /** The torso's width factor. */
73
+ readonly width: number;
74
+ }
75
+
76
+ function viewOf(angleDeg: number): View {
77
+ const side = Math.sin(angleDeg * DEG);
78
+ const front = Math.cos(angleDeg * DEG);
79
+ return { side, front, forward: [side, front * 0.45], isBack: angleDeg > 90, width: 1 - 0.26 * Math.abs(side) };
80
+ }
81
+
82
+ /** A body side's screen x for a lateral distance (sides overlap when turned). */
83
+ const lateralX = (v: View, sideOf: Side, distance: number): number => sideOf * distance * v.front;
84
+
85
+ /** How near the viewer a side is (> 0: drawn in front when turned). */
86
+ const nearness = (v: View, sideOf: Side): number => -sideOf * v.side;
87
+
88
+ const add = (a: Vec, b: Vec, k = 1): Vec => [a[0] + b[0] * k, a[1] + b[1] * k];
89
+
90
+ /** What each limb does in a pose. */
91
+ interface Limbs {
92
+ /** Foot (standing) or knee-then-foot (sitting) per side. */
93
+ readonly leg: (sideOf: Side) => readonly [Vec, Vec, Vec];
94
+ readonly arm: (sideOf: Side) => readonly [Vec, Vec, Vec];
95
+ }
96
+
97
+ /** The walk's and run's step: stride angle, foot lift, bounce and side-to-side rock (degrees). */
98
+ const GAIT = {
99
+ walk: { stride: 36, lift: 3.6, bounce: 3.2, rock: 5 },
100
+ run: { stride: 46, lift: 5.4, bounce: 4.4, rock: 3.5 },
101
+ } as const;
102
+
103
+ /** A gait's numbers. */
104
+ type Gait = (typeof GAIT)[keyof typeof GAIT];
105
+
106
+ /** The gait a pose steps with, if it steps. */
107
+ function gaitOf(pose: BodyPose): Gait | null {
108
+ return pose === 'run' ? GAIT.run : pose === 'walk' || pose === 'dance' ? GAIT.walk : null;
109
+ }
110
+
111
+ /** A shoulder joint: out at the torso's edge (narrower when turned), nudged along the facing. */
112
+ const shoulderAt = (v: View, h: WalkerHeights, sideOf: Side, bob: number): Vec => [lateralX(v, sideOf, SHOULDER_X * v.width) + v.forward[0] * 1.5, h.shoulder + 4 + bob];
113
+
114
+ function standingLimbs(v: View, h: WalkerHeights, pose: BodyPose, phase: number, bob: number): Limbs {
115
+ const gait = gaitOf(pose);
116
+ const stepOf = (sideOf: Side): number => (phase + (sideOf === 1 ? 0 : 0.5)) * Math.PI * 2;
117
+ const swingOf = (sideOf: Side): number => (gait ? Math.sin(stepOf(sideOf)) : 0);
118
+ return {
119
+ leg: (sideOf) => {
120
+ const hip: Vec = [lateralX(v, sideOf, HIP_X), h.hip + bob];
121
+ const lift = gait ? Math.max(0, Math.cos(stepOf(sideOf))) * gait.lift : 0;
122
+ const reach = gait ? Math.sin(gait.stride * DEG) * LEG * swingOf(sideOf) : 0;
123
+ const foot: Vec = [hip[0] + v.forward[0] * reach, -bob * 0.2 + v.forward[1] * reach - lift];
124
+ const knee: Vec = [(hip[0] + foot[0]) / 2 + v.forward[0] * lift, (hip[1] + foot[1]) / 2 + v.forward[1] * lift];
125
+ return [hip, knee, foot];
126
+ },
127
+ arm: (sideOf) => {
128
+ const shoulder = shoulderAt(v, h, sideOf, bob);
129
+ // The arms stand off the round belly.
130
+ const out = sideOf * v.front * 3.4;
131
+ if (pose === 'cheer' || (pose === 'wave' && sideOf === -1)) {
132
+ const wave = pose === 'wave' ? Math.sin(phase * Math.PI * 2) * 3 : 0;
133
+ const elbow: Vec = [shoulder[0] + out * 2.2, shoulder[1] - 7];
134
+ return [shoulder, elbow, [elbow[0] + out * 0.8 + wave, elbow[1] - 10]];
135
+ }
136
+ const swing = gait ? -swingOf(sideOf) * (pose === 'run' ? 1.4 : 1.1) : Math.sin(phase * Math.PI * 2) * 0.06;
137
+ const reach = Math.sin(30 * DEG) * swing;
138
+ const elbow: Vec = [shoulder[0] + out + v.forward[0] * UPPER_ARM * reach * 0.6, shoulder[1] + UPPER_ARM + v.forward[1] * UPPER_ARM * reach];
139
+ const wrist: Vec = [elbow[0] + out * 0.35 + v.forward[0] * FOREARM * reach * 1.6, elbow[1] + FOREARM - Math.abs(reach) * 2 + v.forward[1] * FOREARM * reach];
140
+ return [shoulder, elbow, wrist];
141
+ },
142
+ };
143
+ }
144
+
145
+ function seatedLimbs(v: View, h: WalkerHeights, pose: BodyPose): Limbs {
146
+ const thigh: Vec = [v.forward[0] * THIGH, v.forward[1] * (THIGH + 1)];
147
+ return {
148
+ leg: (sideOf) => {
149
+ const hip: Vec = [lateralX(v, sideOf, HIP_X), h.hip];
150
+ const knee = add(hip, thigh);
151
+ return [hip, knee, [knee[0] + v.forward[0] * 2, WALKER_SEAT_DROP - 2]];
152
+ },
153
+ arm: (sideOf) => {
154
+ const shoulder = shoulderAt(v, h, sideOf, 0);
155
+ const elbow: Vec = [shoulder[0] + sideOf * v.front * 3 + v.forward[0] * 2, shoulder[1] + UPPER_ARM];
156
+ const lap = add([lateralX(v, sideOf, pose === 'play' ? 4 : 8), h.hip - 3], thigh, pose === 'play' ? 0.9 : 0.6);
157
+ return [shoulder, elbow, lap];
158
+ },
159
+ };
160
+ }
161
+
162
+ /** The long-hair split in head space: a little above the torso's top edge (the first walker's line sat 12 head units above it). */
163
+ function shoulderLineOf(torsoTop: number, headY: number): number {
164
+ return (torsoTop - 3 - headY) / WALKER_HEAD_SCALE - 12;
165
+ }
166
+
167
+ /** The walker's drawing for a pose, at a facing angle (0 = S, 180 = N; west facings are mirrored by the caller). */
168
+ export function walkerLayout(c: Colours, pose: BodyPose, angleDeg: number, phase: number, outfit: Outfit = outfitFor(c)): WalkerLayout {
169
+ const v = viewOf(angleDeg);
170
+ const isSitting = pose === 'sit' || pose === 'play';
171
+ const h = isSitting ? SEATED : WALKER_STANDING;
172
+ const cycle = Math.sin(phase * Math.PI * 2);
173
+ const gait = isSitting ? null : gaitOf(pose);
174
+ const hop = pose === 'jump' || pose === 'cheer' ? Math.abs(Math.sin(phase * Math.PI)) * (pose === 'jump' ? 12 : 6) : 0;
175
+ // Short legs: a quick, bouncy waddle that rocks from foot to foot.
176
+ const bob = gait ? -gait.bounce * (1 - Math.abs(cycle)) : pose === 'idle' ? cycle * 0.6 : 0;
177
+ const rock = gait ? cycle * gait.rock * (0.4 + 0.6 * Math.abs(v.front)) : 0;
178
+ const limbs = isSitting ? seatedLimbs(v, h, pose) : standingLimbs(v, h, pose, phase, bob);
179
+ const o = outfit;
180
+ const [farSide, nearSide]: readonly [Side, Side] = nearness(v, 1) > nearness(v, -1) ? [-1, 1] : [1, -1];
181
+ const isTurned = Math.abs(v.side) > 0.3;
182
+ const lean = v.side * (isSitting ? 0.6 : 1);
183
+ const armNodes = (sideOf: Side): SceneNode[] => {
184
+ const [shoulder, elbow, wrist] = limbs.arm(sideOf);
185
+ return [...limb([shoulder, elbow, wrist], ARM_W, c.skin, c.skinInk), ...limb([shoulder, add(shoulder, [elbow[0] - shoulder[0], elbow[1] - shoulder[1]], 0.7)], SLEEVE_W, c.body, c.bodyInk), hand(c, wrist)];
186
+ };
187
+ const legNodes = (sideOf: Side): SceneNode[] => {
188
+ const points = limbs.leg(sideOf);
189
+ const foot = points[2];
190
+ return [...limb(points, LEG_W, o.trousers, o.trousersInk), shoe(foot, v.forward, o)];
191
+ };
192
+ const torsoTop = h.shoulder + bob;
193
+ const torso = shirt(c, v.width, torsoTop, h.hip + 3 + bob, lean, v.isBack);
194
+ // Seated with our back to us, the arms reach forward and the legs are under the seat: both hide behind the back.
195
+ const isTucked = isSitting && v.isBack;
196
+ const behind: SceneNode[] = isTucked ? [...armNodes(farSide), ...armNodes(nearSide)] : isTurned ? armNodes(farSide) : [];
197
+ // Sitting facing us, the thighs come toward the viewer over the shirt's hem.
198
+ const isLegsFirst = v.isBack || !isSitting;
199
+ const legs = isTucked ? [] : [...legNodes(farSide), ...legNodes(nearSide)];
200
+ const body: SceneNode[] = [
201
+ ...(isLegsFirst ? legs : []),
202
+ hips(o, v.width, h.hip + bob),
203
+ neck(c, torsoTop, v.forward[0] * 2),
204
+ ...torso,
205
+ ...(isLegsFirst ? [] : legs),
206
+ ...(isTucked ? [] : isTurned ? armNodes(nearSide) : [...armNodes(-1), ...armNodes(1)]),
207
+ ];
208
+ // The rock turns the body about the feet; the head rides it and nods with it.
209
+ const rocked = (nodes: SceneNode[]): SceneNode[] => (rock === 0 || nodes.length === 0 ? nodes : [g(nodes, { tf: [rot(rock)] })]);
210
+ const headY = h.head + bob * 1.1;
211
+ const prop = add([0, h.hip - 6], v.forward, 8);
212
+ return {
213
+ behind: rocked(behind), body: rocked(body), front: [],
214
+ headDx: v.forward[0] * 1.5 - headY * Math.sin(rock * DEG), headDy: 0, headY, headScale: WALKER_HEAD_SCALE,
215
+ shoulderLine: shoulderLineOf(torsoTop, headY),
216
+ tilt: (gait ? v.side * 2 : 0) + rock, lift: hop, prop: { x: prop[0], y: prop[1] },
217
+ };
218
+ }
219
+
220
+ const GREY = '#6f7787';
221
+ const GREY_INK = '#5c6474';
222
+
223
+ /** Every colour slot in the silhouette grey. */
224
+ const GREY_COLOURS: Colours = {
225
+ skin: GREY, skinShade: GREY, skinLight: GREY, skinInk: GREY_INK, eyeRim: GREY, hair: GREY, hairShade: GREY, hairLight: GREY,
226
+ hairInk: GREY_INK, hairBuzz: GREY, brow: GREY, beard: GREY, beardShade: GREY, beardInk: GREY_INK, stubble: GREY, eye: GREY,
227
+ eyeDark: GREY, body: GREY, bodyShade: GREY, bodyLight: GREY, bodyInk: GREY_INK,
228
+ };
229
+
230
+ /** The grey walker shown before the art loads (or for a hidden player): its nodes, head centre and top. */
231
+ export function walkerSilhouette(): { readonly nodes: readonly SceneNode[]; readonly headY: number; readonly top: number } {
232
+ const b = walkerLayout(GREY_COLOURS, 'idle', 0, 0, { trousers: GREY, trousersInk: GREY_INK, shoes: GREY, shoesInk: GREY_INK });
233
+ const r = 46 * WALKER_HEAD_SCALE;
234
+ return {
235
+ nodes: [...b.behind, ...b.body, ellipse(0, b.headY, r, r * 1.05, { fill: GREY })],
236
+ headY: b.headY,
237
+ top: b.headY - r - 4,
238
+ };
239
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The display list every renderer draws: a small tree of groups and shapes
3
+ * in rig units. The composer builds it once per pose; `toSvg` turns it into
4
+ * markup, `paintCanvas` draws it on a 2D canvas, and the Pixi and three.js
5
+ * adapters draw it through a canvas. Colours in a scene are always derived
6
+ * `#rrggbb` values or fixed constants, never caller text.
7
+ */
8
+
9
+ /** One transform step, applied in order (SVG semantics). */
10
+ export type TfOp =
11
+ | { readonly k: 't'; readonly x: number; readonly y: number }
12
+ | { readonly k: 's'; readonly x: number; readonly y: number }
13
+ | { readonly k: 'r'; readonly a: number }
14
+ | { readonly k: 'skx'; readonly a: number };
15
+
16
+ /** How a shape is painted. */
17
+ export interface Paint {
18
+ readonly fill?: string;
19
+ readonly fillOpacity?: number;
20
+ readonly stroke?: string;
21
+ readonly sw?: number;
22
+ readonly cap?: 'round' | 'butt';
23
+ readonly opacity?: number;
24
+ readonly evenOdd?: boolean;
25
+ }
26
+
27
+ /** A path shape. */
28
+ export interface PathNode extends Paint {
29
+ readonly k: 'path';
30
+ readonly d: string;
31
+ readonly tf?: readonly TfOp[];
32
+ }
33
+
34
+ /** An ellipse (a circle when rx = ry). */
35
+ export interface EllipseNode extends Paint {
36
+ readonly k: 'ellipse';
37
+ readonly cx: number;
38
+ readonly cy: number;
39
+ readonly rx: number;
40
+ readonly ry: number;
41
+ readonly tf?: readonly TfOp[];
42
+ }
43
+
44
+ /** A rectangle with optional rounded corners. */
45
+ export interface RectNode extends Paint {
46
+ readonly k: 'rect';
47
+ readonly x: number;
48
+ readonly y: number;
49
+ readonly w: number;
50
+ readonly h: number;
51
+ readonly r?: number;
52
+ readonly tf?: readonly TfOp[];
53
+ }
54
+
55
+ /** A line of text (overlay labels and FX glyphs). */
56
+ export interface TextNode extends Paint {
57
+ readonly k: 'text';
58
+ readonly x: number;
59
+ readonly y: number;
60
+ readonly text: string;
61
+ readonly size: number;
62
+ readonly font: 'mono' | 'pixel';
63
+ readonly anchor?: 'start' | 'middle';
64
+ readonly bold?: boolean;
65
+ }
66
+
67
+ /** A clip region: the union of its shapes (even-odd when asked). */
68
+ export interface Clip {
69
+ readonly shapes: readonly (PathNode | EllipseNode | RectNode)[];
70
+ readonly evenOdd?: boolean;
71
+ }
72
+
73
+ /** A group: a transform, an optional clip and opacity, and children. */
74
+ export interface GroupNode {
75
+ readonly k: 'g';
76
+ readonly tf?: readonly TfOp[];
77
+ readonly clip?: Clip;
78
+ readonly opacity?: number;
79
+ /** A soft light rim around the silhouette (the world, §14.2). */
80
+ readonly rim?: boolean;
81
+ readonly children: readonly SceneNode[];
82
+ }
83
+
84
+ /** Anything in a scene. */
85
+ export type SceneNode = GroupNode | PathNode | EllipseNode | RectNode | TextNode;
86
+
87
+ /** A group helper that drops empty groups' wrapping. */
88
+ export function g(children: readonly SceneNode[], opts: Omit<GroupNode, 'k' | 'children'> = {}): GroupNode {
89
+ return { k: 'g', ...opts, children };
90
+ }
91
+
92
+ /** A path helper. */
93
+ export function path(d: string, paint: Paint, tf?: readonly TfOp[]): PathNode {
94
+ return { k: 'path', d, ...paint, ...(tf ? { tf } : {}) };
95
+ }
96
+
97
+ /** An ellipse helper. */
98
+ export function ellipse(cx: number, cy: number, rx: number, ry: number, paint: Paint, tf?: readonly TfOp[]): EllipseNode {
99
+ return { k: 'ellipse', cx, cy, rx, ry, ...paint, ...(tf ? { tf } : {}) };
100
+ }
101
+
102
+ /** A rect helper. */
103
+ export function rect(left: number, top: number, w: number, h: number, paint: Paint, r?: number): RectNode {
104
+ return { k: 'rect', x: left, y: top, w, h, ...paint, ...(r === undefined ? {} : { r }) };
105
+ }
106
+
107
+ /** A translate step. */
108
+ export const tr = (dx: number, dy: number): TfOp => ({ k: 't', x: dx, y: dy });
109
+ /** A scale step (uniform when `sy` is omitted). */
110
+ export const sc = (sx: number, sy = sx): TfOp => ({ k: 's', x: sx, y: sy });
111
+ /** A rotate step, degrees. */
112
+ export const rot = (a: number): TfOp => ({ k: 'r', a });
113
+ /** A horizontal skew step, degrees. */
114
+ export const skewX = (a: number): TfOp => ({ k: 'skx', a });
115
+
116
+ /** A round-joined stroke paint. */
117
+ export function line(colour: string, width: number, cap: 'round' | 'butt' = 'round', opacity?: number): Paint {
118
+ return { fill: 'none', stroke: colour, sw: width, cap, ...(opacity === undefined ? {} : { opacity }) };
119
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@diffenderfer-games/hub/avatar/pixi`: avatars in PixiJS. Pass your PIXI
3
+ * namespace (the hub has no pixi dependency):
4
+ *
5
+ * ```ts
6
+ * import * as PIXI from 'pixi.js';
7
+ * import { ready, animator } from '@diffenderfer-games/hub/avatar';
8
+ * import { pixiAvatar } from '@diffenderfer-games/hub/avatar/pixi';
9
+ *
10
+ * await ready();
11
+ * const face = pixiAvatar(PIXI, seat.look, { size: 64, animator: animator(seat.look, { seed: seat.userId }) });
12
+ * stage.addChild(face.view);
13
+ * app.ticker.add(() => face.update(performance.now()));
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ export { pixiAvatar, avatarTexture, type PixiAvatar, type PixiAvatarOptions } from './avatar/adapters/pixi-avatar';
19
+ export { createCrowd, type AvatarCrowd, type CrowdMember, type CrowdOptions } from './avatar/adapters/pixi-crowd';
20
+ export type { PixiContainer, PixiNs, PixiSprite, PixiTexture } from './avatar/adapters/pixi-types';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@diffenderfer-games/hub/avatar/svg`: avatars as SVG markup with no DOM,
3
+ * for servers, workers and server-side rendering. The same renderer as
4
+ * `@diffenderfer-games/hub/avatar` (`render.svg`), without the DOM, canvas
5
+ * and animation outputs.
6
+ *
7
+ * ```ts
8
+ * import { ready, renderSvg } from '@diffenderfer-games/hub/avatar/svg';
9
+ *
10
+ * await ready();
11
+ * const markup = renderSvg(player.look, { size: 64, pose: { expression: 'happy' } });
12
+ * ```
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ export { decodeLook, defaultLook, encodeLook, type AvatarLook } from '@diffenderfer-games/hub-contract/avatar';
17
+ export { ready } from './avatar/art-store';
18
+ export { EXPRESSION_NAMES, isExpression, type Expression } from './avatar/face';
19
+ export { renderSvg } from './avatar/outputs/svg';
20
+ export { describe } from './avatar/describe';
21
+ export type { Facing, Frame, LookLike, Pose, RenderOptions } from './avatar/render/types';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@diffenderfer-games/hub/avatar/three`: avatars in three.js, as a
3
+ * `CanvasTexture` or a billboard `Sprite`. Pass your THREE namespace (the hub
4
+ * has no three dependency):
5
+ *
6
+ * ```ts
7
+ * import * as THREE from 'three';
8
+ * import { ready } from '@diffenderfer-games/hub/avatar';
9
+ * import { avatarSprite } from '@diffenderfer-games/hub/avatar/three';
10
+ *
11
+ * await ready();
12
+ * const { sprite } = avatarSprite(THREE, me.look, { frame: 'head', size: 128, height: 0.4 });
13
+ * helmet.add(sprite);
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ export { avatarCanvasTexture, avatarSprite, type ThreeAvatarOptions, type ThreeAvatarTexture, type ThreeNs, type ThreeTextureNs } from './avatar/adapters/three';
package/src/avatar.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `@diffenderfer-games/hub/avatar`: draw players' avatars in any game.
3
+ *
4
+ * ```ts
5
+ * import { ready, render, animator } from '@diffenderfer-games/hub/avatar';
6
+ *
7
+ * await ready(); // loads the art once
8
+ * el.innerHTML = render.svg(seat.look, { size: 48 });
9
+ * const anim = animator(seat.look, { seed: seat.userId });
10
+ * anim.express('happy');
11
+ * render.canvas(ctx, seat.look, { x, y, size: 64, pose: anim.pose(now) });
12
+ * ```
13
+ *
14
+ * The art loads lazily (`ready()`); before it does, renders draw the
15
+ * silhouette. Zero dependencies; the Pixi and three.js adapters take the
16
+ * library as an argument (`/avatar/pixi`, `/avatar/three`). Docs: "Avatars"
17
+ * in docs/hub.md.
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ import { renderBitmap, renderDataUrl } from './avatar/outputs/bitmap';
22
+ import { renderCanvas, renderCanvasAt } from './avatar/outputs/canvas';
23
+ import { renderElement } from './avatar/outputs/element';
24
+ import { renderSvg } from './avatar/outputs/svg';
25
+
26
+ export {
27
+ ACCESSORY_SLOTS, CATALOG, CATALOG_REVISION, DEFAULT_GEN, FRAME_PRESETS, LOOK_CODE_PREFIX, PALETTES, SECOND_COLOUR_CODE,
28
+ canonicalLook, decodeLook, defaultLook, distinctLooks, encodeLook, isLookCode, isTinted, isValidLook, lookProblem,
29
+ normaliseHex, partById, partName, partsOf, randomLook, resolveColour, sameLook, seededRng,
30
+ type AccessorySlot, type AvatarLook, type CatalogEntry, type DecodedLook, type LookAccessory, type LookColour,
31
+ type LookPart, type LookProblem, type PaletteName, type PartCategory, type Preset, type RandomLookOptions, type Rng,
32
+ } from '@diffenderfer-games/hub-contract/avatar';
33
+ export { ready, onArtReady } from './avatar/art-store';
34
+ export { animator, Animator, type AnimatorOptions, type At, type Feeling } from './avatar/animator/animator';
35
+ export { REACTIONS, type Reaction } from './avatar/animator/reactions';
36
+ export { EXPRESSIONS, EXPRESSION_NAMES, expressionFromEmote, isExpression, mix, type BrowParams, type Expression, type FaceExtra, type FaceParams, type MouthParams } from './avatar/face';
37
+ export { facingFrom, layout, lookToward } from './avatar/layout';
38
+ export { avatarEmoji, describe } from './avatar/describe';
39
+ export { defineAvatarElement } from './avatar/outputs/custom-element';
40
+ export type { AvatarElement } from './avatar/outputs/element';
41
+ export type { CanvasPlacement } from './avatar/outputs/canvas';
42
+ export { overlays, type ActivityIcon, type Overlay, type ReactionGlyph } from './avatar/overlays';
43
+ export { overlaySvg, paintOverlay } from './avatar/outputs/overlay-svg';
44
+ export { catalog } from './avatar/catalog-view';
45
+ export { composeAvatar, HEAD_Y, type Composed } from './avatar/render/compose';
46
+ export { WALKER_HEAD_SCALE, WALKER_SEAT_DROP } from './avatar/render/walker';
47
+ export { paintNodes, type Ctx2D } from './avatar/outputs/canvas-paint';
48
+ export { sceneToSvg } from './avatar/outputs/svg-markup';
49
+ export { toCode, toLook } from './avatar/look-like';
50
+ export { contrast, deltaE, lightness } from './avatar/colour';
51
+ export type { SceneNode } from './avatar/scene';
52
+ export type { Anchor, BodyPose, Facing, Figure, Frame, LookDir, LookLike, Point, Pose, RenderOptions, StatusProp, View } from './avatar/render/types';
53
+ export { AvatarAtlas, createAtlas, type AtlasFrame, type AtlasOptions } from './avatar/atlas';
54
+
55
+ /** Every output: SVG markup, a live element, Canvas 2D, a bitmap or a data URL. */
56
+ export const render = {
57
+ svg: renderSvg,
58
+ element: renderElement,
59
+ canvas: renderCanvas,
60
+ canvasAt: renderCanvasAt,
61
+ bitmap: renderBitmap,
62
+ dataUrl: renderDataUrl,
63
+ };