fix(engine): pin entry-beat roster to the plan in Phase B

The Painter composites exactly plan.entryActiveCharacters into the entry
frame (the same roster the Cinematographer framed). Phase B is told to
reuse that roster, but only the entry beat's id was code-enforced — so an
LLM slip could leave a character in the painted frame that the runtime
entry beat says isn't there. Pin activeCharacters onto the plan's entry
beat as a last line of defense, mirroring the existing id pin.

Speaker is intentionally left to the prompt: it's coupled to line/TTS, so
overwriting it could mis-attribute or orphan Phase B's dialogue.

Addresses Copilot review feedback on PR #27.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
yuanzonghao
2026-06-04 15:48:14 +08:00
parent 3bf5c92841
commit efe021d886
+9
View File
@@ -497,6 +497,15 @@ export async function runWriterBeats(
beats = renameBeatId(beats, beats[0]!.id, plan.entryBeatId);
}
// 把入场 beat 的 roster 钉成 plan 的:画师合成进帧的正是
// plan.entryActiveCharacters,运行时入场 beat 必须显示同一批人(与上面钉
// id 同理)。speaker 故意不钉——它和 line/TTS 耦合,强行覆盖会错配台词。
const entryRoster =
plan.entryActiveCharacters.length > 0 ? plan.entryActiveCharacters : undefined;
beats = beats.map((b) =>
b.id === plan.entryBeatId ? { ...b, activeCharacters: entryRoster } : b,
);
return {
beats,
storyStatePatch: coerceStoryStatePatch(parsed.storyStatePatch),