Order ranked emotes against delayed status responses
This commit is contained in:
@@ -28,6 +28,7 @@ interface TerminalMatch {
|
|||||||
interface QueueEntry { playerId: string; gameSessionId: string; expiresAt: number }
|
interface QueueEntry { playerId: string; gameSessionId: string; expiresAt: number }
|
||||||
interface RankedMatch {
|
interface RankedMatch {
|
||||||
emotes: ReturnType<typeof createEmotes>;
|
emotes: ReturnType<typeof createEmotes>;
|
||||||
|
emoteVersion: number;
|
||||||
id: string; playerIds: Pair<string>; appearances: Pair<FighterAppearance>; engine: MatchEngine;
|
id: string; playerIds: Pair<string>; appearances: Pair<FighterAppearance>; engine: MatchEngine;
|
||||||
actions: Pair<ActionId | null>; departed: Set<string>; timer?: ReturnType<Clock['setTimeout']>;
|
actions: Pair<ActionId | null>; departed: Set<string>; timer?: ReturnType<Clock['setTimeout']>;
|
||||||
terminal?: TerminalMatch; saved: boolean; finishedExpiresAt?: Pair<number>;
|
terminal?: TerminalMatch; saved: boolean; finishedExpiresAt?: Pair<number>;
|
||||||
@@ -222,7 +223,7 @@ export async function createRankedService(db: Db, clock: Clock = systemClock, en
|
|||||||
state.selfIndex = selfIndex; state.ownAction = match.actions[selfIndex];
|
state.selfIndex = selfIndex; state.ownAction = match.actions[selfIndex];
|
||||||
// Own submissions change this participant's version without revealing whether the
|
// Own submissions change this participant's version without revealing whether the
|
||||||
// opponent has locked a choice. Engine transitions always move past either value.
|
// opponent has locked a choice. Engine transitions always move past either value.
|
||||||
state.version = state.version * 2 + (state.ownAction === null ? 0 : 1);
|
state.version = (state.version + match.emoteVersion) * 2 + (state.ownAction === null ? 0 : 1);
|
||||||
state.ranked = true; state.appearances = structuredClone(match.appearances);
|
state.ranked = true; state.appearances = structuredClone(match.appearances);
|
||||||
state.emotes = match.emotes.view(clock.now());
|
state.emotes = match.emotes.view(clock.now());
|
||||||
state.opponentLeft = match.departed.has(match.playerIds[1 - selfIndex]);
|
state.opponentLeft = match.departed.has(match.playerIds[1 - selfIndex]);
|
||||||
@@ -325,7 +326,7 @@ export async function createRankedService(db: Db, clock: Clock = systemClock, en
|
|||||||
}
|
}
|
||||||
const first = await readProfile(candidate.playerId), second = await readProfile(playerId);
|
const first = await readProfile(candidate.playerId), second = await readProfile(playerId);
|
||||||
const id = randomUUID(), engine = createMatchEngine({ matchId: id, mode: 'friend', selfIndex: 0, clock, settings: await createGlobalSettingsStore(db).get() });
|
const id = randomUUID(), engine = createMatchEngine({ matchId: id, mode: 'friend', selfIndex: 0, clock, settings: await createGlobalSettingsStore(db).get() });
|
||||||
const match: RankedMatch = { id, engine, emotes: createEmotes(), playerIds: [candidate.playerId, playerId],
|
const match: RankedMatch = { id, engine, emotes: createEmotes(), emoteVersion: 0, playerIds: [candidate.playerId, playerId],
|
||||||
appearances: [first.appearance, second.appearance], actions: [null, null], departed: new Set(), saved: false };
|
appearances: [first.appearance, second.appearance], actions: [null, null], departed: new Set(), saved: false };
|
||||||
engine.start(clock.now() + matchIntroductionDuration(engine.view().settings, true)); matches.add(match);
|
engine.start(clock.now() + matchIntroductionDuration(engine.view().settings, true)); matches.add(match);
|
||||||
for (const id of match.playerIds) { queue.delete(id); byPlayer.set(id, match); }
|
for (const id of match.playerIds) { queue.delete(id); byPlayer.set(id, match); }
|
||||||
@@ -348,6 +349,7 @@ export async function createRankedService(db: Db, clock: Clock = systemClock, en
|
|||||||
if (match.engine.view().phase === 'finished' || match.departed.size) throw new ApiError(409, 'NO_ACTIVE_MATCH');
|
if (match.engine.view().phase === 'finished' || match.departed.size) throw new ApiError(409, 'NO_ACTIVE_MATCH');
|
||||||
const slot = match.playerIds[0] === playerId ? 0 : 1;
|
const slot = match.playerIds[0] === playerId ? 0 : 1;
|
||||||
if (!match.emotes.send(slot, id, clock.now())) throw new ApiError(400, 'INVALID_REQUEST');
|
if (!match.emotes.send(slot, id, clock.now())) throw new ApiError(400, 'INVALID_REQUEST');
|
||||||
|
match.emoteVersion++;
|
||||||
return statusView(playerId);
|
return statusView(playerId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ test('ranked emotes authenticate membership, reach both players and preserve hid
|
|||||||
const matchId = paired.match!.matchId;
|
const matchId = paired.match!.matchId;
|
||||||
await f.clock.advance(paired.match!.startsAt - f.clock.now());
|
await f.clock.advance(paired.match!.startsAt - f.clock.now());
|
||||||
await f.service.action('player-a', f.a.gameSessionId, matchId, 1, 'charge');
|
await f.service.action('player-a', f.a.gameSessionId, matchId, 1, 'charge');
|
||||||
|
const before = await f.service.status('player-b', f.b.gameSessionId);
|
||||||
const sent = await f.service.emote('player-a', f.a.gameSessionId, matchId, 'greet');
|
const sent = await f.service.emote('player-a', f.a.gameSessionId, matchId, 'greet');
|
||||||
const received = await f.service.status('player-b', f.b.gameSessionId);
|
const received = await f.service.status('player-b', f.b.gameSessionId);
|
||||||
assert.equal(received.match!.emotes?.[0]?.id, 'greet');
|
assert.equal(received.match!.emotes?.[0]?.id, 'greet');
|
||||||
|
assert.ok(received.match!.version > before.match!.version, 'an older poll cannot overwrite this emote');
|
||||||
assert.deepEqual(received.match!.emotes, sent.match!.emotes);
|
assert.deepEqual(received.match!.emotes, sent.match!.emotes);
|
||||||
assert.equal(received.match!.ownAction, null);
|
assert.equal(received.match!.ownAction, null);
|
||||||
assert.deepEqual(received.match!.energy, [0, 0]);
|
assert.deepEqual(received.match!.energy, [0, 0]);
|
||||||
|
|||||||
Reference in New Issue
Block a user