feat(save): persist action queue in v1 save payload
This commit is contained in:
@@ -44,6 +44,15 @@ describe('createSave()', () => {
|
|||||||
state.resources.gold = 999;
|
state.resources.gold = 999;
|
||||||
expect(save.state.resources.gold).toBe(12);
|
expect(save.state.resources.gold).toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('snapshots actionQueue in save payload', () => {
|
||||||
|
const state = sampleState();
|
||||||
|
state.actionQueue = ['forage', 'forage'];
|
||||||
|
const save = createSave(state, 1700);
|
||||||
|
expect(save.state.actionQueue).toEqual(['forage', 'forage']);
|
||||||
|
state.actionQueue.push('forage');
|
||||||
|
expect(save.state.actionQueue).toEqual(['forage', 'forage']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('serialize / deserialize round-trip', () => {
|
describe('serialize / deserialize round-trip', () => {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const gameStateSchema = z.object({
|
|||||||
resources: z.record(z.string(), z.number()),
|
resources: z.record(z.string(), z.number()),
|
||||||
activeActionId: z.string().nullable(),
|
activeActionId: z.string().nullable(),
|
||||||
actionElapsedMs: z.number().nonnegative(),
|
actionElapsedMs: z.number().nonnegative(),
|
||||||
|
actionQueue: z.array(z.string()).default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const saveSchema = z.object({
|
export const saveSchema = z.object({
|
||||||
@@ -47,6 +48,7 @@ export function createSave(state: GameState, now: number): SaveData {
|
|||||||
resources: { ...state.resources },
|
resources: { ...state.resources },
|
||||||
activeActionId: state.activeActionId,
|
activeActionId: state.activeActionId,
|
||||||
actionElapsedMs: state.actionElapsedMs,
|
actionElapsedMs: state.actionElapsedMs,
|
||||||
|
actionQueue: [...state.actionQueue],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { buildContent } from '../../content/schema';
|
import { buildContent } from '../../content/schema';
|
||||||
import { createGameState, startAction } from '../../engine/game';
|
import { createGameState, enqueueAction, startAction } from '../../engine/game';
|
||||||
import { createSave, serializeSave } from '../../engine/save';
|
import { createSave, serializeSave } from '../../engine/save';
|
||||||
import { createMemoryBackend, loadGame, saveGame } from '../persistence';
|
import { createMemoryBackend, loadGame, saveGame } from '../persistence';
|
||||||
|
|
||||||
@@ -13,6 +13,16 @@ function testContent() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function queueTestContent() {
|
||||||
|
return buildContent({
|
||||||
|
resources: [{ id: 'gold', name: 'Gold', startAmount: 0 }],
|
||||||
|
actions: [
|
||||||
|
{ id: 'a', name: 'A', durationMs: 3000, yields: [{ resourceId: 'gold', amount: 1 }] },
|
||||||
|
{ id: 'b', name: 'B', durationMs: 3000, yields: [{ resourceId: 'gold', amount: 1 }] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('loadGame()', () => {
|
describe('loadGame()', () => {
|
||||||
it('returns a fresh game when no save exists', async () => {
|
it('returns a fresh game when no save exists', async () => {
|
||||||
const content = testContent();
|
const content = testContent();
|
||||||
@@ -42,6 +52,19 @@ describe('loadGame()', () => {
|
|||||||
expect(result.offlineMs).toBe(0);
|
expect(result.offlineMs).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('restores actionQueue on load', async () => {
|
||||||
|
const content = queueTestContent();
|
||||||
|
const state = createGameState(content);
|
||||||
|
enqueueAction(state, content, 'a');
|
||||||
|
enqueueAction(state, content, 'b');
|
||||||
|
const backend = createMemoryBackend();
|
||||||
|
await saveGame(state, backend, 1000);
|
||||||
|
|
||||||
|
const result = await loadGame(content, backend, 1000);
|
||||||
|
expect(result.state.activeActionId).toBe('a');
|
||||||
|
expect(result.state.actionQueue).toEqual(['b']);
|
||||||
|
});
|
||||||
|
|
||||||
it('drops an active action that no longer exists in content', async () => {
|
it('drops an active action that no longer exists in content', async () => {
|
||||||
const content = testContent();
|
const content = testContent();
|
||||||
const stale = serializeSave(
|
const stale = serializeSave(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export async function loadGame(
|
|||||||
resources: { ...base.resources, ...save.state.resources },
|
resources: { ...base.resources, ...save.state.resources },
|
||||||
activeActionId,
|
activeActionId,
|
||||||
actionElapsedMs: save.state.actionElapsedMs,
|
actionElapsedMs: save.state.actionElapsedMs,
|
||||||
actionQueue: [...base.actionQueue],
|
actionQueue: [...(save.state.actionQueue ?? [])],
|
||||||
};
|
};
|
||||||
savedAt = save.savedAt;
|
savedAt = save.savedAt;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user