@flayerlabs/gamemode-client 0.1.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 +9 -0
- package/README.md +93 -0
- package/dist/embed.d.ts +45 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +329 -0
- package/dist/embed.js.map +1 -0
- package/dist/identity.d.ts +13 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +49 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/live.d.ts +59 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +526 -0
- package/dist/live.js.map +1 -0
- package/dist/mock.d.ts +55 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +216 -0
- package/dist/mock.js.map +1 -0
- package/dist/replay-market.d.ts +61 -0
- package/dist/replay-market.d.ts.map +1 -0
- package/dist/replay-market.js +99 -0
- package/dist/replay-market.js.map +1 -0
- package/dist/signal.d.ts +11 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +27 -0
- package/dist/signal.js.map +1 -0
- package/package.json +53 -0
- package/src/embed.ts +395 -0
- package/src/identity.ts +51 -0
- package/src/index.ts +172 -0
- package/src/live.ts +609 -0
- package/src/mock.ts +249 -0
- package/src/replay-market.ts +150 -0
- package/src/signal.ts +26 -0
package/dist/mock.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Round } from '@flayerlabs/gamemode-spec/round';
|
|
2
|
+
import { isRefusal } from '@flayerlabs/gamemode-spec';
|
|
3
|
+
import { isReactionId, } from '@flayerlabs/gamemode-spec/live';
|
|
4
|
+
import { Signal } from './signal.js';
|
|
5
|
+
import { CachedIdentityResolver } from './identity.js';
|
|
6
|
+
/** How often the mock looks for a deadline that has fallen due. */
|
|
7
|
+
const TICK_MS = 100;
|
|
8
|
+
/** Long enough to see a spinner, short enough not to be annoying while building. */
|
|
9
|
+
const FAKE_SIGN_MS = 150;
|
|
10
|
+
const FAKE_PENDING_MS = 250;
|
|
11
|
+
/** The gate's real hold window, so a countdown built against the mock is the one players see. */
|
|
12
|
+
const MOCK_HOLD_MS = 5 * 60_000;
|
|
13
|
+
export class MockRoom {
|
|
14
|
+
game;
|
|
15
|
+
round;
|
|
16
|
+
player;
|
|
17
|
+
weiPerPoint;
|
|
18
|
+
allowedReactions;
|
|
19
|
+
listeners = new Set();
|
|
20
|
+
reactionListeners = new Set();
|
|
21
|
+
timer;
|
|
22
|
+
spentWei = 0n;
|
|
23
|
+
heldWei = 0n;
|
|
24
|
+
/** Mirrors the gate's five-minute hold, so a game can build the countdown against the mock. */
|
|
25
|
+
holdExpiresAt = null;
|
|
26
|
+
buyProgress = null;
|
|
27
|
+
disposed = false;
|
|
28
|
+
connectionSignal = new Signal('connected');
|
|
29
|
+
launchSignal;
|
|
30
|
+
presenceSignal = new Signal({ connectedCount: 1 });
|
|
31
|
+
marketSignal;
|
|
32
|
+
stopMarket;
|
|
33
|
+
constructor(game, options) {
|
|
34
|
+
this.game = game;
|
|
35
|
+
const opensAt = Date.now() + (options.lobbyMs ?? 3_000);
|
|
36
|
+
this.player = options.player ?? '0xyou';
|
|
37
|
+
this.weiPerPoint = options.weiPerPoint ?? 10000000000000n;
|
|
38
|
+
this.allowedReactions = new Set(options.reactionIds ?? []);
|
|
39
|
+
if ([...this.allowedReactions].some((id) => !isReactionId(id))) {
|
|
40
|
+
throw new RangeError('reactionIds must contain only valid reaction identifiers');
|
|
41
|
+
}
|
|
42
|
+
this.round = Round.start(game, options.config, options.seed ?? 1, {
|
|
43
|
+
opensAt,
|
|
44
|
+
closesAt: opensAt + (options.roundMs ?? 120_000),
|
|
45
|
+
});
|
|
46
|
+
const launch = options.launch ?? {
|
|
47
|
+
roundId: 'mock',
|
|
48
|
+
poolId: 'mock',
|
|
49
|
+
coinAddress: null,
|
|
50
|
+
name: 'Mock coin',
|
|
51
|
+
symbol: 'MOCK',
|
|
52
|
+
imageUrl: null,
|
|
53
|
+
opensAt,
|
|
54
|
+
closesAt: opensAt + (options.roundMs ?? 120_000),
|
|
55
|
+
booksCloseAt: opensAt + (options.roundMs ?? 120_000),
|
|
56
|
+
};
|
|
57
|
+
this.launchSignal = new Signal(Object.freeze({ ...launch }));
|
|
58
|
+
this.marketSignal = new Signal(options.platform?.market?.current() ?? { status: 'unavailable', prices: [], trades: [], marketCapUsd: null });
|
|
59
|
+
this.stopMarket = options.platform?.market?.subscribe((state) => this.marketSignal.set(state)) ?? (() => { });
|
|
60
|
+
this.identity = new CachedIdentityResolver(options.platform?.identity);
|
|
61
|
+
this.round.send({ kind: 'join', player: this.player, seed: options.seed ?? 1, at: Date.now() });
|
|
62
|
+
this.publishEconomy();
|
|
63
|
+
this.timer = setInterval(() => this.tick(), TICK_MS);
|
|
64
|
+
}
|
|
65
|
+
tick() {
|
|
66
|
+
if (this.round.advanceTo(Date.now()) > 0)
|
|
67
|
+
this.emit();
|
|
68
|
+
}
|
|
69
|
+
emit() {
|
|
70
|
+
this.publishEconomy();
|
|
71
|
+
const snapshot = {
|
|
72
|
+
publicView: this.round.publicView(),
|
|
73
|
+
playerView: this.round.playerView(this.player),
|
|
74
|
+
};
|
|
75
|
+
for (const listener of this.listeners)
|
|
76
|
+
listener(snapshot);
|
|
77
|
+
}
|
|
78
|
+
subscribe(listener) {
|
|
79
|
+
this.listeners.add(listener);
|
|
80
|
+
// Immediately, so a game can render from its first frame without waiting for something to
|
|
81
|
+
// happen. A lobby that stays blank until the first tick looks broken.
|
|
82
|
+
listener({ publicView: this.round.publicView(), playerView: this.round.playerView(this.player) });
|
|
83
|
+
return () => this.listeners.delete(listener);
|
|
84
|
+
}
|
|
85
|
+
async send(action) {
|
|
86
|
+
// Parsed exactly as the server would, so an action the server would throw away is refused
|
|
87
|
+
// here too rather than working locally and failing live.
|
|
88
|
+
const parsed = this.game.parseAction(action);
|
|
89
|
+
if (parsed === null)
|
|
90
|
+
return { accepted: false, refuse: 'action.malformed' };
|
|
91
|
+
const result = this.round.send({ kind: 'action', player: this.player, action: parsed, at: Date.now() });
|
|
92
|
+
this.emit();
|
|
93
|
+
return isRefusal(result) ? { accepted: false, refuse: result.refuse } : { accepted: true };
|
|
94
|
+
}
|
|
95
|
+
now() {
|
|
96
|
+
// The mock IS the server, so its clock is the correct one by definition. A live room corrects
|
|
97
|
+
// for skew here instead.
|
|
98
|
+
return Date.now();
|
|
99
|
+
}
|
|
100
|
+
launch = {
|
|
101
|
+
current: () => this.launchSignal.current(),
|
|
102
|
+
subscribe: (listener) => this.launchSignal.subscribe(listener),
|
|
103
|
+
};
|
|
104
|
+
connection = {
|
|
105
|
+
current: () => this.connectionSignal.current(),
|
|
106
|
+
subscribe: (listener) => this.connectionSignal.subscribe(listener),
|
|
107
|
+
};
|
|
108
|
+
market = {
|
|
109
|
+
current: () => this.marketSignal.current(),
|
|
110
|
+
subscribe: (listener) => this.marketSignal.subscribe(listener),
|
|
111
|
+
};
|
|
112
|
+
identity;
|
|
113
|
+
presence = {
|
|
114
|
+
current: () => this.presenceSignal.current(),
|
|
115
|
+
subscribe: (listener) => this.presenceSignal.subscribe(listener),
|
|
116
|
+
};
|
|
117
|
+
economyState() {
|
|
118
|
+
const earnedWei = BigInt(this.round.pointsFor(this.player)) * this.weiPerPoint;
|
|
119
|
+
const availableWei = earnedWei - this.spentWei - this.heldWei;
|
|
120
|
+
return {
|
|
121
|
+
weiPerPoint: this.weiPerPoint,
|
|
122
|
+
earnedWei,
|
|
123
|
+
heldWei: this.heldWei,
|
|
124
|
+
spentWei: this.spentWei,
|
|
125
|
+
availableWei: availableWei > 0n ? availableWei : 0n,
|
|
126
|
+
holdExpiresAt: this.heldWei > 0n ? this.holdExpiresAt : null,
|
|
127
|
+
buy: this.buyProgress,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
economySignal = new Signal({
|
|
131
|
+
weiPerPoint: 0n,
|
|
132
|
+
earnedWei: 0n,
|
|
133
|
+
heldWei: 0n,
|
|
134
|
+
spentWei: 0n,
|
|
135
|
+
availableWei: 0n,
|
|
136
|
+
holdExpiresAt: null,
|
|
137
|
+
buy: null,
|
|
138
|
+
});
|
|
139
|
+
publishEconomy() {
|
|
140
|
+
this.economySignal.set(this.economyState());
|
|
141
|
+
}
|
|
142
|
+
economy = {
|
|
143
|
+
current: () => this.economyState(),
|
|
144
|
+
subscribe: (listener) => this.economySignal.subscribe(listener),
|
|
145
|
+
available: () => this.economyState().availableWei,
|
|
146
|
+
buy: async (maxSpendWei) => {
|
|
147
|
+
if (maxSpendWei <= 0n) {
|
|
148
|
+
this.buyProgress = { state: 'failed', reason: 'nothing-to-spend' };
|
|
149
|
+
this.publishEconomy();
|
|
150
|
+
return { bought: false, reason: 'nothing-to-spend' };
|
|
151
|
+
}
|
|
152
|
+
const available = this.economy.available();
|
|
153
|
+
if (available <= 0n) {
|
|
154
|
+
this.buyProgress = { state: 'failed', reason: 'nothing-to-spend' };
|
|
155
|
+
this.publishEconomy();
|
|
156
|
+
return { bought: false, reason: 'nothing-to-spend' };
|
|
157
|
+
}
|
|
158
|
+
if (this.buyProgress?.state === 'signing' || this.buyProgress?.state === 'pending') {
|
|
159
|
+
return { bought: false, reason: 'try-again' };
|
|
160
|
+
}
|
|
161
|
+
const spend = maxSpendWei < available ? maxSpendWei : available;
|
|
162
|
+
this.heldWei += spend;
|
|
163
|
+
this.holdExpiresAt = Date.now() + MOCK_HOLD_MS;
|
|
164
|
+
this.buyProgress = { state: 'signing', spendWei: spend };
|
|
165
|
+
this.publishEconomy();
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, FAKE_SIGN_MS));
|
|
167
|
+
if (this.disposed) {
|
|
168
|
+
this.heldWei -= spend;
|
|
169
|
+
this.buyProgress = { state: 'failed', reason: 'try-again' };
|
|
170
|
+
this.publishEconomy();
|
|
171
|
+
return { bought: false, reason: 'try-again' };
|
|
172
|
+
}
|
|
173
|
+
this.buyProgress = { state: 'pending', spendWei: spend };
|
|
174
|
+
this.publishEconomy();
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, FAKE_PENDING_MS));
|
|
176
|
+
if (this.disposed) {
|
|
177
|
+
this.heldWei -= spend;
|
|
178
|
+
this.buyProgress = { state: 'failed', reason: 'try-again' };
|
|
179
|
+
this.publishEconomy();
|
|
180
|
+
return { bought: false, reason: 'try-again' };
|
|
181
|
+
}
|
|
182
|
+
this.heldWei -= spend;
|
|
183
|
+
this.spentWei += spend;
|
|
184
|
+
this.buyProgress = { state: 'confirmed', spentWei: spend };
|
|
185
|
+
this.emit();
|
|
186
|
+
return { bought: true, spentWei: spend };
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
social = {
|
|
190
|
+
// Echoed straight back so reaction UI can be built and seen working with nobody else present.
|
|
191
|
+
react: (id) => {
|
|
192
|
+
if (!isReactionId(id) || !this.allowedReactions.has(id))
|
|
193
|
+
return;
|
|
194
|
+
for (const listener of this.reactionListeners)
|
|
195
|
+
listener({ player: this.player, id });
|
|
196
|
+
},
|
|
197
|
+
onReaction: (listener) => {
|
|
198
|
+
this.reactionListeners.add(listener);
|
|
199
|
+
return () => this.reactionListeners.delete(listener);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
dispose() {
|
|
203
|
+
this.disposed = true;
|
|
204
|
+
clearInterval(this.timer);
|
|
205
|
+
this.listeners.clear();
|
|
206
|
+
this.reactionListeners.clear();
|
|
207
|
+
this.connectionSignal.set('ended');
|
|
208
|
+
this.connectionSignal.clear();
|
|
209
|
+
this.launchSignal.clear();
|
|
210
|
+
this.presenceSignal.clear();
|
|
211
|
+
this.stopMarket();
|
|
212
|
+
this.marketSignal.clear();
|
|
213
|
+
this.economySignal.clear();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=mock.js.map
|
package/dist/mock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../src/mock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AACxD,OAAO,EAAE,SAAS,EAAkC,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EACL,YAAY,GAMb,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD,mEAAmE;AACnE,MAAM,OAAO,GAAG,GAAG,CAAC;AAEpB,oFAAoF;AACpF,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,eAAe,GAAG,GAAG,CAAC;AAC5B,iGAAiG;AACjG,MAAM,YAAY,GAAG,CAAC,GAAG,MAAM,CAAC;AAEhC,MAAM,OAAO,QAAQ;IAuBA;IApBF,KAAK,CAA8D;IACnE,MAAM,CAAW;IACjB,WAAW,CAAS;IACpB,gBAAgB,CAAsB;IACtC,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;IACrE,iBAAiB,GAAG,IAAI,GAAG,EAAyB,CAAC;IACrD,KAAK,CAAiC;IAC/C,QAAQ,GAAG,EAAE,CAAC;IACd,OAAO,GAAG,EAAE,CAAC;IACrB,+FAA+F;IACvF,aAAa,GAAkB,IAAI,CAAC;IACpC,WAAW,GAAuB,IAAI,CAAC;IACvC,QAAQ,GAAG,KAAK,CAAC;IACR,gBAAgB,GAAG,IAAI,MAAM,CAAkB,WAAW,CAAC,CAAC;IAC5D,YAAY,CAAwB;IACpC,cAAc,GAAG,IAAI,MAAM,CAAgB,EAAE,cAAc,EAAE,CAAC,EAAE,CAAC,CAAC;IAClE,YAAY,CAAsB;IAClC,UAAU,CAAa;IAExC,YACmB,IAAsE,EACvF,OAA4B;QADX,SAAI,GAAJ,IAAI,CAAkE;QAGvF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC;QACxC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,eAAmB,CAAC;QAC9D,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,UAAU,CAAC,0DAA0D,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,EAAE;YAChE,OAAO;YACP,QAAQ,EAAE,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;SACjD,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI;YAC/B,OAAO,EAAE,MAAM;YACf,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,IAAI;YACjB,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,IAAI;YACd,OAAO;YACP,QAAQ,EAAE,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;YAChD,YAAY,EAAE,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;SACrD,CAAC;QACF,IAAI,CAAC,YAAY,GAAG,IAAI,MAAM,CAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,YAAY,GAAG,IAAI,MAAM,CAC5B,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAC7G,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7G,IAAI,CAAC,QAAQ,GAAG,IAAI,sBAAsB,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAEvE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAChG,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACvD,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACxD,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAqC;YACjD,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE;YACnC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;SAC/C,CAAC;QACF,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC;IAED,SAAS,CAAC,QAA8D;QACtE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,0FAA0F;QAC1F,sEAAsE;QACtE,QAAQ,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClG,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc;QACvB,0FAA0F;QAC1F,yDAAyD;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAE5E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACxG,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC7F,CAAC;IAED,GAAG;QACD,8FAA8F;QAC9F,yBAAyB;QACzB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAEQ,MAAM,GAAG;QAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;QAC1C,SAAS,EAAE,CAAC,QAAwC,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC;KAC/F,CAAC;IAEO,UAAU,GAAG;QACpB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE;QAC9C,SAAS,EAAE,CAAC,QAA0C,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,QAAQ,CAAC;KACrG,CAAC;IAEO,MAAM,GAAG;QAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE;QAC1C,SAAS,EAAE,CAAC,QAAsC,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC;KAC7F,CAAC;IAEO,QAAQ,CAAyB;IAEjC,QAAQ,GAAG;QAClB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QAC5C,SAAS,EAAE,CAAC,QAAwC,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC;KACjG,CAAC;IAEM,YAAY;QAClB,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;QAC/E,MAAM,YAAY,GAAG,SAAS,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9D,OAAO;YACL,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,SAAS;YACT,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,YAAY,EAAE,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE;YACnD,aAAa,EAAE,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI;YAC5D,GAAG,EAAE,IAAI,CAAC,WAAW;SACtB,CAAC;IACJ,CAAC;IAEgB,aAAa,GAAG,IAAI,MAAM,CAAe;QACxD,WAAW,EAAE,EAAE;QACf,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,EAAE;QACZ,YAAY,EAAE,EAAE;QAChB,aAAa,EAAE,IAAI;QACnB,GAAG,EAAE,IAAI;KACV,CAAC,CAAC;IAEK,cAAc;QACpB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IAC9C,CAAC;IAEQ,OAAO,GAAY;QAC1B,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE;QAClC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,QAAQ,CAAC;QAC/D,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,YAAY;QACjD,GAAG,EAAE,KAAK,EAAE,WAAmB,EAAsB,EAAE;YACrD,IAAI,WAAW,IAAI,EAAE,EAAE,CAAC;gBACtB,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;gBACnE,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;YACvD,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC3C,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;gBACpB,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;gBACnE,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;YACvD,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;gBACnF,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;YAChD,CAAC;YAED,MAAM,KAAK,GAAG,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;YAChE,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;YACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;YAC/C,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YACzD,IAAI,CAAC,cAAc,EAAE,CAAC;YAEtB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;YAClE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;gBACtB,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;gBAC5D,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;YAChD,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YACzD,IAAI,CAAC,cAAc,EAAE,CAAC;YAEtB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;YACrE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;gBACtB,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;gBAC5D,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;YAChD,CAAC;YAED,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC;YACtB,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;YACvB,IAAI,CAAC,WAAW,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC3C,CAAC;KACF,CAAC;IAEO,MAAM,GAAW;QACxB,8FAA8F;QAC9F,KAAK,EAAE,CAAC,EAAU,EAAE,EAAE;YACpB,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO;YAChE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,iBAAiB;gBAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,UAAU,EAAE,CAAC,QAAQ,EAAE,EAAE;YACvB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACrC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvD,CAAC;KACF,CAAC;IAEF,OAAO;QACL,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MarketState } from '@flayerlabs/gamemode-spec/live';
|
|
2
|
+
import type { Readable } from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* A market that never happened, played back on a timer.
|
|
5
|
+
*
|
|
6
|
+
* Market data comes from an adapter the embedding page supplies, and a game building on a laptop
|
|
7
|
+
* has no page and no chain — so `room.market` reports `unavailable` and the chart is empty. That is
|
|
8
|
+
* honest and it is also useless: the chart is often the second thing a game draws, and it cannot be
|
|
9
|
+
* built at all until someone else's integration exists.
|
|
10
|
+
*
|
|
11
|
+
* This fills that gap the same way the mock room does. The mock runs the game's REAL rules so a
|
|
12
|
+
* game cannot pass locally and fail live; this replays REAL market shapes — a price series and
|
|
13
|
+
* trades arriving over time — so a chart built against it is a chart that works. What is faked is
|
|
14
|
+
* only the chain.
|
|
15
|
+
*
|
|
16
|
+
* It is not a simulation. Prices do not respond to buying, because a fixture that reacted to the
|
|
17
|
+
* player would be a second, wrong implementation of a market, and the point of a fixture is that it
|
|
18
|
+
* is the same every run.
|
|
19
|
+
*/
|
|
20
|
+
export interface MarketFixture {
|
|
21
|
+
/** In ascending time order. Offsets in ms from when the replay starts. */
|
|
22
|
+
prices: readonly {
|
|
23
|
+
afterMs: number;
|
|
24
|
+
priceEth: number;
|
|
25
|
+
}[];
|
|
26
|
+
/** In ascending time order, and independent of prices. */
|
|
27
|
+
trades: readonly {
|
|
28
|
+
afterMs: number;
|
|
29
|
+
player: string | null;
|
|
30
|
+
side: 'buy' | 'sell';
|
|
31
|
+
spendWei: bigint;
|
|
32
|
+
priceEth: number | null;
|
|
33
|
+
}[];
|
|
34
|
+
marketCapUsd?: number | null;
|
|
35
|
+
/** Start again from the beginning once everything has played. Off by default. */
|
|
36
|
+
loop?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface ReplayMarket extends Readable<MarketState> {
|
|
39
|
+
/** Stop the timer. A replay left running holds a handle open and keeps a process alive. */
|
|
40
|
+
stop(): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a market adapter that replays a fixture.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const market = replayMarket(BUSY_LAUNCH)
|
|
47
|
+
* const room = createMockRoom(game, { config, platform: { market } })
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Pass the same fixture to the live room's `platform` to develop a chart against a real gate but no
|
|
51
|
+
* real chain.
|
|
52
|
+
*/
|
|
53
|
+
export declare function replayMarket(fixture: MarketFixture, now?: () => number): ReplayMarket;
|
|
54
|
+
/**
|
|
55
|
+
* A fixture to start from: a launch that opens quiet, spikes, and settles.
|
|
56
|
+
*
|
|
57
|
+
* Real enough to find the bugs a flat line hides — a chart that looks right on smooth data and
|
|
58
|
+
* wrong on a spike is the usual outcome of building against nothing.
|
|
59
|
+
*/
|
|
60
|
+
export declare const BUSY_LAUNCH: MarketFixture;
|
|
61
|
+
//# sourceMappingURL=replay-market.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-market.d.ts","sourceRoot":"","sources":["../src/replay-market.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,WAAW,EAAe,MAAM,gCAAgC,CAAC;AAC5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,MAAM,EAAE,SAAS;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACzD,0DAA0D;IAC1D,MAAM,EAAE,SAAS;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,EAAE,CAAC;IACJ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,YAAa,SAAQ,QAAQ,CAAC,WAAW,CAAC;IACzD,2FAA2F;IAC3F,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,YAAY,CAqE/F;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,aAmBzB,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Signal } from './signal.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a market adapter that replays a fixture.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const market = replayMarket(BUSY_LAUNCH)
|
|
7
|
+
* const room = createMockRoom(game, { config, platform: { market } })
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Pass the same fixture to the live room's `platform` to develop a chart against a real gate but no
|
|
11
|
+
* real chain.
|
|
12
|
+
*/
|
|
13
|
+
export function replayMarket(fixture, now = Date.now) {
|
|
14
|
+
const prices = [...fixture.prices].sort((a, b) => a.afterMs - b.afterMs);
|
|
15
|
+
const trades = [...fixture.trades].sort((a, b) => a.afterMs - b.afterMs);
|
|
16
|
+
const span = Math.max(prices.at(-1)?.afterMs ?? 0, trades.at(-1)?.afterMs ?? 0);
|
|
17
|
+
// `loading` rather than `live` until the first entry is due, so a chart's empty state is
|
|
18
|
+
// reachable in development instead of only in production.
|
|
19
|
+
const signal = new Signal({
|
|
20
|
+
status: 'loading',
|
|
21
|
+
prices: [],
|
|
22
|
+
trades: [],
|
|
23
|
+
marketCapUsd: fixture.marketCapUsd ?? null,
|
|
24
|
+
});
|
|
25
|
+
const startedAt = now();
|
|
26
|
+
let frozen = null;
|
|
27
|
+
/**
|
|
28
|
+
* The state at this instant, computed rather than remembered.
|
|
29
|
+
*
|
|
30
|
+
* `current()` reads this directly. Serving the last tick's value instead would make a read up to
|
|
31
|
+
* a quarter second stale, which is invisible in a browser and the difference between a passing
|
|
32
|
+
* and a failing test anywhere a clock is controlled.
|
|
33
|
+
*/
|
|
34
|
+
const compute = () => {
|
|
35
|
+
const elapsed = fixture.loop && span > 0 ? (now() - startedAt) % (span + 1) : now() - startedAt;
|
|
36
|
+
const due = (all) => all.filter((entry) => entry.afterMs <= elapsed);
|
|
37
|
+
const shownPrices = due(prices).map((price) => ({
|
|
38
|
+
at: startedAt + price.afterMs,
|
|
39
|
+
priceEth: price.priceEth,
|
|
40
|
+
}));
|
|
41
|
+
const shownTrades = due(trades).map((trade, index) => ({
|
|
42
|
+
// Stable across ticks, because a chart keyed on this must not remount every frame.
|
|
43
|
+
id: `replay-${index}`,
|
|
44
|
+
at: startedAt + trade.afterMs,
|
|
45
|
+
player: trade.player,
|
|
46
|
+
side: trade.side,
|
|
47
|
+
spendWei: trade.spendWei,
|
|
48
|
+
priceEth: trade.priceEth,
|
|
49
|
+
}));
|
|
50
|
+
return {
|
|
51
|
+
status: shownPrices.length === 0 && shownTrades.length === 0 ? 'loading' : 'live',
|
|
52
|
+
prices: shownPrices,
|
|
53
|
+
trades: shownTrades,
|
|
54
|
+
marketCapUsd: fixture.marketCapUsd ?? null,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const current = () => frozen ?? compute();
|
|
58
|
+
const timer = setInterval(() => signal.set(current()), 250);
|
|
59
|
+
// Node keeps a process alive for a pending timer; a fixture must not be the reason a test hangs.
|
|
60
|
+
timer.unref?.();
|
|
61
|
+
signal.set(current());
|
|
62
|
+
return {
|
|
63
|
+
current,
|
|
64
|
+
subscribe: (listener) => signal.subscribe(listener),
|
|
65
|
+
stop: () => {
|
|
66
|
+
// Frozen rather than blanked: a stopped replay keeps showing its last chart instead of
|
|
67
|
+
// flashing empty on the way out.
|
|
68
|
+
frozen ??= compute();
|
|
69
|
+
clearInterval(timer);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A fixture to start from: a launch that opens quiet, spikes, and settles.
|
|
75
|
+
*
|
|
76
|
+
* Real enough to find the bugs a flat line hides — a chart that looks right on smooth data and
|
|
77
|
+
* wrong on a spike is the usual outcome of building against nothing.
|
|
78
|
+
*/
|
|
79
|
+
export const BUSY_LAUNCH = {
|
|
80
|
+
marketCapUsd: 184_000,
|
|
81
|
+
prices: [
|
|
82
|
+
{ afterMs: 0, priceEth: 0.000_012 },
|
|
83
|
+
{ afterMs: 2_000, priceEth: 0.000_014 },
|
|
84
|
+
{ afterMs: 4_000, priceEth: 0.000_019 },
|
|
85
|
+
{ afterMs: 6_000, priceEth: 0.000_041 },
|
|
86
|
+
{ afterMs: 8_000, priceEth: 0.000_038 },
|
|
87
|
+
{ afterMs: 10_000, priceEth: 0.000_052 },
|
|
88
|
+
{ afterMs: 14_000, priceEth: 0.000_047 },
|
|
89
|
+
{ afterMs: 18_000, priceEth: 0.000_049 },
|
|
90
|
+
],
|
|
91
|
+
trades: [
|
|
92
|
+
{ afterMs: 1_500, player: '0x1111111111111111111111111111111111111111', side: 'buy', spendWei: 12000000000000000n, priceEth: 0.000_013 },
|
|
93
|
+
{ afterMs: 5_200, player: '0x2222222222222222222222222222222222222222', side: 'buy', spendWei: 40000000000000000n, priceEth: 0.000_021 },
|
|
94
|
+
{ afterMs: 6_100, player: null, side: 'buy', spendWei: 8000000000000000n, priceEth: 0.000_041 },
|
|
95
|
+
{ afterMs: 9_400, player: '0x3333333333333333333333333333333333333333', side: 'sell', spendWei: 5000000000000000n, priceEth: 0.000_039 },
|
|
96
|
+
{ afterMs: 12_800, player: '0x1111111111111111111111111111111111111111', side: 'buy', spendWei: 25000000000000000n, priceEth: 0.000_050 },
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=replay-market.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-market.js","sourceRoot":"","sources":["../src/replay-market.ts"],"names":[],"mappings":"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"}
|
package/dist/signal.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A tiny observable value. Subscribers always receive the current value immediately. */
|
|
2
|
+
export declare class Signal<T> {
|
|
3
|
+
private value;
|
|
4
|
+
private readonly listeners;
|
|
5
|
+
constructor(value: T);
|
|
6
|
+
current(): T;
|
|
7
|
+
set(value: T): void;
|
|
8
|
+
subscribe(listener: (value: T) => void): () => void;
|
|
9
|
+
clear(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../src/signal.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,qBAAa,MAAM,CAAC,CAAC;IAGP,OAAO,CAAC,KAAK;IAFzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;gBAEvC,KAAK,EAAE,CAAC;IAE5B,OAAO,IAAI,CAAC;IAIZ,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAMnB,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAMnD,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/signal.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A tiny observable value. Subscribers always receive the current value immediately. */
|
|
2
|
+
export class Signal {
|
|
3
|
+
value;
|
|
4
|
+
listeners = new Set();
|
|
5
|
+
constructor(value) {
|
|
6
|
+
this.value = value;
|
|
7
|
+
}
|
|
8
|
+
current() {
|
|
9
|
+
return this.value;
|
|
10
|
+
}
|
|
11
|
+
set(value) {
|
|
12
|
+
if (Object.is(value, this.value))
|
|
13
|
+
return;
|
|
14
|
+
this.value = value;
|
|
15
|
+
for (const listener of this.listeners)
|
|
16
|
+
listener(value);
|
|
17
|
+
}
|
|
18
|
+
subscribe(listener) {
|
|
19
|
+
this.listeners.add(listener);
|
|
20
|
+
listener(this.value);
|
|
21
|
+
return () => this.listeners.delete(listener);
|
|
22
|
+
}
|
|
23
|
+
clear() {
|
|
24
|
+
this.listeners.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../src/signal.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,OAAO,MAAM;IAGG;IAFH,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE3D,YAAoB,KAAQ;QAAR,UAAK,GAAL,KAAK,CAAG;IAAG,CAAC;IAEhC,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,GAAG,CAAC,KAAQ;QACV,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO;QACzC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC;IAED,SAAS,CAAC,QAA4B;QACpC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flayerlabs/gamemode-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser and mock-room client for Flaunch Game Modes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Flayer Labs",
|
|
7
|
+
"homepage": "https://github.com/flayerlabs/gamemode-sdk#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/flayerlabs/gamemode-sdk.git",
|
|
11
|
+
"directory": "packages/client"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/flayerlabs/gamemode-sdk/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prebuild": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
23
|
+
"build": "tsc -p tsconfig.build.json",
|
|
24
|
+
"prepack": "pnpm build",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/pg": "^8.21.0",
|
|
30
|
+
"@types/ws": "^8.18.1",
|
|
31
|
+
"pg": "^8.23.0",
|
|
32
|
+
"viem": "^2.55.13",
|
|
33
|
+
"ws": "^8.21.3"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@flayerlabs/gamemode-spec": "^0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"default": "./dist/index.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"src"
|
|
48
|
+
],
|
|
49
|
+
"sideEffects": false,
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
}
|
|
53
|
+
}
|