@remix-gg/three 0.1.1
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/dist/assets.d.ts +36 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +100 -0
- package/dist/audio.d.ts +42 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +150 -0
- package/dist/camera.d.ts +72 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +120 -0
- package/dist/collide.d.ts +111 -0
- package/dist/collide.d.ts.map +1 -0
- package/dist/collide.js +321 -0
- package/dist/forgiveness.d.ts +71 -0
- package/dist/forgiveness.d.ts.map +1 -0
- package/dist/forgiveness.js +85 -0
- package/dist/game.d.ts +69 -0
- package/dist/game.d.ts.map +1 -0
- package/dist/game.js +209 -0
- package/dist/hud/index.d.ts +72 -0
- package/dist/hud/index.d.ts.map +1 -0
- package/dist/hud/index.js +142 -0
- package/dist/hud/styles.d.ts +14 -0
- package/dist/hud/styles.d.ts.map +1 -0
- package/dist/hud/styles.js +142 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/input/gestures.d.ts +124 -0
- package/dist/input/gestures.d.ts.map +1 -0
- package/dist/input/gestures.js +171 -0
- package/dist/input/index.d.ts +30 -0
- package/dist/input/index.d.ts.map +1 -0
- package/dist/input/index.js +121 -0
- package/dist/juice.d.ts +151 -0
- package/dist/juice.d.ts.map +1 -0
- package/dist/juice.js +237 -0
- package/dist/loop.d.ts +27 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +30 -0
- package/dist/platform/index.d.ts +50 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +177 -0
- package/dist/platform/sdk-contract.d.ts +113 -0
- package/dist/platform/sdk-contract.d.ts.map +1 -0
- package/dist/platform/sdk-contract.js +18 -0
- package/dist/ramp.d.ts +39 -0
- package/dist/ramp.d.ts.map +1 -0
- package/dist/ramp.js +24 -0
- package/dist/random.d.ts +128 -0
- package/dist/random.d.ts.map +1 -0
- package/dist/random.js +160 -0
- package/dist/scene/dispose.d.ts +46 -0
- package/dist/scene/dispose.d.ts.map +1 -0
- package/dist/scene/dispose.js +108 -0
- package/dist/scene/lighting.d.ts +42 -0
- package/dist/scene/lighting.d.ts.map +1 -0
- package/dist/scene/lighting.js +118 -0
- package/dist/scene/pool.d.ts +36 -0
- package/dist/scene/pool.d.ts.map +1 -0
- package/dist/scene/pool.js +70 -0
- package/dist/three.d.ts +2 -0
- package/dist/three.d.ts.map +1 -0
- package/dist/three.js +11 -0
- package/dist/viewport.d.ts +86 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +174 -0
- package/package.json +43 -0
package/dist/juice.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The curated set. Linear motion everywhere is the tell of an untweened game —
|
|
3
|
+
* real objects accelerate — but a menagerie of forty easings is choice with no
|
|
4
|
+
* information. These seven cover the moves portrait games make: `quadOut` for
|
|
5
|
+
* almost everything that moves on screen, `backOut` for pops that overshoot,
|
|
6
|
+
* `elasticOut` for the rare springy emphasis, the rest for symmetry and ramps.
|
|
7
|
+
*/
|
|
8
|
+
export const easings = {
|
|
9
|
+
linear: (t) => t,
|
|
10
|
+
quadIn: (t) => t * t,
|
|
11
|
+
quadOut: (t) => t * (2 - t),
|
|
12
|
+
quadInOut: (t) => (t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t)),
|
|
13
|
+
cubicOut: (t) => 1 + (t - 1) ** 3,
|
|
14
|
+
backOut: (t) => 1 + 2.70158 * (t - 1) ** 3 + 1.70158 * (t - 1) ** 2,
|
|
15
|
+
elasticOut: (t) => t === 0 || t === 1 ? t : 2 ** (-10 * t) * Math.sin((t * 10 - 0.75) * ((2 * Math.PI) / 3)) + 1,
|
|
16
|
+
};
|
|
17
|
+
/** Absorbs accumulated step error in "has this timer elapsed" comparisons. */
|
|
18
|
+
const EPSILON = 1e-9;
|
|
19
|
+
const resolveEasing = (easing) => {
|
|
20
|
+
if (easing === undefined)
|
|
21
|
+
return easings.quadOut;
|
|
22
|
+
if (typeof easing === 'function')
|
|
23
|
+
return easing;
|
|
24
|
+
const found = easings[easing];
|
|
25
|
+
if (!found)
|
|
26
|
+
throw new Error(`unknown easing '${String(easing)}'`);
|
|
27
|
+
return found;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A tween runner stepped by the game's own fixed timestep.
|
|
31
|
+
*
|
|
32
|
+
* Stepped, not clocked: it advances only when `update(step)` is called, so
|
|
33
|
+
* tweens pause with the simulation (including during hit-stop, which is the
|
|
34
|
+
* behaviour that reads as impact), run identically on 60 Hz and 120 Hz
|
|
35
|
+
* displays, and are deterministic under test.
|
|
36
|
+
*
|
|
37
|
+
* Create it once at module scope, call `tweens.update(step)` first thing in
|
|
38
|
+
* the game's update, and `tweens.cancelAll()` from the run reset — a tween
|
|
39
|
+
* from the previous run landing on a freshly reset object is a classic ghost.
|
|
40
|
+
*/
|
|
41
|
+
export function createTweens() {
|
|
42
|
+
let live = [];
|
|
43
|
+
return {
|
|
44
|
+
to(target, to, options) {
|
|
45
|
+
if (!(options.duration >= 0)) {
|
|
46
|
+
throw new Error(`tween duration must be >= 0, got ${options.duration}`);
|
|
47
|
+
}
|
|
48
|
+
const record = target;
|
|
49
|
+
const keys = Object.keys(to);
|
|
50
|
+
const tween = {
|
|
51
|
+
target: record,
|
|
52
|
+
keys,
|
|
53
|
+
from: keys.map((key) => {
|
|
54
|
+
const value = record[key];
|
|
55
|
+
if (typeof value !== 'number') {
|
|
56
|
+
throw new Error(`tween target property '${key}' is not a number`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}),
|
|
60
|
+
to: keys.map((key) => to[key]),
|
|
61
|
+
easing: resolveEasing(options.easing),
|
|
62
|
+
duration: options.duration,
|
|
63
|
+
yoyo: options.yoyo ?? false,
|
|
64
|
+
elapsed: 0,
|
|
65
|
+
cancelled: false,
|
|
66
|
+
onUpdate: options.onUpdate,
|
|
67
|
+
onComplete: options.onComplete,
|
|
68
|
+
};
|
|
69
|
+
live.push(tween);
|
|
70
|
+
return {
|
|
71
|
+
cancel() {
|
|
72
|
+
tween.cancelled = true;
|
|
73
|
+
},
|
|
74
|
+
get done() {
|
|
75
|
+
return tween.cancelled || tween.elapsed + EPSILON >= tween.duration * (tween.yoyo ? 2 : 1);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
update(step) {
|
|
80
|
+
if (live.length === 0)
|
|
81
|
+
return;
|
|
82
|
+
const finished = [];
|
|
83
|
+
for (const tween of live) {
|
|
84
|
+
if (tween.cancelled)
|
|
85
|
+
continue;
|
|
86
|
+
tween.elapsed += step;
|
|
87
|
+
const total = tween.duration * (tween.yoyo ? 2 : 1);
|
|
88
|
+
// The epsilon absorbs accumulated float error: thirty 1/60 steps sum
|
|
89
|
+
// to a hair under 0.5, and without it every tween runs one extra frame.
|
|
90
|
+
const landed = tween.elapsed + EPSILON >= total;
|
|
91
|
+
if (landed) {
|
|
92
|
+
// Snap to the exact destination rather than trusting easing(1) —
|
|
93
|
+
// curves like backOut miss their endpoint by float noise, and a
|
|
94
|
+
// scale meant to return to 1 must be 1, not 1 ± 2e-16, forever.
|
|
95
|
+
const finals = tween.yoyo ? tween.from : tween.to;
|
|
96
|
+
for (let i = 0; i < tween.keys.length; i++) {
|
|
97
|
+
tween.target[tween.keys[i]] = finals[i];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const t = tween.elapsed / total;
|
|
102
|
+
// The yoyo leg mirrors progress; easing applies per leg so the
|
|
103
|
+
// return trip decelerates the same way the outbound one did.
|
|
104
|
+
const leg = tween.yoyo ? 1 - Math.abs(1 - 2 * t) : t;
|
|
105
|
+
const eased = tween.easing(leg);
|
|
106
|
+
for (let i = 0; i < tween.keys.length; i++) {
|
|
107
|
+
const from = tween.from[i];
|
|
108
|
+
tween.target[tween.keys[i]] = from + (tween.to[i] - from) * eased;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
tween.onUpdate?.();
|
|
112
|
+
if (landed)
|
|
113
|
+
finished.push(tween);
|
|
114
|
+
}
|
|
115
|
+
if (finished.length > 0 || live.some((tween) => tween.cancelled)) {
|
|
116
|
+
live = live.filter((tween) => !tween.cancelled && !finished.includes(tween));
|
|
117
|
+
// Callbacks run after the list is settled, so an onComplete that starts
|
|
118
|
+
// the next tween (a squash chaining into a stretch) is not swept away.
|
|
119
|
+
for (const tween of finished)
|
|
120
|
+
tween.onComplete?.();
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
cancelAll() {
|
|
124
|
+
for (const tween of live)
|
|
125
|
+
tween.cancelled = true;
|
|
126
|
+
live = [];
|
|
127
|
+
},
|
|
128
|
+
get active() {
|
|
129
|
+
return live.filter((tween) => !tween.cancelled).length;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Wraps any camera rig with trauma-based screen shake.
|
|
135
|
+
*
|
|
136
|
+
* A decorator, because ownership is the entire problem: rigs own the camera
|
|
137
|
+
* transform, and `followRig` rewrites it every frame while `portraitRig` never
|
|
138
|
+
* touches it after boot. Adding an offset directly therefore either vanishes
|
|
139
|
+
* next frame or accumulates forever, depending on which rig the game picked.
|
|
140
|
+
* The wrapper removes last frame's offset, lets the inner rig do its work,
|
|
141
|
+
* then applies a fresh one — correct against both behaviours.
|
|
142
|
+
*
|
|
143
|
+
* Trauma-squared with linear decay (Squirrel Eiserloh's model): shake feels
|
|
144
|
+
* proportional to events this way, and it composes — two quick hits raise
|
|
145
|
+
* trauma additively instead of restarting a fixed animation.
|
|
146
|
+
*
|
|
147
|
+
* Shake runs on render time (`rig.update(dt)`), so it keeps moving during
|
|
148
|
+
* hit-stop and after `gameOver` — a dead-still camera over a game-over sheet
|
|
149
|
+
* reads as a hang, and shake-through-freeze is the classic impact combo.
|
|
150
|
+
*
|
|
151
|
+
* const rig = shakeRig(portraitRig())
|
|
152
|
+
* await createGame({ camera: rig, ... })
|
|
153
|
+
* rig.shake(0.4)
|
|
154
|
+
*/
|
|
155
|
+
export function shakeRig(rig, options = {}) {
|
|
156
|
+
const maxOffset = options.maxOffset ?? 0.35;
|
|
157
|
+
const maxRoll = options.maxRoll ?? 0.04;
|
|
158
|
+
const decay = options.decay ?? 1.5;
|
|
159
|
+
const rng = options.rng ?? Math.random;
|
|
160
|
+
let trauma = 0;
|
|
161
|
+
let appliedX = 0;
|
|
162
|
+
let appliedY = 0;
|
|
163
|
+
let appliedRoll = 0;
|
|
164
|
+
// Inverse in reverse order: the roll changed the local axes the offsets
|
|
165
|
+
// were applied along, so it unwinds first or the translation comes back
|
|
166
|
+
// along the wrong basis and the camera drifts a little every frame.
|
|
167
|
+
const remove = () => {
|
|
168
|
+
rig.camera.rotateZ(-appliedRoll);
|
|
169
|
+
rig.camera.translateY(-appliedY);
|
|
170
|
+
rig.camera.translateX(-appliedX);
|
|
171
|
+
appliedX = 0;
|
|
172
|
+
appliedY = 0;
|
|
173
|
+
appliedRoll = 0;
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
get camera() {
|
|
177
|
+
return rig.camera;
|
|
178
|
+
},
|
|
179
|
+
resize(viewport) {
|
|
180
|
+
rig.resize(viewport);
|
|
181
|
+
},
|
|
182
|
+
update(dt) {
|
|
183
|
+
remove();
|
|
184
|
+
rig.update(dt);
|
|
185
|
+
trauma = Math.max(trauma - decay * dt, 0);
|
|
186
|
+
const shake = trauma * trauma;
|
|
187
|
+
if (shake > 0) {
|
|
188
|
+
appliedX = maxOffset * shake * (rng() * 2 - 1);
|
|
189
|
+
appliedY = maxOffset * shake * (rng() * 2 - 1);
|
|
190
|
+
appliedRoll = maxRoll * shake * (rng() * 2 - 1);
|
|
191
|
+
rig.camera.translateX(appliedX);
|
|
192
|
+
rig.camera.translateY(appliedY);
|
|
193
|
+
rig.camera.rotateZ(appliedRoll);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
shake(amount) {
|
|
197
|
+
trauma = Math.min(trauma + Math.max(amount, 0), 1);
|
|
198
|
+
},
|
|
199
|
+
get trauma() {
|
|
200
|
+
return trauma;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Hit-stop: the few-frame freeze that sells an impact.
|
|
206
|
+
*
|
|
207
|
+
* It cannot be a sleep and cannot skip rendering — the loop keeps drawing the
|
|
208
|
+
* frozen state, which is exactly the effect. What stops is simulation, and the
|
|
209
|
+
* game owns its update, so this is a countdown the game gates on rather than
|
|
210
|
+
* something the SDK can impose.
|
|
211
|
+
*
|
|
212
|
+
* Keep it short and rare: 0.05–0.12s, on the few biggest moments (a death, a
|
|
213
|
+
* heavy hit). On every minor event it stops reading as impact and starts
|
|
214
|
+
* reading as jank. Screen shake keeps moving through it (see `shakeRig`) —
|
|
215
|
+
* freeze-plus-shake is the combination that reads as force.
|
|
216
|
+
*/
|
|
217
|
+
export function createHitStop() {
|
|
218
|
+
let remaining = 0;
|
|
219
|
+
return {
|
|
220
|
+
trigger(seconds) {
|
|
221
|
+
remaining = Math.max(remaining, seconds);
|
|
222
|
+
},
|
|
223
|
+
frozen(step) {
|
|
224
|
+
// The epsilon keeps three 1/60 steps from leaving 5e-18s of freeze that
|
|
225
|
+
// buys a whole extra frozen frame.
|
|
226
|
+
if (remaining <= EPSILON) {
|
|
227
|
+
remaining = 0;
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
remaining -= step;
|
|
231
|
+
return true;
|
|
232
|
+
},
|
|
233
|
+
get active() {
|
|
234
|
+
return remaining > EPSILON;
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
package/dist/loop.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure fixed-timestep accumulator. Extracted from the render loop so the timing
|
|
3
|
+
* rules are unit-testable with zero DOM and zero WebGL.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A frame gap longer than this is a stall, not gameplay: a backgrounded tab, a
|
|
7
|
+
* GC pause, or a paused debugger. Clamping it is what stops the game from
|
|
8
|
+
* burst-simulating minutes of physics in a single frame when the player comes
|
|
9
|
+
* back.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_FRAME_DELTA = 0.25;
|
|
12
|
+
export type StepResult = {
|
|
13
|
+
/** How many fixed `update(step)` calls this frame owes. */
|
|
14
|
+
steps: number;
|
|
15
|
+
/** Sub-step remainder in 0..1, for interpolating the render between states. */
|
|
16
|
+
alpha: number;
|
|
17
|
+
/** Carry-over to feed into the next call. */
|
|
18
|
+
accumulator: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* @param accumulator seconds carried over from the previous frame
|
|
22
|
+
* @param dt seconds of wall clock since the previous frame
|
|
23
|
+
* @param step fixed simulation step in seconds (1/60)
|
|
24
|
+
* @param maxSteps hard ceiling on steps per frame — the spiral-of-death guard
|
|
25
|
+
*/
|
|
26
|
+
export declare function stepClock(accumulator: number, dt: number, step: number, maxSteps: number): StepResult;
|
|
27
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,OAAO,CAAA;AAEnC,MAAM,MAAM,UAAU,GAAG;IACvB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,UAAU,CAcZ"}
|
package/dist/loop.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure fixed-timestep accumulator. Extracted from the render loop so the timing
|
|
3
|
+
* rules are unit-testable with zero DOM and zero WebGL.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A frame gap longer than this is a stall, not gameplay: a backgrounded tab, a
|
|
7
|
+
* GC pause, or a paused debugger. Clamping it is what stops the game from
|
|
8
|
+
* burst-simulating minutes of physics in a single frame when the player comes
|
|
9
|
+
* back.
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_FRAME_DELTA = 0.25;
|
|
12
|
+
/**
|
|
13
|
+
* @param accumulator seconds carried over from the previous frame
|
|
14
|
+
* @param dt seconds of wall clock since the previous frame
|
|
15
|
+
* @param step fixed simulation step in seconds (1/60)
|
|
16
|
+
* @param maxSteps hard ceiling on steps per frame — the spiral-of-death guard
|
|
17
|
+
*/
|
|
18
|
+
export function stepClock(accumulator, dt, step, maxSteps) {
|
|
19
|
+
if (!(step > 0))
|
|
20
|
+
throw new Error(`stepClock: step must be > 0, got ${step}`);
|
|
21
|
+
const total = accumulator + Math.min(Math.max(dt, 0), MAX_FRAME_DELTA);
|
|
22
|
+
const steps = Math.min(Math.floor(total / step), Math.max(0, Math.floor(maxSteps)));
|
|
23
|
+
// `total % step` serves both cases. In the normal case it equals
|
|
24
|
+
// `total - steps * step`. When we hit `maxSteps` it additionally DROPS the
|
|
25
|
+
// backlog we refused to simulate — keeping it would guarantee we hit the cap
|
|
26
|
+
// again next frame and never catch up (the spiral of death), while the
|
|
27
|
+
// remainder preserves sub-step phase so motion stays smooth.
|
|
28
|
+
const remainder = total % step;
|
|
29
|
+
return { steps, alpha: remainder / step, accumulator: remainder };
|
|
30
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type FarcadeSDKGlobal, type GameInfo, type GameState, type HapticFeedbackType, type Player, type SafeAreaInset, type ShopItem, type ViewContext } from './sdk-contract.js';
|
|
2
|
+
export type Platform = {
|
|
3
|
+
readonly info: GameInfo | null;
|
|
4
|
+
readonly player: Player | null;
|
|
5
|
+
readonly players: readonly Player[];
|
|
6
|
+
readonly viewContext: ViewContext;
|
|
7
|
+
readonly safeArea: SafeAreaInset;
|
|
8
|
+
readonly state: GameState | null;
|
|
9
|
+
readonly shopItems: readonly ShopItem[];
|
|
10
|
+
/** True once `gameOver` has been sent for the current run. */
|
|
11
|
+
readonly isOver: boolean;
|
|
12
|
+
ready(): Promise<GameInfo>;
|
|
13
|
+
gameOver(score: number): void;
|
|
14
|
+
/** Clears the one-`game_over`-per-run latch. Called by `game.restart()`. */
|
|
15
|
+
rearm(): void;
|
|
16
|
+
saveState(state: GameState): void;
|
|
17
|
+
onPlayAgain(cb: () => void): () => void;
|
|
18
|
+
onToggleMute(cb: (muted: boolean) => void): () => void;
|
|
19
|
+
onGameInfo(cb: (info: GameInfo) => void): () => void;
|
|
20
|
+
haptic(type?: HapticFeedbackType): void;
|
|
21
|
+
hasItem(slug: string): boolean;
|
|
22
|
+
itemCount(slug: string): number;
|
|
23
|
+
purchase(slug: string): Promise<{
|
|
24
|
+
success: boolean;
|
|
25
|
+
item?: string;
|
|
26
|
+
}>;
|
|
27
|
+
reportError(error: unknown): void;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolves `window.FarcadeSDK`, rAF-polling until it appears.
|
|
31
|
+
*
|
|
32
|
+
* The poll is not paranoia: remix-dev's dev-init installs its SDK mock
|
|
33
|
+
* asynchronously, so the global is genuinely absent at module-eval time in the
|
|
34
|
+
* dev dashboard. Resolves `null` past the deadline rather than rejecting — a
|
|
35
|
+
* game opened as a bare file has no host and must still boot.
|
|
36
|
+
*/
|
|
37
|
+
export declare function awaitSdk(timeoutMs?: number): Promise<FarcadeSDKGlobal | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Typed wrapper over `window.FarcadeSDK`.
|
|
40
|
+
*
|
|
41
|
+
* It owns the two things every game gets wrong on its own: exactly one
|
|
42
|
+
* `game_over` per run, and the fact that `game_info` (and therefore
|
|
43
|
+
* `contentSafeAreaInset`) arrives *after* the first frames.
|
|
44
|
+
*
|
|
45
|
+
* The wrapper registers exactly one callback per SDK event and fans out to its
|
|
46
|
+
* own listener sets, because the SDK's `onPlayAgain`/`onToggleMute` helpers wrap
|
|
47
|
+
* the callback in a closure and so cannot be unsubscribed.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createPlatform(sdk: FarcadeSDKGlobal | null): Platform;
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/platform/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,KAAK,SAAS,EAEd,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,WAAW,EAEjB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAA;IACvC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,4EAA4E;IAC5E,KAAK,IAAI,IAAI,CAAA;IACb,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IACvC,YAAY,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;IACtD,UAAU,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;IACpD,MAAM,CAAC,IAAI,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACpE,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;CAClC,CAAA;AAaD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAc3E;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,QAAQ,CAsHrE"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ZERO_SAFE_AREA_INSET, } from './sdk-contract.js';
|
|
2
|
+
const STANDALONE_PLAYER = { id: 'local', name: 'Player', purchasedItems: [] };
|
|
3
|
+
const STANDALONE_INFO = {
|
|
4
|
+
players: [STANDALONE_PLAYER],
|
|
5
|
+
player: STANDALONE_PLAYER,
|
|
6
|
+
shopItems: [],
|
|
7
|
+
viewContext: 'full_screen',
|
|
8
|
+
contentSafeAreaInset: ZERO_SAFE_AREA_INSET,
|
|
9
|
+
initialGameState: null,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Resolves `window.FarcadeSDK`, rAF-polling until it appears.
|
|
13
|
+
*
|
|
14
|
+
* The poll is not paranoia: remix-dev's dev-init installs its SDK mock
|
|
15
|
+
* asynchronously, so the global is genuinely absent at module-eval time in the
|
|
16
|
+
* dev dashboard. Resolves `null` past the deadline rather than rejecting — a
|
|
17
|
+
* game opened as a bare file has no host and must still boot.
|
|
18
|
+
*/
|
|
19
|
+
export function awaitSdk(timeoutMs = 2000) {
|
|
20
|
+
const global = globalThis;
|
|
21
|
+
if (global.FarcadeSDK)
|
|
22
|
+
return Promise.resolve(global.FarcadeSDK);
|
|
23
|
+
if (typeof requestAnimationFrame !== 'function')
|
|
24
|
+
return Promise.resolve(null);
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const deadline = performance.now() + timeoutMs;
|
|
27
|
+
const poll = () => {
|
|
28
|
+
if (global.FarcadeSDK)
|
|
29
|
+
return resolve(global.FarcadeSDK);
|
|
30
|
+
if (performance.now() >= deadline)
|
|
31
|
+
return resolve(null);
|
|
32
|
+
requestAnimationFrame(poll);
|
|
33
|
+
};
|
|
34
|
+
requestAnimationFrame(poll);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function subscribe(set, cb) {
|
|
38
|
+
set.add(cb);
|
|
39
|
+
return () => {
|
|
40
|
+
set.delete(cb);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Typed wrapper over `window.FarcadeSDK`.
|
|
45
|
+
*
|
|
46
|
+
* It owns the two things every game gets wrong on its own: exactly one
|
|
47
|
+
* `game_over` per run, and the fact that `game_info` (and therefore
|
|
48
|
+
* `contentSafeAreaInset`) arrives *after* the first frames.
|
|
49
|
+
*
|
|
50
|
+
* The wrapper registers exactly one callback per SDK event and fans out to its
|
|
51
|
+
* own listener sets, because the SDK's `onPlayAgain`/`onToggleMute` helpers wrap
|
|
52
|
+
* the callback in a closure and so cannot be unsubscribed.
|
|
53
|
+
*/
|
|
54
|
+
export function createPlatform(sdk) {
|
|
55
|
+
const listeners = { playAgain: new Set(), toggleMute: new Set(), gameInfo: new Set() };
|
|
56
|
+
let info = null;
|
|
57
|
+
let state = null;
|
|
58
|
+
let over = false;
|
|
59
|
+
if (sdk) {
|
|
60
|
+
const handlePlayAgain = () => {
|
|
61
|
+
for (const cb of [...listeners.playAgain])
|
|
62
|
+
cb();
|
|
63
|
+
};
|
|
64
|
+
const handleToggleMute = (data) => {
|
|
65
|
+
for (const cb of [...listeners.toggleMute])
|
|
66
|
+
cb(data.isMuted);
|
|
67
|
+
};
|
|
68
|
+
const handleGameInfo = (next) => {
|
|
69
|
+
info = next;
|
|
70
|
+
if (next.initialGameState)
|
|
71
|
+
state = next.initialGameState.gameState;
|
|
72
|
+
for (const cb of [...listeners.gameInfo])
|
|
73
|
+
cb(next);
|
|
74
|
+
};
|
|
75
|
+
const handleGameStateUpdated = (data) => {
|
|
76
|
+
state = data ? data.gameState : null;
|
|
77
|
+
};
|
|
78
|
+
// remix-dev 1.8.1 implements the generic event API but predates these
|
|
79
|
+
// named helpers. Prefer the production helpers and fall back without making
|
|
80
|
+
// a freshly scaffolded game crash before its first frame.
|
|
81
|
+
if (typeof sdk.onPlayAgain === 'function')
|
|
82
|
+
sdk.onPlayAgain(handlePlayAgain);
|
|
83
|
+
else
|
|
84
|
+
sdk.on?.('play_again', handlePlayAgain);
|
|
85
|
+
if (typeof sdk.onToggleMute === 'function')
|
|
86
|
+
sdk.onToggleMute(handleToggleMute);
|
|
87
|
+
else
|
|
88
|
+
sdk.on?.('toggle_mute', handleToggleMute);
|
|
89
|
+
if (typeof sdk.onGameInfo === 'function')
|
|
90
|
+
sdk.onGameInfo(handleGameInfo);
|
|
91
|
+
else
|
|
92
|
+
sdk.on?.('game_info', handleGameInfo);
|
|
93
|
+
if (typeof sdk.onGameStateUpdated === 'function')
|
|
94
|
+
sdk.onGameStateUpdated(handleGameStateUpdated);
|
|
95
|
+
else
|
|
96
|
+
sdk.on?.('game_state_updated', handleGameStateUpdated);
|
|
97
|
+
}
|
|
98
|
+
const purchasedItems = () => info?.player.purchasedItems ?? [];
|
|
99
|
+
return {
|
|
100
|
+
get info() {
|
|
101
|
+
return info;
|
|
102
|
+
},
|
|
103
|
+
get player() {
|
|
104
|
+
return info?.player ?? null;
|
|
105
|
+
},
|
|
106
|
+
get players() {
|
|
107
|
+
return info?.players ?? [];
|
|
108
|
+
},
|
|
109
|
+
get viewContext() {
|
|
110
|
+
return info?.viewContext ?? 'full_screen';
|
|
111
|
+
},
|
|
112
|
+
get safeArea() {
|
|
113
|
+
return info?.contentSafeAreaInset ?? ZERO_SAFE_AREA_INSET;
|
|
114
|
+
},
|
|
115
|
+
get state() {
|
|
116
|
+
return state;
|
|
117
|
+
},
|
|
118
|
+
get shopItems() {
|
|
119
|
+
return info?.shopItems ?? [];
|
|
120
|
+
},
|
|
121
|
+
get isOver() {
|
|
122
|
+
return over;
|
|
123
|
+
},
|
|
124
|
+
async ready() {
|
|
125
|
+
if (!sdk) {
|
|
126
|
+
info = STANDALONE_INFO;
|
|
127
|
+
return STANDALONE_INFO;
|
|
128
|
+
}
|
|
129
|
+
const resolved = await sdk.ready();
|
|
130
|
+
info = resolved;
|
|
131
|
+
if (!state && resolved.initialGameState)
|
|
132
|
+
state = resolved.initialGameState.gameState;
|
|
133
|
+
return resolved;
|
|
134
|
+
},
|
|
135
|
+
gameOver(score) {
|
|
136
|
+
// Idempotent per run. The host scores the FIRST game_over it sees, so a
|
|
137
|
+
// second one from a death animation's callback would silently overwrite a
|
|
138
|
+
// real score with a stale one.
|
|
139
|
+
if (over)
|
|
140
|
+
return;
|
|
141
|
+
over = true;
|
|
142
|
+
sdk?.singlePlayer.actions.gameOver({ score });
|
|
143
|
+
},
|
|
144
|
+
rearm() {
|
|
145
|
+
over = false;
|
|
146
|
+
},
|
|
147
|
+
saveState(next) {
|
|
148
|
+
state = next;
|
|
149
|
+
sdk?.singlePlayer.actions.saveGameState({ gameState: next });
|
|
150
|
+
},
|
|
151
|
+
onPlayAgain: (cb) => subscribe(listeners.playAgain, cb),
|
|
152
|
+
onToggleMute: (cb) => subscribe(listeners.toggleMute, cb),
|
|
153
|
+
onGameInfo: (cb) => subscribe(listeners.gameInfo, cb),
|
|
154
|
+
haptic(type) {
|
|
155
|
+
const actions = sdk?.singlePlayer.actions;
|
|
156
|
+
if (actions?.hapticFeedback)
|
|
157
|
+
actions.hapticFeedback(type);
|
|
158
|
+
else
|
|
159
|
+
sdk?.hapticFeedback?.(type);
|
|
160
|
+
},
|
|
161
|
+
hasItem: (slug) => purchasedItems().includes(slug),
|
|
162
|
+
itemCount: (slug) => purchasedItems().filter((item) => item === slug).length,
|
|
163
|
+
async purchase(slug) {
|
|
164
|
+
if (!sdk)
|
|
165
|
+
return { success: false };
|
|
166
|
+
return sdk.purchase({ item: slug });
|
|
167
|
+
},
|
|
168
|
+
reportError(error) {
|
|
169
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
170
|
+
const actions = sdk?.singlePlayer.actions;
|
|
171
|
+
if (actions?.reportError)
|
|
172
|
+
actions.reportError({ message: err.message, error: err });
|
|
173
|
+
else
|
|
174
|
+
sdk?.reportError?.({ message: err.message, error: err });
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VENDORED copy of the public type surface of `@remix-gg/sdk`
|
|
3
|
+
* (`packages/game-sdk/src/index.ts`). Types only — no logic, no runtime import.
|
|
4
|
+
*
|
|
5
|
+
* Two reasons this is a copy rather than a dependency:
|
|
6
|
+
*
|
|
7
|
+
* 1. `apps/web/lib/format-game.ts` unconditionally injects a
|
|
8
|
+
* `<script src=".../@remix-gg/sdk@x/dist/index.min.js">` into every published
|
|
9
|
+
* game. A bundled second copy would run `RemixSDK`'s constructor twice — two
|
|
10
|
+
* `ready` postMessages and two listener sets — and the host handshake and the
|
|
11
|
+
* launch gate both key off exactly one.
|
|
12
|
+
* 2. Repo rule: public packages must not publish `workspace:` dependencies.
|
|
13
|
+
*
|
|
14
|
+
* Keep in sync by hand when `packages/game-sdk/src/index.ts` changes. The
|
|
15
|
+
* runtime we talk to is `window.FarcadeSDK`, so only the shapes we actually
|
|
16
|
+
* consume live here.
|
|
17
|
+
*/
|
|
18
|
+
export type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament';
|
|
19
|
+
export type SafeAreaInset = {
|
|
20
|
+
top: number;
|
|
21
|
+
right: number;
|
|
22
|
+
bottom: number;
|
|
23
|
+
left: number;
|
|
24
|
+
};
|
|
25
|
+
export declare const ZERO_SAFE_AREA_INSET: SafeAreaInset;
|
|
26
|
+
export type GameState = Record<string, unknown>;
|
|
27
|
+
export type Player = {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
purchasedItems: string[];
|
|
31
|
+
imageUrl?: string;
|
|
32
|
+
};
|
|
33
|
+
export type InventoryItem = {
|
|
34
|
+
slug: string;
|
|
35
|
+
quantity: number;
|
|
36
|
+
};
|
|
37
|
+
export type ShopItem = {
|
|
38
|
+
slug: string;
|
|
39
|
+
name: string;
|
|
40
|
+
itemType?: string;
|
|
41
|
+
bitsCost?: number | null;
|
|
42
|
+
description?: string | null;
|
|
43
|
+
iconUrl?: string | null;
|
|
44
|
+
tier?: number | null;
|
|
45
|
+
};
|
|
46
|
+
export type GameInfo = {
|
|
47
|
+
players: Player[];
|
|
48
|
+
player: Player;
|
|
49
|
+
shopItems?: ShopItem[];
|
|
50
|
+
viewContext: ViewContext;
|
|
51
|
+
contentSafeAreaInset: SafeAreaInset;
|
|
52
|
+
initialGameState: {
|
|
53
|
+
id: string;
|
|
54
|
+
gameState: GameState;
|
|
55
|
+
} | null;
|
|
56
|
+
};
|
|
57
|
+
export type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error';
|
|
58
|
+
export type ToggleMuteData = {
|
|
59
|
+
isMuted: boolean;
|
|
60
|
+
};
|
|
61
|
+
export type GameStateUpdatedData = {
|
|
62
|
+
id: string;
|
|
63
|
+
gameState: GameState;
|
|
64
|
+
} | null;
|
|
65
|
+
export type PurchaseCompleteData = {
|
|
66
|
+
success: boolean;
|
|
67
|
+
item?: string;
|
|
68
|
+
};
|
|
69
|
+
export type GameErrorData = {
|
|
70
|
+
message: string;
|
|
71
|
+
source?: string;
|
|
72
|
+
lineno?: number;
|
|
73
|
+
colno?: number;
|
|
74
|
+
error?: Error;
|
|
75
|
+
};
|
|
76
|
+
export type FarcadeSDKEventMap = {
|
|
77
|
+
play_again: Record<string, never>;
|
|
78
|
+
toggle_mute: ToggleMuteData;
|
|
79
|
+
game_info: GameInfo;
|
|
80
|
+
game_state_updated: GameStateUpdatedData;
|
|
81
|
+
};
|
|
82
|
+
/** The subset of `window.FarcadeSDK` this SDK actually calls. */
|
|
83
|
+
export type FarcadeSDKGlobal = {
|
|
84
|
+
ready: () => Promise<GameInfo>;
|
|
85
|
+
purchase: (data: {
|
|
86
|
+
item: string;
|
|
87
|
+
}) => Promise<PurchaseCompleteData>;
|
|
88
|
+
reportError?: (data: GameErrorData) => void;
|
|
89
|
+
hapticFeedback?: (type?: HapticFeedbackType) => void;
|
|
90
|
+
on?: <Event extends keyof FarcadeSDKEventMap>(event: Event, cb: (data: FarcadeSDKEventMap[Event]) => void) => void;
|
|
91
|
+
onPlayAgain?: (cb: () => void) => void;
|
|
92
|
+
onToggleMute?: (cb: (data: ToggleMuteData) => void) => void;
|
|
93
|
+
onGameInfo?: (cb: (data: GameInfo) => void) => void;
|
|
94
|
+
onGameStateUpdated?: (cb: (data: GameStateUpdatedData) => void) => void;
|
|
95
|
+
readonly gameInfo?: GameInfo;
|
|
96
|
+
readonly gameState?: GameState | null;
|
|
97
|
+
readonly shopItems: ShopItem[];
|
|
98
|
+
readonly players?: Player[];
|
|
99
|
+
readonly player?: Player;
|
|
100
|
+
singlePlayer: {
|
|
101
|
+
actions: {
|
|
102
|
+
gameOver: (data: {
|
|
103
|
+
score: number;
|
|
104
|
+
}) => void;
|
|
105
|
+
saveGameState: (data: {
|
|
106
|
+
gameState: GameState;
|
|
107
|
+
}) => void;
|
|
108
|
+
reportError?: (data: GameErrorData) => void;
|
|
109
|
+
hapticFeedback?: (type?: HapticFeedbackType) => void;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=sdk-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-contract.d.ts","sourceRoot":"","sources":["../../src/platform/sdk-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VENDORED copy of the public type surface of `@remix-gg/sdk`
|
|
3
|
+
* (`packages/game-sdk/src/index.ts`). Types only — no logic, no runtime import.
|
|
4
|
+
*
|
|
5
|
+
* Two reasons this is a copy rather than a dependency:
|
|
6
|
+
*
|
|
7
|
+
* 1. `apps/web/lib/format-game.ts` unconditionally injects a
|
|
8
|
+
* `<script src=".../@remix-gg/sdk@x/dist/index.min.js">` into every published
|
|
9
|
+
* game. A bundled second copy would run `RemixSDK`'s constructor twice — two
|
|
10
|
+
* `ready` postMessages and two listener sets — and the host handshake and the
|
|
11
|
+
* launch gate both key off exactly one.
|
|
12
|
+
* 2. Repo rule: public packages must not publish `workspace:` dependencies.
|
|
13
|
+
*
|
|
14
|
+
* Keep in sync by hand when `packages/game-sdk/src/index.ts` changes. The
|
|
15
|
+
* runtime we talk to is `window.FarcadeSDK`, so only the shapes we actually
|
|
16
|
+
* consume live here.
|
|
17
|
+
*/
|
|
18
|
+
export const ZERO_SAFE_AREA_INSET = { top: 0, right: 0, bottom: 0, left: 0 };
|