@tesana/sdk 1.0.4 → 1.2.0-canary.39285723019.2

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
@@ -52,18 +52,80 @@ succeeding.
52
52
  | `tesana.shop` | In-game shop — `catalog` / `items` / `buy` / `entitlements` / `inventory` |
53
53
  | `tesana.economy` | Coins — `balance` / `packs` / `topUp` / `signIn` |
54
54
  | `tesana.multiplayer` | Open lobbies — `list` / `create` / `join` / `quickJoin` / `send` / `on` |
55
- | `tesana.player` | Identity — `login({ tesanaUserId })` |
55
+ | `tesana.player` | Identity — `me` / `isGuest` / `login()` (asks the host to sign in, like `economy.signIn`) |
56
56
  | `tesana.vanity` | The game's own address on tesana.ai |
57
57
  | `tesana.ads` | Ads, when the host allows them |
58
+ | `tesana.gameplay` | Progression events — `levelStart` / `levelComplete` / `death` / `checkpoint` / `score` |
59
+ | `tesana.track` | Any other gameplay event, by name |
60
+ | `tesana.feedback` | Player reports to the creator — `open` (host form) / `submit`; crashes are filed automatically |
61
+ | `tesana.config` | Values the creator tunes without a rebuild — `get(key, default)` / `getAll` / `ready` |
62
+ | `tesana.share` | Invite links — `share({ roomCode, levelId, text })` / `share.params()` |
58
63
 
59
64
  Leaderboards and achievements are client-reported, so they are a ranking to show
60
65
  off rather than a prize table; nothing here mints coins or grants entitlements.
61
66
  A counted achievement is decided by the server, not the game.
62
67
 
68
+ ### Gameplay analytics
69
+
70
+ `tesana.track` and `tesana.gameplay` report what happened in the game so its
71
+ creator can see where players get stuck and where they quit. They go to the
72
+ Tesana page above the game, which files them against the play session; there
73
+ is no key and no third-party provider.
74
+
75
+ ```js
76
+ tesana.gameplay.levelStart({ levelId: "forest", levelIndex: 1 });
77
+ tesana.gameplay.death({ levelId: "forest", x: player.x, y: player.y });
78
+ tesana.gameplay.checkpoint({ levelId: "forest", name: "bridge" });
79
+ tesana.gameplay.levelComplete({ levelId: "forest" });
80
+ tesana.gameplay.score({ value: 4200 });
81
+ tesana.track("powerup_picked", { kind: "triple_shot" });
82
+ ```
83
+
84
+ ```ts
85
+ type GameplayProps = {
86
+ levelId?: string | number; // the join key: the same value in every call for one level
87
+ levelIndex?: number; // 0-based position, when levels are ordered
88
+ x?: number; y?: number; z?: number; // where it happened (deaths draw the heatmap)
89
+ value?: number; // a score, a count
90
+ name?: string; // a death's cause, a checkpoint's name
91
+ [context: string]: string | number | boolean | null | undefined;
92
+ };
93
+
94
+ track(name: string, props?: GameplayProps): boolean;
95
+ gameplay.levelStart(props?: GameplayProps | string | number): boolean; // bare value = levelId
96
+ gameplay.levelComplete(props?: GameplayProps | string | number): boolean;
97
+ gameplay.death(props?: GameplayProps | string | number): boolean;
98
+ gameplay.checkpoint(props?: GameplayProps | string | number): boolean;
99
+ gameplay.score(props?: GameplayProps | number): boolean; // bare value = value
100
+ ```
101
+
102
+ - **Never throws, never async.** Each call answers `true` when the event was
103
+ handed on and `false` when it was dropped (a bad name, too many events, or no
104
+ Tesana page and no runtime shell to hand it to). Call it inline, unguarded.
105
+ - **No `init()` needed.** The same functions are on the global `Tesana`, so a
106
+ game can report its first level before `__TESANA_READY__` resolves.
107
+ - **Delivery.** The runtime shell's `window.__TESANA_EVENT__` when the page has
108
+ it, else `postMessage({ type: "tesana-gameplay-event", event })` to the parent.
109
+ `event` is the shape `POST /api/games/<id>/play/events/` stores: `type`
110
+ (`level_start`, `level_complete`, `death`, `checkpoint`, `score` or `custom`),
111
+ `name`, `level_id`, `level_index`, `x`, `y`, `z`, `value`, `payload`, `ts`.
112
+ - **`track` names.** A name that is one of the types above files the event as
113
+ that type; any other name is a `custom` event with that name.
114
+ - **Caps.** Names are cut to 64 characters. Up to 20 context keys are kept
115
+ (keys cut to 40 characters, strings to 64); objects, arrays, functions and
116
+ non-finite numbers are dropped, and keys stop being added before the payload
117
+ passes the endpoint's 2,000-character limit.
118
+ - **Rate.** At most 10 events in any one second and 1,000 per page load. Report
119
+ discrete moments — a level, a death, a run's end — never every frame.
120
+
63
121
  ### Offline behaviour
64
122
 
65
123
  Every call resolves rather than throwing when there is no host, no endpoint or
66
124
  no network. Saves read as empty, the shop reports `no_host`, and the game plays.
125
+ `config.get` answers the last session's value or the game's default, and
126
+ `feedback.open()` and `share()` resolve `{ ok: false, reason }`. Writes that
127
+ report something (`scores.submit`, `feedback.submit`) reject, so the game can say
128
+ it did not send.
67
129
  Real money is never the game's business — `topUp()` asks the host to run
68
130
  checkout, and the price shown is read from the service.
69
131