feat: bootstrap walking skeleton

This commit is contained in:
ginnoir
2026-06-11 17:06:52 -05:00
commit ec8f857845
44 changed files with 6677 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import { z } from 'zod';
/**
* Content schemas.
*
* Game content (resources, actions, later story nodes) is data, validated at
* load time with zod so a malformed definition fails loudly instead of corrupting
* runtime state. The engine consumes the validated `Content` object and never
* reaches for raw definitions.
*/
export const resourceDefSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
startAmount: z.number().nonnegative().default(0),
});
export const actionDefSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
durationMs: z.number().positive(),
yields: z.object({
resourceId: z.string().min(1),
amount: z.number().positive(),
}),
});
export type ResourceDef = z.infer<typeof resourceDefSchema>;
export type ActionDef = z.infer<typeof actionDefSchema>;
export interface Content {
resources: ResourceDef[];
actions: ActionDef[];
resourcesById: Record<string, ResourceDef>;
actionsById: Record<string, ActionDef>;
}
function indexById<T extends { id: string }>(items: T[], kind: string): Record<string, T> {
const byId: Record<string, T> = {};
for (const item of items) {
if (byId[item.id]) {
throw new Error(`Duplicate ${kind} id "${item.id}"`);
}
byId[item.id] = item;
}
return byId;
}
/** Validate raw definitions and build the indexed, referentially-checked Content. */
export function buildContent(input: { resources: unknown[]; actions: unknown[] }): Content {
const resources = input.resources.map((r) => resourceDefSchema.parse(r));
const actions = input.actions.map((a) => actionDefSchema.parse(a));
const resourcesById = indexById(resources, 'resource');
const actionsById = indexById(actions, 'action');
for (const action of actions) {
if (!resourcesById[action.yields.resourceId]) {
throw new Error(
`Action "${action.id}" yields unknown resource "${action.yields.resourceId}"`,
);
}
}
return { resources, actions, resourcesById, actionsById };
}