// scripts/export-native-presets.ts
//
// Exports every built-in preset to JSON for the native Swift/Metal engine.
//
// The web app's BUILT_IN_PRESETS is the single source of truth: its literals
// run through mk()/sp()/hex() and the attraction/repulsion → interactionMatrix
// fold, so the *final* numbers exist only after construction — they cannot be
// hand-copied faithfully. This script writes the fully-resolved presets out as
// JSON the native `Preset` Codable model reads verbatim, so web ⇄ native can
// never drift.
//
// Run: npx tsx scripts/export-native-presets.ts
//
// Targets (both written, since SwiftPM bundles resources from the target tree):
// apple/presets/<id>.json — canonical set (+ README/thumbs)
// apple/PhysarumEngine/Sources/PhysarumViewer/Presets/ — bundled into the viewer/.app
//
// `author`/`thumbnail` are dropped: thumbnail is a web asset path, author is
// metadata the native engine ignores — matching the existing native JSONs.
// Output is run through `biome format` so it matches the repo's canonical JSON
// style (short arrays inline, long ones wrapped) and the pre-commit hook is a
// no-op on the generated files.
import { execSync } from 'node:child_process';
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { BUILT_IN_PRESETS } from '../src/presets/builtIn';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, '..');
const TARGETS = [
resolve(ROOT, 'apple/presets'),
resolve(ROOT, 'apple/PhysarumEngine/Sources/PhysarumViewer/Presets'),
resolve(ROOT, 'apple/PhysarumApp/Resources/Presets'),
];
for (const dir of TARGETS) mkdirSync(dir, { recursive: true });
let count = 0;
for (const preset of BUILT_IN_PRESETS) {
// Explicit field selection — drop author/thumbnail, keep engine-relevant data.
const out = {
id: preset.id,
name: preset.name,
builtIn: preset.builtIn,
tags: preset.tags,
tagSignal: preset.tagSignal,
params: preset.params,
};
const json = `${JSON.stringify(out, null, 2)}\n`;
for (const dir of TARGETS) {
writeFileSync(resolve(dir, `${preset.id}.json`), json, 'utf8');
}
count++;
}
// Copy thumbnails into the app bundle (Resources/Thumbnails), from the canonical
// apple/presets/thumbs set, so the macOS app's library grid has its images.
const thumbsSrc = resolve(ROOT, 'apple/presets/thumbs');
const thumbsDst = resolve(ROOT, 'apple/PhysarumApp/Resources/Thumbnails');
mkdirSync(thumbsDst, { recursive: true });
for (const preset of BUILT_IN_PRESETS) {
const png = `${preset.id}.png`;
const from = resolve(thumbsSrc, png);
if (existsSync(from)) copyFileSync(from, resolve(thumbsDst, png));
}
// Canonicalise formatting via biome (the same formatter the pre-commit hook
// enforces), so short arrays collapse to one line and over-long ones wrap.
execSync(
`npx @biomejs/biome format --write --files-ignore-unknown=true ${TARGETS.map((t) => `"${t}"`).join(' ')}`,
{
cwd: ROOT,
stdio: 'inherit',
},
);
console.log(`exported ${count} presets → ${TARGETS.length} targets`);
for (const dir of TARGETS) console.log(` ${dir.replace(`${ROOT}/`, '')}`);