@fabi-play/sdk 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,76 @@
1
+ Fabi Play Kit License
2
+ Version 1.0 (DRAFT, not yet reviewed by a lawyer)
3
+
4
+ Copyright (c) 2026 Fabi Play. All rights reserved.
5
+
6
+ This license applies to the Fabi Play Kit: the packages @fabi-play/protocol, @fabi-play/motion,
7
+ @fabi-play/avatars, @fabi-play/sdk and @fabi-play/engine, in source and built form, as published on npm, served
8
+ from https://cdn.fabiplay.app or found in this repository, including their code, type declarations, documentation,
9
+ avatar artwork and other assets (together, the "Kit").
10
+
11
+ 1. Free of charge, for Fabi Play only
12
+
13
+ Fabi Play grants you a worldwide, non-exclusive, non-transferable, non-sublicensable, royalty-free license to
14
+ use, copy, and modify the Kit, and to distribute it as part of a Permitted Work, solely to make, test, run and
15
+ distribute Permitted Works.
16
+
17
+ A "Permitted Work" is a game, tool, or other software made to run on, be published on, or support the
18
+ development of games for the Fabi Play platform (https://fabiplay.app), including games submitted to Fabi Play
19
+ and tools used to build, test or preview them.
20
+
21
+ 2. What is not allowed
22
+
23
+ You may not, without Fabi Play's prior written permission:
24
+
25
+ a. use the Kit, or any part or modified version of it, in software that is not a Permitted Work, including in
26
+ any other game platform, product or service;
27
+ b. distribute the Kit, or a modified version of it, on its own or as a library, SDK, engine or framework for
28
+ others to build with, other than as part of a Permitted Work (installing it from npm or loading it from
29
+ https://cdn.fabiplay.app is not a distribution by you);
30
+ c. sell, rent or sublicense the Kit itself;
31
+ d. remove or alter this license, copyright notices, or the license banners in the built files;
32
+ e. use the avatars, the mascot "Fabi", or the name, logos or other trademarks of Fabi Play, except as the Kit
33
+ does when it runs inside a Permitted Work. This license grants no trademark rights;
34
+ f. use the Kit to circumvent, interfere with or misrepresent the Fabi Play platform, its safety, privacy or
35
+ parental controls, or its rules for games.
36
+
37
+ 3. Visible, not open source
38
+
39
+ The Kit's code is published so that developers can read it, debug with it and build Permitted Works. Being
40
+ visible does not make it open source; no rights are granted beyond those stated here.
41
+
42
+ 4. Your works
43
+
44
+ You keep all rights in your own code and content. This license does not give Fabi Play any rights in your
45
+ Permitted Works; publishing a game on Fabi Play is governed by Fabi Play's separate developer terms.
46
+
47
+ 5. Feedback
48
+
49
+ If you send Fabi Play suggestions or changes for the Kit, Fabi Play may use them without obligation to you.
50
+
51
+ 6. Termination
52
+
53
+ This license ends automatically if you break its terms. If you fix the breach within 30 days of becoming aware
54
+ of it, the license is reinstated, unless Fabi Play has already told you in writing that it is terminated. On
55
+ termination you must stop using and distributing the Kit. Permitted Works already distributed to end users in
56
+ compliance with this license may continue to be used by those end users.
57
+
58
+ 7. No warranty
59
+
60
+ THE KIT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
61
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
62
+
63
+ 8. Limitation of liability
64
+
65
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL FABI PLAY OR ITS CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
66
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
67
+ CONNECTION WITH THE KIT OR THE USE OR OTHER DEALINGS IN THE KIT.
68
+
69
+ 9. Changes to this license
70
+
71
+ Fabi Play may publish new versions of this license. Each version of the Kit is governed by the license version
72
+ it is distributed with, unless you choose to accept a later version.
73
+
74
+ 10. Contact
75
+
76
+ For permissions beyond this license: https://fabiplay.app
package/README.md CHANGED
@@ -1,3 +1,87 @@
1
- # Temporary Holding Version
1
+ # @fabi-play/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ What a [Fabi Play](https://fabiplay.app) game uses, from inside its sandboxed frame: the players and their bodies,
4
+ movements by name (jump, squat, clap, shapes, lanes, freeze, hands as pointers), the avatars, the breaks, the scores
5
+ and the end of the game.
6
+
7
+ ## npm
8
+
9
+ ```sh
10
+ npm install @fabi-play/sdk
11
+ ```
12
+
13
+ ```ts
14
+ import { connect } from '@fabi-play/sdk'
15
+
16
+ const play = await connect()
17
+ play.on('gesture', ({ player, gesture }) => console.log(player, gesture))
18
+ play.ready()
19
+ ```
20
+
21
+ ESM only, types included, no dependencies.
22
+
23
+ ## Script tag
24
+
25
+ From the CDN, pinned to an exact version with its Subresource Integrity hash (listed in
26
+ `https://cdn.fabiplay.app/sdk/<version>/integrity.json`, and in `dist/integrity.json` of this package):
27
+
28
+ ```html
29
+ <script
30
+ src="https://cdn.fabiplay.app/sdk/0.1.0/fabi-play.umd.min.js"
31
+ integrity="sha384-…"
32
+ crossorigin="anonymous"
33
+ data-keys="jump:Space,left:ArrowLeft,right:ArrowRight"
34
+ ></script>
35
+ <script>
36
+ FabiPlay.on('jump', ({ player }) => hop(player))
37
+ </script>
38
+ ```
39
+
40
+ | File | What |
41
+ | --- | --- |
42
+ | `fabi-play.umd.min.js` | UMD, minified: global `FabiPlay`, or the module under AMD / CommonJS |
43
+ | `fabi-play.umd.js` | the same, readable |
44
+ | `fabi-play.js` | the classic script (IIFE): sets `window.FabiPlay` |
45
+ | `fabi-play.d.ts` | types for script-tag games (`FabiPlay.PlayerFrame`, …) |
46
+ | `fabi-play.schema.json` | the JSON Schema of `fabi-play.json` |
47
+
48
+ ## Fabi explains your game (intro)
49
+
50
+ Before calibration, Fabi (the mascot) can explain your game on the TV in a few steps: a speech bubble over an image
51
+ of the game, or a move shown with the children's own avatars, which they can also try. You write only data, in
52
+ `fabi-play.json`:
53
+
54
+ ```json
55
+ "intro": { "steps": [
56
+ { "say": { "it": "Si corre nel bosco! Ognuno ha la sua pista e raccoglie le stelle.", "en": "Let's run in the woods! Everyone has a track and collects stars." }, "image": "intro/track.jpg" },
57
+ { "say": { "it": "Tronco? Salta! Provate adesso.", "en": "A log? Jump! Try it now." }, "move": "jump", "practice": true },
58
+ { "say": { "it": "Ramo basso? Giù!", "en": "Low branch? Down!" }, "move": "squat" }
59
+ ] }
60
+ ```
61
+
62
+ - **1 to 4 steps.** Fabi Play adds its own last step ("Ready?") and goes on to calibration.
63
+ - **`say`**: one sentence, in Italian and English, up to 90 characters each (an emoji counts as one), with no spaces
64
+ at either end and no control characters. Or one per age range, when the little
65
+ ones need simpler words: `{ "3-5": { "it": "…", "en": "…" }, "9-12": { "it": "…", "en": "…" } }` (at least one
66
+ range; a child of a range you left out gets the closest one you wrote, the younger if two are as close).
67
+ - **Each step has an `image` or a `move`, not both.**
68
+ - `image`: a picture of the game, 16:9, at least 1280×720, `.jpg` or `.png` (no WebP: old TV browsers may not
69
+ decode it; use JPG, quality about 80, for screenshots and PNG only for flat art), a path
70
+ relative to the game's address like `entry`: no scheme (`https://`), no leading `/`, no `.` or `..` segments,
71
+ letters, digits, `_` and `-`, extension in lowercase (`intro/track.jpg`, not `intro/Track.JPG`).
72
+ - `move`: one of `jump`, `squat`, `clap`, `lane-left`, `lane-right`, `arms-up`, `arms-wide`, `arms-v`, `star`,
73
+ drawn with each child's avatar.
74
+ - `practice` (optional, only with a `move`): `true` and the children try it, each gets a tick, and the step moves
75
+ on when all did, or after 8 seconds. Default `false`: Fabi only shows it.
76
+ - **The voice is Fabi Play's.** Fabi speaks each sentence with a clip Fabi Play generates from the text when the
77
+ game is published. Changing a text needs a new clip: until it is made, the TV shows that sentence in the bubble
78
+ only.
79
+ - **No `intro`**: the game goes straight to calibration, as before.
80
+ - **An invalid intro** is reported by the check (the Studio, `parseManifest`, the JSON Schema) and skipped by the
81
+ TV: it never blocks the game.
82
+
83
+ Docs: https://docs.fabiplay.app.
84
+
85
+ ## License
86
+
87
+ Fabi Play Kit License: free to use, for making games and tools for Fabi Play only. See [LICENSE](./LICENSE).
@@ -0,0 +1,256 @@
1
+ // @fabi-play/sdk/avatars: the avatars' rig @fabi-play/engine draws with. Not a game API.
2
+ // Generated by scripts/types.mjs from the sources: do not edit.
3
+
4
+ type Body = number[];
5
+ /**
6
+ * Age ranges: each has its own games. Only the range is kept, never a birth date: it is all the catalogue and the
7
+ * games need (simpler rules and no losing for the little ones, scores and challenges for the older ones).
8
+ */
9
+ export type AgeRange = "3-5" | "6-8" | "9-12";
10
+ /**
11
+ * A tiny shape list, the one source of truth for every avatar picture. The rig and the portrait build a list of
12
+ * shapes; two small backends draw it: on a 2D canvas (the TV, the games) and as an SVG string (the phones, the
13
+ * previews). Both read the same numbers, so the canvas and the SVG pictures match.
14
+ *
15
+ * Paths use a small subset of SVG path syntax, absolute commands only (M L Q C Z), so the canvas backend can trace
16
+ * them without Path2D (not available everywhere, e.g. React Native canvases or test mocks). The rig builds them as
17
+ * PathData, number arrays (no strings built and parsed again every frame); fixed paths may stay strings.
18
+ */
19
+ /** How an avatar shape is painted (CSS colours). */
20
+ export interface Paint {
21
+ /** Fill colour; none without it. */
22
+ fill?: string;
23
+ /** Outline colour; none without it. */
24
+ stroke?: string;
25
+ /** Stroke width, in the units of the shape (scaled by the groups around it). */
26
+ lw?: number;
27
+ /** 0–1, multiplied with the alpha around it. */
28
+ alpha?: number;
29
+ }
30
+ /** One shape of an avatar picture (the rig's output), in canvas units; k says which. */
31
+ export type Shape = (Paint & {
32
+ k: "circle";
33
+ x: number;
34
+ y: number;
35
+ r: number;
36
+ }) | (Paint & {
37
+ k: "ellipse";
38
+ x: number;
39
+ y: number;
40
+ rx: number;
41
+ ry: number;
42
+ rot?: number;
43
+ })
44
+ /** A rounded rectangle by its centre, rotated by rot radians around it. */
45
+ | (Paint & {
46
+ k: "rect";
47
+ x: number;
48
+ y: number;
49
+ w: number;
50
+ h: number;
51
+ r: number;
52
+ rot?: number;
53
+ }) | (Paint & {
54
+ k: "path";
55
+ d: string | PathData;
56
+ })
57
+ /** A polyline [x0, y0, x1, y1, …], stroked with round caps and joins. */
58
+ | (Paint & {
59
+ k: "line";
60
+ pts: number[];
61
+ })
62
+ /** Children drawn translated by (x, y), then rotated by rot radians, then scaled by s. */
63
+ | {
64
+ k: "group";
65
+ x: number;
66
+ y: number;
67
+ rot?: number;
68
+ s?: number;
69
+ alpha?: number;
70
+ items: Shape[];
71
+ };
72
+ /**
73
+ * A path as numbers: each command code (PATH.M, …) followed by its coordinates, absolute: M x y, L x y,
74
+ * Q cx cy x y, C c1x c1y c2x c2y x y, Z. Coordinates are rounded to 2 decimals, as the SVG prints them, so canvas and
75
+ * SVG pictures match.
76
+ */
77
+ export type PathData = readonly number[];
78
+ /** The part of CanvasRenderingContext2D the backend uses (so tests can pass a mock). */
79
+ export type Ctx2D = Pick<CanvasRenderingContext2D, "save" | "restore" | "translate" | "rotate" | "scale" | "beginPath" | "closePath" | "moveTo" | "lineTo" | "quadraticCurveTo" | "bezierCurveTo" | "arc" | "ellipse" | "fill" | "stroke" | "rect" | "clip" | "globalAlpha" | "fillStyle" | "strokeStyle" | "lineWidth" | "lineCap" | "lineJoin">;
80
+ export declare function drawShapes(ctx: Ctx2D, shapes: readonly Shape[]): void;
81
+ export interface Pt {
82
+ x: number;
83
+ y: number;
84
+ }
85
+ /** An avatar's colours (CSS). */
86
+ export interface Palette {
87
+ /** Head and torso. */
88
+ body: string;
89
+ /** Arms and legs. */
90
+ limbs: string;
91
+ /** Belly patch, muzzle, inner face. */
92
+ belly: string;
93
+ /** Details: inner ears, horns, beaks, tail tips, spikes. */
94
+ accent: string;
95
+ /** The outline around every part: dark, so the avatar reads on light and dark backgrounds. */
96
+ outline: string;
97
+ /** Hands; defaults to limbs. */
98
+ hands?: string;
99
+ /** Feet; defaults to limbs. */
100
+ feet?: string;
101
+ /** Solid ears of another colour (panda); by default ears are body-coloured with an accent inside. */
102
+ ears?: string;
103
+ /** The face and head when they differ from the torso (people, the lamb's face); defaults to body. */
104
+ skin?: string;
105
+ /** Hair (hair, curls, locks). */
106
+ hair?: string;
107
+ }
108
+ /** The head's outline. */
109
+ export type HeadShape = "round" | "wide" | "tall" | "square" | "bulb" | "bucket";
110
+ /** The ears (or what is in their place: horns, antennae, a fin). */
111
+ export type EarKind = "none" | "pointy" | "round" | "big-round" | "long" | "side" | "tufts" | "antenna" | "antennae" | "horns" | "unicorn" | "floppy" | "droopy" | "fin" | "human";
112
+ /** How the eyes are drawn. */
113
+ export type EyeKind = "dot" | "big" | "mask" | "screen" | "almond" | "visor" | "calm" | "slit" | "goggle" | "bead";
114
+ /** How the mouth (or beak) is drawn. */
115
+ export type MouthKind = "none" | "smile" | "open" | "cat" | "beak" | "teeth" | "grill" | "tiny" | "smirk" | "bill" | "hook" | "jaws";
116
+ /** The patch around the nose and mouth. */
117
+ export type MuzzleKind = "none" | "oval" | "heart" | "monkey" | "disc" | "cheeks" | "band";
118
+ /** The tail, behind the body. */
119
+ export type TailKind = "none" | "fluffy" | "brush" | "pom" | "long" | "curl" | "tuft" | "striped" | "spiky" | "arrow" | "fish" | "plume";
120
+ /** Things on the head. */
121
+ export type HeadExtra = "mane" | "rainbow-mane" | "stripes" | "spikes" | "spots" | "curl" | "helmet" | "bolts" | "whiskers" | "tuft" | "brows" | "eggshell" | "wool" | "eye-spot" | "crest" | "forehead" | "gills" | "ruff" | "hair" | "curls" | "locks" | "ninja-hood" | "cap" | "safari-hat" | "wizard-hat" | "plume" | "stripe" | "bandana" | "eye-patch" | "dive-mask" | "domino";
122
+ /** Things on the body. */
123
+ export type BodyExtra = "wings" | "backpack" | "sheet" | "tentacles" | "panel" | "badge" | "joints" | "feathers" | "flippers" | "boots" | "scarf" | "spots" | "wool" | "robe" | "cape" | "fins" | "tank" | "straps" | "belt" | "stripes" | "pauldrons" | "emblem" | "pocket" | "racing-stripe" | "star";
124
+ /** An avatar's face. */
125
+ export interface FaceDef {
126
+ /** How the eyes are drawn. */
127
+ eyes: EyeKind;
128
+ /** Eye colour for big/screen/almond/visor eyes. */
129
+ eyeColor?: string;
130
+ /** Eyes higher than usual and bulging over the head (frog). */
131
+ eyesOnTop?: boolean;
132
+ /** Ring around the eyes (owl). */
133
+ eyeRing?: string;
134
+ /** How the mouth is drawn. */
135
+ mouth: MouthKind;
136
+ /** Nose colour; none when missing. */
137
+ nose?: string;
138
+ /** The patch around nose and mouth. */
139
+ muzzle: MuzzleKind;
140
+ /** Blush colour; none when missing. */
141
+ cheeks?: string;
142
+ /** Eyebrows of this colour (people): a little attitude. */
143
+ brows?: string;
144
+ }
145
+ /** The age band an avatar is for, or 'all': offered in every band (Fabi the fox, the app's mascot). */
146
+ export type AvatarBand = AgeRange | "all";
147
+ /** An avatar, as data: one renderer draws any definition. */
148
+ export interface AvatarDef {
149
+ /** Stable kebab-case id: stored on the phone and sent around, never renamed. */
150
+ id: string;
151
+ /** Its name, in English and Italian, for pickers. */
152
+ name: {
153
+ en: string;
154
+ it: string;
155
+ };
156
+ /**
157
+ * The age band the avatar is for: its style and its body proportions (see BAND_BODY in rig.ts). 'all': in every
158
+ * band's list, drawn with the 6–8 proportions.
159
+ */
160
+ band: AvatarBand;
161
+ /** A special avatar (the mascot): first in every band's list; pickers may mark it. */
162
+ special?: boolean;
163
+ /** Its colours. */
164
+ palette: Palette;
165
+ /** The head's outline. */
166
+ head: HeadShape;
167
+ /** The ears, or what is in their place. */
168
+ ears: EarKind;
169
+ /** The face. */
170
+ face: FaceDef;
171
+ /** The tail. */
172
+ tail: TailKind;
173
+ /** Things on the head (manes, hats, masks). */
174
+ headExtras: readonly HeadExtra[];
175
+ /** Things on the body (wings, capes, belts). */
176
+ bodyExtras: readonly BodyExtra[];
177
+ /** Colours of the rainbow mane, of the stripes, of the spots: per-avatar extra colours. */
178
+ extraColors?: readonly string[];
179
+ }
180
+ /** Where player space (0–1) lands on the canvas. */
181
+ export interface Box {
182
+ /** Left edge, canvas units. */
183
+ x: number;
184
+ /** Top edge, canvas units. */
185
+ y: number;
186
+ /** Width, canvas units. */
187
+ w: number;
188
+ /** Height, canvas units. */
189
+ h: number;
190
+ }
191
+ /**
192
+ * Body proportions by age band, in torso units U (head, limbs, torso) unless said otherwise. The joints are always
193
+ * the child's: limbs run through them and the head sits on the shoulders; only sizes change.
194
+ *
195
+ * 3–5: a big round head, chubby limbs, a round tummy that reaches low (the legs look short): toddlers.
196
+ * 6–8: the original proportions.
197
+ * 9–12: a smaller head, slimmer limbs and hips, a squarer torso: older kids.
198
+ */
199
+ export interface BandBody {
200
+ /** Head radius. */
201
+ head: number;
202
+ /** How far the head centre sits above the shoulder line, in head radii. */
203
+ neck: number;
204
+ /** Limb thickness. */
205
+ arm: number;
206
+ leg: number;
207
+ /** Hand radius. */
208
+ hand: number;
209
+ /** Feet size, as a factor. */
210
+ foot: number;
211
+ /** The torso: at least this half width at the shoulders and the hips, and this much past the joints. */
212
+ shoulderMin: number;
213
+ shoulderPad: number;
214
+ hipMin: number;
215
+ hipPad: number;
216
+ /** How far the torso reaches below the hips. */
217
+ bottom: number;
218
+ /** The waist's bulge, as a share of the wider of shoulders and hips. */
219
+ bulge: number;
220
+ /** A raised arm is drawn over the head (a big head would hide it). */
221
+ armsOverHead: boolean;
222
+ }
223
+ export declare const BAND_BODY: Record<AgeRange, BandBody>;
224
+ export interface RigOptions {
225
+ mirrored?: boolean;
226
+ /** Portraits leave the tail out: in a head-and-shoulders crop it would only peek in as a stray shape. */
227
+ portrait?: boolean;
228
+ /** Body proportions (see BAND_BODY); 6–8 by default. avatarShapes takes the avatar's own band. */
229
+ band?: AgeRange;
230
+ }
231
+ /** The torso frame: origin between the shoulders, r to the right, d down along the torso; lengths in pixels. */
232
+ export interface Frame {
233
+ S: Pt;
234
+ r: Pt;
235
+ d: Pt;
236
+ U: number;
237
+ /** Shoulders to hips along d. */
238
+ L: number;
239
+ hipsOk: boolean;
240
+ head: Pt;
241
+ headR: number;
242
+ headRot: number;
243
+ angle: number;
244
+ }
245
+ /** The torso frame and head placement, or null when the shoulders are not seen. */
246
+ export declare function frameOf(body: Body, box: Box, opts?: RigOptions): Frame | null;
247
+ /** All the shapes of an avatar for this body, in canvas pixels. Empty when the shoulders are not seen. */
248
+ export declare function avatarShapes(body: Body, def: AvatarDef, box: Box, opts?: RigOptions): Shape[];
249
+ /** The avatar of a child who has not picked one yet: the fox (6–8's first). */
250
+ export declare const DEFAULT_AVATAR: AvatarDef;
251
+ /** The avatar with this id, or undefined. */
252
+ export declare const avatarById: (id: string) => AvatarDef | undefined;
253
+ /** The portrait: head and shoulders of the neutral pose, framed in a size × size square at (0, 0). */
254
+ export declare function iconShapes(def: AvatarDef | string, size: number): Shape[];
255
+
256
+ export {};
@@ -0,0 +1,18 @@
1
+ import {
2
+ BAND_BODY,
3
+ DEFAULT_AVATAR,
4
+ avatarById,
5
+ avatarShapes,
6
+ drawShapes,
7
+ frameOf,
8
+ iconShapes
9
+ } from "./chunks/chunk-57WXWAXG.js";
10
+ export {
11
+ BAND_BODY,
12
+ DEFAULT_AVATAR,
13
+ avatarById,
14
+ avatarShapes,
15
+ drawShapes,
16
+ frameOf,
17
+ iconShapes
18
+ };