fix(offline): cold-boot offline from the persisted session + profile #199
@@ -17,6 +17,9 @@ import type { Variant } from '../model';
|
||||
export interface Seat {
|
||||
kind: 'human' | 'robot';
|
||||
name: string;
|
||||
/** The player's real account id for a human seat, so the client identifies "you" and the correct
|
||||
* turn exactly as in an online game; absent for the robot (a synthetic id is derived). */
|
||||
accountId?: string;
|
||||
}
|
||||
|
||||
/** LocalGameRecord is the durable form of one local game — everything needed to reconstruct it. */
|
||||
|
||||
@@ -51,6 +51,20 @@ describe('LocalSource.list', () => {
|
||||
expect(list.every((g) => g.vsAi && g.status === 'active')).toBe(true);
|
||||
});
|
||||
|
||||
it('carries the human seat account id into the GameView so the client identifies "you"', async () => {
|
||||
const src = new LocalSource();
|
||||
await src.create({
|
||||
...opts('local:me', 3n),
|
||||
seats: [
|
||||
{ kind: 'human', name: 'You', accountId: 'acct-42' },
|
||||
{ kind: 'robot', name: 'Robot' },
|
||||
],
|
||||
});
|
||||
const [g] = await src.list();
|
||||
expect(g.seats[0].accountId).toBe('acct-42'); // the human's real id, matched against the session
|
||||
expect(g.seats[1].accountId).not.toBe('acct-42'); // the robot keeps a synthetic id
|
||||
});
|
||||
|
||||
it('reconstructs a game persisted by a previous session (not in the source cache)', async () => {
|
||||
// Seed the store via one source, then list from a fresh source whose in-memory cache is empty.
|
||||
await new LocalSource().create(opts('local:old', 7n));
|
||||
|
||||
@@ -120,7 +120,7 @@ export class LocalSource implements GameLoopSource {
|
||||
const now = nowUnix();
|
||||
const record = serializeGame(game, {
|
||||
id: opts.id,
|
||||
seats: opts.seats.map((s) => ({ kind: s.kind, name: s.name })),
|
||||
seats: opts.seats.map((s) => ({ kind: s.kind, name: s.name, accountId: s.accountId })),
|
||||
createdAtUnix: now,
|
||||
updatedAtUnix: now,
|
||||
robotLastMoveAtUnix: now,
|
||||
@@ -341,7 +341,7 @@ export class LocalSource implements GameLoopSource {
|
||||
const { game, record } = entry;
|
||||
const seats: Seat[] = record.seats.map((s, i) => ({
|
||||
seat: i,
|
||||
accountId: `${LOCAL_ID_PREFIX}${s.kind}:${i}`,
|
||||
accountId: s.accountId ?? `${LOCAL_ID_PREFIX}${s.kind}:${i}`,
|
||||
displayName: s.name,
|
||||
score: game.scoreOf(i),
|
||||
hintsUsed: 0,
|
||||
|
||||
@@ -74,7 +74,9 @@
|
||||
seed: randomSeed(),
|
||||
multipleWords: multipleWordsForRequest(v, multipleWords),
|
||||
seats: [
|
||||
{ kind: 'human', name: app.profile?.displayName ?? 'You' },
|
||||
// The human seat carries the real account id so the game screen and the lobby identify
|
||||
// "you" and the correct turn exactly as for an online game (the robot uses a synthetic id).
|
||||
{ kind: 'human', name: app.profile?.displayName ?? 'You', accountId: app.session?.userId },
|
||||
{ kind: 'robot', name: 'Robot' },
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user