feat(save): persist story flags and node progress in v1
This commit is contained in:
@@ -58,6 +58,17 @@ describe('createSave()', () => {
|
|||||||
state.actionQueue.push('forage');
|
state.actionQueue.push('forage');
|
||||||
expect(save.state.actionQueue).toEqual(['forage', 'forage']);
|
expect(save.state.actionQueue).toEqual(['forage', 'forage']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('snapshots story fields in the save payload', () => {
|
||||||
|
const state = sampleState();
|
||||||
|
state.storyFlags = { route_a: true };
|
||||||
|
state.currentStoryNodeId = 'route_a_beat';
|
||||||
|
state.seenStoryNodeIds = ['boot_intro', 'route_a_beat'];
|
||||||
|
const save = createSave(state, 1700);
|
||||||
|
expect(save.state.storyFlags).toEqual({ route_a: true });
|
||||||
|
expect(save.state.currentStoryNodeId).toBe('route_a_beat');
|
||||||
|
expect(save.state.seenStoryNodeIds).toHaveLength(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('serialize / deserialize round-trip', () => {
|
describe('serialize / deserialize round-trip', () => {
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export const gameStateSchema = z.object({
|
|||||||
activeActionId: z.string().nullable(),
|
activeActionId: z.string().nullable(),
|
||||||
actionElapsedMs: z.number().nonnegative(),
|
actionElapsedMs: z.number().nonnegative(),
|
||||||
actionQueue: z.array(z.string()).default([]),
|
actionQueue: z.array(z.string()).default([]),
|
||||||
|
storyFlags: z.record(z.string(), z.boolean()).default({}),
|
||||||
|
currentStoryNodeId: z.string().default(''),
|
||||||
|
seenStoryNodeIds: z.array(z.string()).default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const saveSchema = z.object({
|
export const saveSchema = z.object({
|
||||||
@@ -49,6 +52,9 @@ export function createSave(state: GameState, now: number): SaveData {
|
|||||||
activeActionId: state.activeActionId,
|
activeActionId: state.activeActionId,
|
||||||
actionElapsedMs: state.actionElapsedMs,
|
actionElapsedMs: state.actionElapsedMs,
|
||||||
actionQueue: [...state.actionQueue],
|
actionQueue: [...state.actionQueue],
|
||||||
|
storyFlags: { ...state.storyFlags },
|
||||||
|
currentStoryNodeId: state.currentStoryNodeId,
|
||||||
|
seenStoryNodeIds: [...state.seenStoryNodeIds],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,19 @@ describe('loadGame()', () => {
|
|||||||
expect(result.state.actionQueue).toEqual(['b']);
|
expect(result.state.actionQueue).toEqual(['b']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('restores story fields on load', async () => {
|
||||||
|
const content = testContent();
|
||||||
|
const backend = createMemoryBackend();
|
||||||
|
const state = createGameState(content);
|
||||||
|
state.storyFlags = { route_b: true };
|
||||||
|
state.currentStoryNodeId = 'fork_choice';
|
||||||
|
state.seenStoryNodeIds = ['boot_intro'];
|
||||||
|
await saveGame(state, backend, 1000);
|
||||||
|
const loaded = await loadGame(content, backend, 1000);
|
||||||
|
expect(loaded.state.storyFlags.route_b).toBe(true);
|
||||||
|
expect(loaded.state.currentStoryNodeId).toBe('fork_choice');
|
||||||
|
});
|
||||||
|
|
||||||
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(
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ export async function loadGame(
|
|||||||
activeActionId,
|
activeActionId,
|
||||||
actionElapsedMs: save.state.actionElapsedMs,
|
actionElapsedMs: save.state.actionElapsedMs,
|
||||||
actionQueue: [...(save.state.actionQueue ?? [])],
|
actionQueue: [...(save.state.actionQueue ?? [])],
|
||||||
storyFlags: { ...base.storyFlags },
|
storyFlags: { ...save.state.storyFlags },
|
||||||
currentStoryNodeId: base.currentStoryNodeId,
|
currentStoryNodeId: save.state.currentStoryNodeId ?? '',
|
||||||
seenStoryNodeIds: [...base.seenStoryNodeIds],
|
seenStoryNodeIds: [...(save.state.seenStoryNodeIds ?? [])],
|
||||||
};
|
};
|
||||||
savedAt = save.savedAt;
|
savedAt = save.savedAt;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user