@fabi-play/engine 0.4.0 → 0.7.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/README.md CHANGED
@@ -50,6 +50,20 @@ Load the SDK first, then the engine, each pinned with its SRI hash
50
50
 
51
51
  Docs: https://docs.fabiplay.app/engine/overview/.
52
52
 
53
+ ## Hands, orientation, safe area
54
+
55
+ `engine.hands()` (stage units), `engine.touched(node)` and the rings of `engine.handMarkers()` are where the hands
56
+ **aim**, with the protocol's `aimPoint`: on a TV or a tablet (landscape) on the avatar's hand, or on the real one over
57
+ the video, as before; on a phone held upright (`engine.orientation === 'portrait'`), where the camera image is small
58
+ at the bottom, the arms' reach around the shoulders stretched to the whole stage, so arms straight up touch near the
59
+ top (y ≈ 0.15 × 720) and arms out reach the sides, held for 300 ms when the camera loses a wrist. The avatars are still
60
+ drawn through the view (`engine.view`), small at the bottom. Games need no change; to draw on the body itself, use the
61
+ SDK's `toScreen(wrist)`.
62
+
63
+ `engine.safeArea` is what a phone's notch and home bar cover, in fractions of the viewport (`{ top, bottom, left,
64
+ right }`, each 0–0.25, zeros on a TV): keep the HUD and targets out of it, e.g. a score at
65
+ `y = engine.safeArea.top * 720 + 24`.
66
+
53
67
  ## Filters
54
68
 
55
69
  Any node can go through filters: colour filters, blur and drop shadows draw the same on WebGL and Canvas 2D; custom
@@ -86,6 +86,36 @@ declare namespace FabiEngine {
86
86
  /** Height, a fraction of the viewport's height. */
87
87
  h: number;
88
88
  }
89
+ /** A hand: the child's own left or right. */
90
+ export type HandSide = "left" | "right";
91
+ class AimHold {
92
+ readonly holdMs: number;
93
+ private readonly last;
94
+ constructor(holdMs?: number);
95
+ /** A number for a player's hand. */
96
+ static key(player: number, side: HandSide): number;
97
+ /** The aim to use at time t (ms) for the hand key, given what aimPoint said now (p, or null if lost). */
98
+ at(key: number, p: Point | null, t: number): Point | null;
99
+ /** Forgets every hand. */
100
+ clear(): void;
101
+ }
102
+ /**
103
+ * The parts of the game's viewport a phone's notch, rounded corners or home bar may cover, in fractions of the
104
+ * viewport (top and bottom of its height, left and right of its width; each 0–0.25). The game's frame cannot read
105
+ * env(safe-area-inset-*), so the screen sends it (init.safeArea): keep buttons, scores and targets out of it, and
106
+ * still paint the background under it. Zeros where nothing is covered (a TV, a tablet, older hosts).
107
+ */
108
+ export interface SafeArea {
109
+ top: number;
110
+ bottom: number;
111
+ left: number;
112
+ right: number;
113
+ }
114
+ /**
115
+ * A screen's orientation: 'landscape' when its width is at least its height, else 'portrait' (see orientationOf).
116
+ * GameManifest.orientations lists those a game supports; HostMessage init.orientation says the one it starts in.
117
+ */
118
+ export type Orientation = "landscape" | "portrait";
89
119
  /**
90
120
  * Small pure helpers: 2D affine matrices, colours, easing. No DOM here, so they run in Node tests.
91
121
  */
@@ -1339,11 +1369,18 @@ declare namespace FabiEngine {
1339
1369
  }[];
1340
1370
  /** A player's colour (CSS), as the TV draws them: player 1 orange, 2 blue, 3 green, 4 lilac, then again. */
1341
1371
  export const playerColor: (player: number) => "#EE6A43" | "#6FA0E0" | "#8CC9A0" | "#C49BE0";
1372
+ /** handsOf's aim: the screen's orientation and, in portrait, what keeps hands through short losses (and its clock, ms). */
1373
+ export interface HandsAim {
1374
+ orientation?: Orientation;
1375
+ hold?: AimHold;
1376
+ t?: number;
1377
+ }
1342
1378
  /**
1343
- * Every player's hands in stage units: wrists of bodyOf(frame) (null: the frame's own body), through toScreen
1344
- * (player space to screen fractions), times the stage size.
1379
+ * Every player's hands in stage units, where they aim (the protocol's aimPoint), times the stage size; bodyOf(frame)
1380
+ * is the body to read (null: the frame's own). Landscape (the default): its wrists through toScreen (player space to
1381
+ * screen fractions). Portrait (aim.orientation): the wrists stretched to the whole screen, held by aim.hold if given.
1345
1382
  */
1346
- export function handsOf(frames: readonly PlayerFrame[], toScreen: (p: Point) => Point, width: number, height: number, bodyOf?: (f: PlayerFrame) => Body | null): Hand[];
1383
+ export function handsOf(frames: readonly PlayerFrame[], toScreen: (p: Point) => Point, width: number, height: number, bodyOf?: (f: PlayerFrame) => Body | null, aim?: HandsAim): Hand[];
1347
1384
  /** What a node offers to hit tests: its world transform, size and anchor, or a radius. */
1348
1385
  export interface Hittable {
1349
1386
  /** The node's transform to the stage. */
@@ -1520,6 +1557,10 @@ declare namespace FabiEngine {
1520
1557
  readonly backdrop?: Backdrop;
1521
1558
  /** Where the camera image is on the screen, in fractions; null if unknown. */
1522
1559
  readonly view?: BackdropView | null;
1560
+ /** The screen's orientation at the start ('landscape' if unknown): in 'portrait' the hands aim (see Engine.hands). */
1561
+ readonly orientation?: Orientation;
1562
+ /** What a phone's notch and home bar cover, fractions of the viewport (see Engine.safeArea). */
1563
+ readonly safeArea?: SafeArea;
1523
1564
  /** The screen's sound is off. */
1524
1565
  readonly muted?: boolean;
1525
1566
  /** What the screen knows about the device (init.device): a starting tier and the display's rate. */
@@ -1629,6 +1670,18 @@ declare namespace FabiEngine {
1629
1670
  readonly backdrop: Backdrop;
1630
1671
  /** Where the camera image is on the screen, in fractions; null if the host didn't say. */
1631
1672
  readonly view: BackdropView | null;
1673
+ /**
1674
+ * The screen's orientation when the game started: 'landscape' (a TV, a tablet; also before the host says) or
1675
+ * 'portrait' (a phone held upright). It decides where hands() are (see there).
1676
+ */
1677
+ readonly orientation: Orientation;
1678
+ /**
1679
+ * What a phone's notch, rounded corners and home bar cover, in fractions of the viewport (top and bottom of its
1680
+ * height, left and right of its width; each 0–0.25; zeros on a TV, a tablet, and from hosts that don't say). The
1681
+ * game's frame can't read env(safe-area-inset-*): keep buttons, scores and targets out of it (in stage units, top
1682
+ * × 720, left × stage.width), still paint the background under it.
1683
+ */
1684
+ readonly safeArea: Readonly<SafeArea>;
1632
1685
  /** Paused by the host (pause, break) or by pause(): game time, tweens, particles and sound are stopped. */
1633
1686
  readonly paused: boolean;
1634
1687
  /** Sounds and tones, quiet while paused or muted. */
@@ -1715,9 +1768,16 @@ declare namespace FabiEngine {
1715
1768
  * bitmap fonts (.fnt, or a BMFont .json, + page images), sounds and fonts, by name.
1716
1769
  */
1717
1770
  load(assets: Record<string, string>): Promise<void>;
1718
- /** This frame's hands (of one player, or everyone's), stage units: in the avatar's hands, or on the real ones. */
1771
+ /**
1772
+ * This frame's hands (of one player, or everyone's), stage units, where they aim: what they touch, catch, press.
1773
+ * Landscape (a TV, a tablet): in the avatar's hands, or on the real ones over the video. Portrait (a phone held
1774
+ * upright, where the camera image is small at the bottom): the wrists stretched around the shoulders to the whole
1775
+ * stage (the protocol's aimPoint), so arms straight up reach the top (y ≈ 0.15 × 720) and arms out the sides, held
1776
+ * for 300 ms when the camera loses a wrist; the avatars are still drawn small in the view. To draw on the body
1777
+ * itself, use toScreen(wrist) of the SDK. touched() and handMarkers() use these hands.
1778
+ */
1719
1779
  hands(player?: number): Hand[];
1720
- /** The first hand (of one player, or anyone's) touching the node, or null. padding: stage units. */
1780
+ /** The first hand (of one player, or anyone's) touching the node, or null; the hands are hands()'s. padding: stage units. */
1721
1781
  touched(node: Node, player?: number, opts?: {
1722
1782
  padding?: number;
1723
1783
  }): Hand | null;
@@ -1927,7 +1987,7 @@ declare namespace FabiEngine {
1927
1987
  /** Eyebrows of this colour (people): a little attitude. */
1928
1988
  brows?: string;
1929
1989
  }
1930
- /** The age band an avatar is for, or 'all': offered in every band (Fabi the fox, the app's mascot). */
1990
+ /** The age band an avatar is for, or 'all': of no band (Fabi the fox, the app's mascot: the host, in no picker). */
1931
1991
  export type AvatarBand = AgeRange | "all";
1932
1992
  /** An avatar, as data: one renderer draws any definition. */
1933
1993
  export interface AvatarDef {
@@ -1940,7 +2000,7 @@ declare namespace FabiEngine {
1940
2000
  };
1941
2001
  /**
1942
2002
  * The age band the avatar is for, its home: its style and its body proportions (see BAND_BODY in rig.ts). 'all': in
1943
- * every band's list, drawn with the 6–8 proportions. See alsoIn for an avatar offered in other bands too.
2003
+ * no band's list (the mascot), drawn with the 6–8 proportions. See alsoIn for an avatar offered in other bands too.
1944
2004
  */
1945
2005
  band: AvatarBand;
1946
2006
  /**
@@ -1949,7 +2009,7 @@ declare namespace FabiEngine {
1949
2009
  * (outline), bandOf, and its proportions when the band shown is not known. Not for 'all' (the mascot).
1950
2010
  */
1951
2011
  alsoIn?: readonly AgeRange[];
1952
- /** A special avatar (the mascot): first in every band's list; pickers may mark it. */
2012
+ /** A special avatar (the mascot): the host, not a player; in no band's list (AVATAR_BANDS). */
1953
2013
  special?: boolean;
1954
2014
  /** Its colours. */
1955
2015
  palette: Palette;