refactor(presets): split mixed narration+dialogue beats into separate beats
Preset firstact JSONs had 37% of beats bundling narration and dialogue on the same beat object, while the current engine (Paradigm D prose splitter) produces strictly one-type-per-beat output. This mismatch caused the preset card experience to feel different from actual gameplay. Split 275 mixed beats across 94 files (120 total) into independent narration→dialogue beat pairs, preserving all images, characters, voices, and graph references. Beat count: 738→1013. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* One-off structural transform: splits mixed beats (narration + dialogue on the
|
||||
* same beat) into two separate beats so preset firstact JSONs match the current
|
||||
* engine's Paradigm-D output where each beat is strictly one type.
|
||||
*
|
||||
* Safe to run multiple times — already-split files are left unchanged.
|
||||
*
|
||||
* node scripts/split-firstact-beats.mjs [--dry-run]
|
||||
*/
|
||||
|
||||
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { resolve, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = resolve(__dirname, "..");
|
||||
const DRY_RUN = process.argv.includes("--dry-run");
|
||||
|
||||
const DIRS = [
|
||||
resolve(ROOT, "public/home/firstact"),
|
||||
resolve(ROOT, "public/home/firstact-portrait"),
|
||||
];
|
||||
|
||||
let totalFiles = 0;
|
||||
let totalBeats = 0;
|
||||
let totalSplit = 0;
|
||||
let filesModified = 0;
|
||||
|
||||
// Collect transformed data in memory for validation (works in dry-run too)
|
||||
const transformed = [];
|
||||
|
||||
for (const dir of DIRS) {
|
||||
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
||||
for (const file of files) {
|
||||
const path = resolve(dir, file);
|
||||
const data = JSON.parse(readFileSync(path, "utf8"));
|
||||
const beats = data.scene?.beats;
|
||||
if (!beats || !Array.isArray(beats)) continue;
|
||||
|
||||
totalFiles++;
|
||||
let splitCount = 0;
|
||||
const newBeats = [];
|
||||
|
||||
for (const beat of beats) {
|
||||
const hasNarration = Boolean(beat.narration);
|
||||
const hasSpeaker = Boolean(beat.speaker);
|
||||
|
||||
if (hasNarration && hasSpeaker) {
|
||||
const dialId = `${beat.id}_d`;
|
||||
|
||||
const narrBeat = {
|
||||
id: beat.id,
|
||||
narration: beat.narration,
|
||||
next: { type: "continue", nextBeatId: dialId },
|
||||
};
|
||||
|
||||
const dialBeat = {
|
||||
id: dialId,
|
||||
speaker: beat.speaker,
|
||||
line: beat.line,
|
||||
...(beat.lineDelivery && { lineDelivery: beat.lineDelivery }),
|
||||
...(beat.activeCharacters && { activeCharacters: beat.activeCharacters }),
|
||||
next: beat.next,
|
||||
};
|
||||
|
||||
newBeats.push(narrBeat, dialBeat);
|
||||
splitCount++;
|
||||
} else {
|
||||
newBeats.push(beat);
|
||||
}
|
||||
|
||||
totalBeats++;
|
||||
}
|
||||
|
||||
data.scene.beats = newBeats;
|
||||
transformed.push({ file, data });
|
||||
|
||||
if (splitCount > 0) {
|
||||
filesModified++;
|
||||
totalSplit += splitCount;
|
||||
|
||||
if (!DRY_RUN) {
|
||||
writeFileSync(path, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Validation (runs against in-memory data, works for both dry-run and real) ──
|
||||
console.log("\n=== Split Results ===");
|
||||
console.log(`Files scanned: ${totalFiles}`);
|
||||
console.log(`Files modified: ${filesModified}`);
|
||||
console.log(`Beats scanned: ${totalBeats}`);
|
||||
console.log(`Beats split: ${totalSplit}`);
|
||||
console.log(`New total beats: ${totalBeats + totalSplit}`);
|
||||
if (DRY_RUN) console.log("(dry-run — no files written)");
|
||||
|
||||
let errors = 0;
|
||||
|
||||
for (const { file, data } of transformed) {
|
||||
const beats = data.scene.beats;
|
||||
const label = file;
|
||||
const beatIds = new Set(beats.map((b) => b.id));
|
||||
|
||||
if (!beatIds.has(data.scene.entryBeatId)) {
|
||||
console.error(`[ERR] ${label}: entryBeatId "${data.scene.entryBeatId}" not found`);
|
||||
errors++;
|
||||
}
|
||||
|
||||
for (const beat of beats) {
|
||||
if (beat.narration && beat.speaker) {
|
||||
console.error(`[ERR] ${label}: beat ${beat.id} still mixed`);
|
||||
errors++;
|
||||
}
|
||||
|
||||
if (beat.next?.type === "continue" && beat.next.nextBeatId) {
|
||||
if (!beatIds.has(beat.next.nextBeatId)) {
|
||||
console.error(`[ERR] ${label}: beat ${beat.id} -> dangling nextBeatId "${beat.next.nextBeatId}"`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (beat.next?.type === "choice") {
|
||||
for (const choice of beat.next.choices ?? []) {
|
||||
const eff = choice.effect;
|
||||
if (eff?.kind === "advance-beat" && eff.targetBeatId) {
|
||||
if (!beatIds.has(eff.targetBeatId)) {
|
||||
console.error(`[ERR] ${label}: beat ${beat.id} choice -> dangling targetBeatId "${eff.targetBeatId}"`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nValidation: ${errors === 0 ? "PASS ✓" : `FAIL — ${errors} error(s)`}`);
|
||||
process.exit(errors ? 1 : 0);
|
||||
Reference in New Issue
Block a user