/**
* Defensive loading of a universe's clock + climate model from its package.
*
* universes/<universe>/world.json # optional `time` / `weather` blocks
*
* The clock is authored inside `world.json` (it is intrinsic to the setting's
* identity), so this reader parses that same file and extracts the chronos model
* via `normalizeChronosModel`. A missing file, bad JSON, or no `time`/`weather`
* blocks all yield the built-in default model — a turn never breaks on chronos
* I/O. Path helpers are intentionally duplicated (not cross-imported) to keep
* `chronos` decoupled, exactly as `arc/load.ts` does.
*/
import { readFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { ChronosModel, normalizeChronosModel } from "./schema.js";
/** Default universes root: a `universes/` folder in the plugin working dir. */
function defaultUniversesDir(): string {
return resolve(process.cwd(), "universes");
}
/** Sanitize a universe id into a safe single path segment. */
function universeSegment(universe: string): string {
const safe = universe.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 100);
return safe || "default";
}
export interface ChronosLoadOptions {
/** Override the universes root (e.g. from tuning). Empty = default. */
universesDir?: string;
}
function resolvePackageDir(universe: string, options?: ChronosLoadOptions): string {
const base = options?.universesDir?.trim();
const root = base ? resolve(base) : defaultUniversesDir();
return join(root, universeSegment(universe));
}
/**
* Load a universe's clock/climate model from `universes/<universe>/world.json`.
* Always returns a valid {@link ChronosModel} (the built-in default when the file
* is absent/invalid or authors no `time`/`weather`), so the caller never has to
* null-check — mirrors how `loadWorldDef` always returns a definition.
*/
export async function loadChronosModel(
universe: string,
options?: ChronosLoadOptions,
): Promise<ChronosModel> {
const file = join(resolvePackageDir(universe, options), "world.json");
try {
const raw = await readFile(file, "utf8");
return normalizeChronosModel(JSON.parse(raw));
} catch {
return normalizeChronosModel(undefined);
}
}