prompt-template.js 7.93 KB
// Stage2 six-section prompt assembler (dev-plan.md T7; template source of
// truth: docs/todo/dressup-dance-room-mvcp.md M.15, one-step version
// confirmed by the S1 spike). Includes the footwear fix M.15 flagged as
// still outstanding ("her footwear... is retained unchanged" / "does not
// affect her footwear") so the same "shoes self-selected" drift the S1
// spike hit doesn't recur.
const MAX_DESCRIPTOR_WORDS = 40; // ~2-3 concrete features, per docs/h3skill.md's own guidance
const DETAILED_DESCRIPTION_MAX_WORDS = 500; // H6

function words(text) {
  return text.trim().split(/\s+/).filter(Boolean);
}

function truncateWords(text, maxWords) {
  const w = words(text);
  return w.length <= maxWords ? text.trim() : w.slice(0, maxWords).join(" ");
}

function wordCount(text) {
  return words(text).length;
}

function extractSection(prompt, name, nextName) {
  const re = new RegExp(`^${name}:\\n([\\s\\S]*?)\\n\\n${nextName}:`, "m");
  const match = prompt.match(re);
  return match ? match[1] : "";
}

function render({ appearanceDescriptor, garmentDescriptor, motionDescriptor }) {
  return `subject_definitions:
<Subject 1> is the young woman whose visual identity is defined by <Picture 1>: ${appearanceDescriptor}. Preserve her face, hairstyle, and body exactly as shown; only her outfit, pose, motion, and framing change in the target video.
<Picture 1> is the character reference frame for <Subject 1>, showing her face, hairstyle, and body in a neutral standing pose, wearing a neutral outfit that does not appear in the target video; her footwear as shown here is retained unchanged in the target video.
<Picture 2> is the garment reference image: ${garmentDescriptor}, laid flat with no person wearing it. Only its garment attributes (silhouette, color, fabric, trim, cut) transfer onto <Subject 1>; no other content of this image appears in the target video, and it does not affect her footwear.
<Video 1> is the reference choreography clip. It defines the dance structure, phrasing, and camera rhythm that the target video follows; it is not a visual identity source, and none of its performer, background, or imagery appears in the target video.
<Audio 1> is the background music track carried on <Video 1>, reused verbatim as the music bed of the target video. It contains no speech.

summary:
[reference generation + attribute transfer + audio reuse] The target video shows <Subject 1>, now wearing the outfit transferred from <Picture 2>, performing ${motionDescriptor} in a vertical studio setting, following the pacing and camera rhythm established in <Video 1>, with the music of <Audio 1> reused as its track.

retention_analysis:
<Subject 1> (appears in [Shot 1]): fully_preserved - her face, hairstyle, and body are retained unchanged throughout; her outfit changes to the garment shown in <Picture 2>, her footwear stays as shown in <Picture 1>.
<Picture 1>: fully_preserved - it supplies the face, hairstyle, body, and footwear of <Subject 1> and the opening framing of the shot.
<Picture 2>: attribute_transfer - only the garment's silhouette, color, fabric, and trim transfer onto <Subject 1>; the image itself does not appear.
<Video 1>: weak_reference - only the choreography phrasing and the camera rhythm are referenced; no performer, background, or imagery from it is retained.
<Audio 1>: fully_copy - the music track is reused verbatim as the non-diegetic music bed.

detailed_description:
A vibrant, mobile-first vertical dance video with warm, glossy studio lighting, saturated color, and a clean contemporary look sustained from the first frame to the last. [Shot 1] The shot begins from <Picture 1>, with <Subject 1> now wearing the outfit transferred from <Picture 2>. She stands centered in a medium-full vertical frame against a softly lit seamless backdrop that falls off into gentle shadow toward the edges. A warm key light sits slightly camera-left, a cooler rim light separates her shoulders and hair from the background, and a soft fill keeps her face evenly exposed. She begins in a relaxed neutral stance, weight settled on her back foot, hands loose at her sides, chin level with the lens. On the first musical accent she starts ${motionDescriptor}, and the routine unfolds continuously from there with no cut. Her weight transfers smoothly from foot to foot on the beat, hips leading each transfer and shoulders following a fraction behind, so the movement reads as one connected chain rather than a series of separate poses. Her arms trace clean, unhurried arcs that extend fully at the peak of each phrase and soften on the release, wrists relaxed and fingers naturally extended. Between phrases she adds small directional changes: a quarter turn away from the lens and back, a step that closes distance with the camera, a shift of gaze that returns to the lens on the downbeat. The camera holds a Static Shot through the opening beats, then performs a slow Push In with small amplitude at slow speed, tightening from medium-full to medium while keeping her head and hands inside the frame at every point of the routine. Her expression stays confident and playful, with a light smile and eyes that meet the lens at the end of each phrase. The fabric of the transferred outfit swings and settles naturally with each step, turn, and arm extension, and her hair carries the momentum of her head movement half a beat behind it. The pacing, phrasing, and camera rhythm follow the structure established in <Video 1>: the routine builds energy through the middle of the clip, reaches its widest and fastest movement around the two-thirds point, then settles back toward stillness. The closing beats return her to a stance, framing, and lighting that closely match her opening position, so the clip loops cleanly for repeated live playback. No text, graphics, watermarks, or overlays appear anywhere in the frame at any point.

overall_soundscape:
Soft ambient room tone of an enclosed studio sits under the music, with faint fabric rustle as <Subject 1> moves and light rhythmic footfall on the floor as her weight transfers. No speech, no crowd, and no off-screen sound events occur at any point.

non_diegetic_music:
An upbeat instrumental track drives the routine from the first frame to the last; its tempo, instrumentation, and dynamics are copied verbatim from <Audio 1>. Its accents align with the weight transfers and arm extensions of <Subject 1>, and its energy build through the middle of the clip matches the build in her movement.`;
}

// Builds the Stage2 prompt from a character/garment/dance-ref triple,
// truncating descriptors that would blow the word budgets in M.15.1.
// character/garment/danceRef are the raw store records (M.3/M.4/M.5).
function buildStage2Prompt({ character, garment, danceRef }) {
  let appearanceDescriptor = truncateWords(character.appearanceDescriptor, MAX_DESCRIPTOR_WORDS);
  let garmentDescriptor = truncateWords(garment.appearanceDescriptor, MAX_DESCRIPTOR_WORDS);
  let motionDescriptor = danceRef.motionDescriptor;

  let prompt = render({ appearanceDescriptor, garmentDescriptor, motionDescriptor });
  const detailedDescriptionWords = wordCount(extractSection(prompt, "detailed_description", "overall_soundscape"));

  // motionDescriptor is the only placeholder inside detailed_description
  // (H6's 350-500 word section) — if it alone pushes the section over
  // budget, trim it and re-render. It appears in `summary` too, but that
  // section has no word cap (H10 caps it by sentence count, not words).
  if (detailedDescriptionWords > DETAILED_DESCRIPTION_MAX_WORDS) {
    const excess = detailedDescriptionWords - DETAILED_DESCRIPTION_MAX_WORDS;
    const motionWords = wordCount(motionDescriptor);
    motionDescriptor = truncateWords(motionDescriptor, Math.max(1, motionWords - excess));
    prompt = render({ appearanceDescriptor, garmentDescriptor, motionDescriptor });
  }

  return { prompt, appearanceDescriptor, garmentDescriptor, motionDescriptor };
}

module.exports = { buildStage2Prompt, truncateWords, wordCount, extractSection, MAX_DESCRIPTOR_WORDS, DETAILED_DESCRIPTION_MAX_WORDS };