feat(m1): foundations — queue engine, costs, stub content #13

Merged
ginnoir merged 12 commits from feat/m1-foundations into main 2026-06-11 18:28:16 -05:00
2 changed files with 34 additions and 0 deletions
Showing only changes of commit 923a7a3dac - Show all commits
+27
View File
@@ -97,3 +97,30 @@ describe('costs and multi-yield', () => {
expect(content.actionsById.forage.costs).toEqual([]);
});
});
describe('unlock conditions', () => {
it('accepts optional minResources and requireStoryFlags', () => {
const actions = [
{
id: 'forage',
name: 'Forage',
durationMs: 3000,
yields: [{ resourceId: 'gold', amount: 1 }],
unlock: {
minResources: { gold: 10 },
requireStoryFlags: ['intro_complete'],
},
},
];
const content = buildContent({ resources: validResources, actions });
expect(content.actionsById.forage.unlock).toEqual({
minResources: { gold: 10 },
requireStoryFlags: ['intro_complete'],
});
});
it('defaults unlock to undefined when omitted', () => {
const content = buildContent({ resources: validResources, actions: validActions });
expect(content.actionsById.forage.unlock).toBeUndefined();
});
});
+7
View File
@@ -20,16 +20,23 @@ export const resourceAmountSchema = z.object({
amount: z.number().positive(),
});
export const unlockDefSchema = z.object({
minResources: z.record(z.string(), z.number().nonnegative()).optional(),
requireStoryFlags: z.array(z.string().min(1)).optional(),
});
export const actionDefSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
durationMs: z.number().positive(),
costs: z.array(resourceAmountSchema).default([]),
yields: z.array(resourceAmountSchema).min(1),
unlock: unlockDefSchema.optional(),
});
export type ResourceDef = z.infer<typeof resourceDefSchema>;
export type ResourceAmount = z.infer<typeof resourceAmountSchema>;
export type UnlockDef = z.infer<typeof unlockDefSchema>;
export type ActionDef = z.infer<typeof actionDefSchema>;
export interface Content {