/** * Structurally pin each scene character's locked costume into the scene prompt. * * For every character listed on the scene that has a registered profile with a * `costume`, append a deterministic clause `Keep in exactly.` * using the profile's canonical display name. Characters without a costume do * not affect the prompt, and existing clauses are not appended twice. */ export function appendCostumeClauses( promptText: string, sceneCharacters: string[], costumeByLowerName: Map, ): string { let result = promptText; const appended = new Set(); for (const sceneName of sceneCharacters) { const key = sceneName.trim().toLowerCase(); if (!key || appended.has(key)) continue; const profile = costumeByLowerName.get(key); const costume = profile?.costume?.trim(); if (!profile || !costume) continue; appended.add(key); const clause = `Keep ${profile.name} in ${costume} exactly.`; if (result.includes(clause)) continue; result = result.length > 0 ? `${result} ${clause}` : clause; } return result; } /** * Structurally pin each scene character's relative size into the scene prompt. * * `proportions` is an absolute phrase that can name other cast members, so a * per-character clause carries relative scale without calculating ratios for * each shot. Missing scale data leaves the prompt byte-identical. */ export function appendScaleClauses( promptText: string, sceneCharacters: string[], scaleByLowerName: Map, ): string { let result = promptText; const appended = new Set(); for (const sceneName of sceneCharacters) { const key = sceneName.trim().toLowerCase(); if (!key || appended.has(key)) continue; const profile = scaleByLowerName.get(key); const proportions = profile?.proportions?.trim(); if (!profile || !proportions) continue; appended.add(key); const clause = `Scale: ${profile.name} is ${proportions}.`; if (result.includes(clause)) continue; result = result.length > 0 ? `${result} ${clause}` : clause; } return result; }