Initial TTRPG character meta-builder with visual sheet editor.
Offline Electron app seeds 10 system presets, supports create-from-scratch designer with palette/drag/resize/bricks, and exports .ttrpg-system.json for the player. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import type { SystemDefinition, ValidationIssue, ValidationResult } from './types.ts';
|
||||
import { SYSTEM_FORMAT, FORMAT_VERSION } from './types.ts';
|
||||
|
||||
function err(path: string, message: string, code: string): ValidationIssue {
|
||||
return { path, message, code };
|
||||
}
|
||||
|
||||
export function validateSystemDefinition(system: unknown): ValidationResult {
|
||||
const errors: ValidationIssue[] = [];
|
||||
if (!system || typeof system !== 'object') {
|
||||
return { ok: false, errors: [err('$', 'System must be an object', 'type')] };
|
||||
}
|
||||
const s = system as Partial<SystemDefinition>;
|
||||
if (s.format !== SYSTEM_FORMAT) errors.push(err('format', `Expected ${SYSTEM_FORMAT}`, 'format'));
|
||||
if (s.formatVersion !== FORMAT_VERSION) {
|
||||
errors.push(err('formatVersion', `Expected ${FORMAT_VERSION}`, 'formatVersion'));
|
||||
}
|
||||
if (!s.id || typeof s.id !== 'string') errors.push(err('id', 'id required', 'required'));
|
||||
if (!s.name || typeof s.name !== 'string') errors.push(err('name', 'name required', 'required'));
|
||||
if (typeof s.schemaVersion !== 'number') errors.push(err('schemaVersion', 'number required', 'type'));
|
||||
if (typeof s.revision !== 'number') errors.push(err('revision', 'number required', 'type'));
|
||||
if (!s.bricks || typeof s.bricks !== 'object') errors.push(err('bricks', 'bricks required', 'required'));
|
||||
if (!Array.isArray(s.creationRules)) errors.push(err('creationRules', 'array required', 'type'));
|
||||
if (!s.sheetLayout || !Array.isArray(s.sheetLayout.pages)) {
|
||||
errors.push(err('sheetLayout.pages', 'pages required', 'required'));
|
||||
}
|
||||
|
||||
const derived = s.bricks?.derived ?? [];
|
||||
const keys = new Set(derived.map((d) => d.key));
|
||||
for (const d of derived) {
|
||||
if (!d.formula) errors.push(err(`derived.${d.key}`, 'formula required', 'required'));
|
||||
for (const dep of d.dependsOn ?? []) {
|
||||
if (dep.startsWith('derived.') && !keys.has(dep.slice('derived.'.length))) {
|
||||
// allow forward refs; cycle checked elsewhere
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: errors.length === 0, errors };
|
||||
}
|
||||
Reference in New Issue
Block a user