From 2a97546a7c824ad139088027abfc34974022a8df Mon Sep 17 00:00:00 2001 From: Wickedness Date: Thu, 18 Jun 2026 00:31:54 +0900 Subject: [PATCH] Strengthen camera pose diversity for non-frontal card prompts --- tools/export-card-assets.mjs | 85 +++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/tools/export-card-assets.mjs b/tools/export-card-assets.mjs index 246d4d9..6e0ab48 100644 --- a/tools/export-card-assets.mjs +++ b/tools/export-card-assets.mjs @@ -184,11 +184,11 @@ function damageSpec(stage) { } const CAMERA_ORIENTATION_PLAN = { - normal: ["side", "low", "rear", "high", "side"], - "light-damage": ["low", "side", "high", "rear", "low"], - "medium-damage": ["side", "rear", "high", "low", "side"], - "heavy-damage": ["rear", "low", "high", "side", "rear"], - defeated: ["rear", "high", "low", "side", "rear"], + normal: ["low", "side", "high", "rear", "low", "high"], + "light-damage": ["low", "high", "side", "rear", "low", "high", "side"], + "medium-damage": ["side", "high", "rear", "low", "side", "high", "rear"], + "heavy-damage": ["low", "rear", "high", "side", "high", "low", "rear"], + defeated: ["rear", "high", "low", "side", "low", "high", "rear"], }; const CAMERA_VARIANTS = { @@ -199,14 +199,14 @@ const CAMERA_VARIANTS = { "eye-level portrait-frontal frame with clear silhouette and full torso", ], side: [ - "off-center side-front shot with one step forward and hips leading the movement", - "profile three-quarter shot with the leading shoulder turned toward camera", + "strong profile side shot with one step forward and hips leading the movement", + "45-degree profile turn with the leading shoulder pushed toward camera", "three-quarter side stance with left foot forward and shoulders angled", ], low: [ - "low-angle front composition from knee level, strong lower-body energy and open shoulders", + "low-angle side-knee view with explosive weight shift and open shoulder line", "low side angle showing bent knee, shoulder hit, and torn details", - "low front-left angle with knees bent and the body driving forward", + "extreme low-angle ground-lean with bent knees and body driving forward", ], high: [ "high-angle three-quarter top-down look, knees and torso readable while keeping face and hands visible", @@ -226,14 +226,14 @@ const CAMERA_VARIANTS = { "eye-level chest-forward front read with body beginning a guard turn", ], side: [ - "side-front pivot camera with a shoulder turn and counter-step body shift", + "side pivot camera with shoulder turn and counter-step body shift", "off-center side-frame with one foot forward and hips leading", "low-angle side shot with bent knee, shoulder hit, and torn cloth edge", ], low: [ - "low-angle side-front composition, character descending into impact posture", + "low-angle side composition with character descending into impact posture", "low side angle with a defensive crouch and torn cuff reveal", - "low front-left angle with split-leg recovery and recoil", + "low-side recoil angle with split-leg recovery and body pullback", ], high: [ "high-angle diagonal from above, revealing shoulder and torso recoil details", @@ -253,12 +253,12 @@ const CAMERA_VARIANTS = { "frontal combat lock frame before a body rotation", ], side: [ - "strong side view, body twisted from force so shoulder, torso, and abdomen damage are obvious", + "strong side profile, body twisted from force so shoulder, torso, and abdomen damage are obvious", "rearward three-quarter side-forward action frame while keeping enough face profile", "low-stance three-quarter side view that reveals back-side torso damage and damaged hips", ], low: [ - "high impact side-body twist at knee level, impact showing heavy skirt edge tear", + "high-impact low-side body twist at knee level, impact showing heavy skirt edge tear", "dramatic low-angle body twist shot with one leg braced, one leg carrying weight", "low side frame while balance collapses backward", ], @@ -280,12 +280,12 @@ const CAMERA_VARIANTS = { "frontal collapse pivot exposing shoulder and torso damage", ], side: [ - "dramatic low side view from near ground, torn shoulder and torso damage clearly visible", + "dramatic side view from near ground, torn shoulder and torso damage clearly visible", "wide extreme-side camera with body falling forward and equipment flying", "over-the-shoulder angle with bent lower body and exposed damaged limbs", ], low: [ - "low front-left angle with broken gear dragging across lower body and back", + "low-side angle with broken gear dragging across lower body and back", "dramatic low side shot with leg dropped, shoulders and hips broken open", "ground-skimming low angle on a heavy counter-fall pose", ], @@ -303,8 +303,8 @@ const CAMERA_VARIANTS = { defeated: { front: [ "front-facing defeated collapse with one knee down and torso dropping", - "kneeling front-down composition showing exposed hip and waist damage", - "collapsed chest-forward posture still holding face profile", + "kneeling collapsed posture with exposed hip and waist damage", + "collapsed chest-forward posture still holding a readable face profile", ], side: [ "low angle side-back shot with fallen armor pieces and defeated body posture", @@ -322,7 +322,7 @@ const CAMERA_VARIANTS = { "high-angle side sweep showing full-body give-up posture", ], rear: [ - "rear three-quarter, side-back, or very low side view; show damaged back/shoulder/cloak with readable face profile", + "rear three-quarter, side-back, or very low side view showing damaged back/shoulder/cloak with readable face profile", "side-back defeated fall with broken armor panels and limp arms", "rear collapse frame with shoulder line and torso giving way", ], @@ -331,13 +331,13 @@ const CAMERA_VARIANTS = { const CAMERA_ORIENTATION_RULES = { normal: - "Use one angle from side/rear/low/high per render; only use straight-front when the character identity cannot be preserved.", + "Use one angle from side/rear/low/high per render. Direct front is disallowed unless the character becomes unreadable without it.", "light-damage": - "Prioritize side, rear, low-angle, or high-angle framing; use frontal only when the character identity fails without it.", + "Prioritize side, rear, low-angle, or high-angle framing. Frontal is forbidden except when the face identity would otherwise be lost.", "medium-damage": - "Use energetic side/rear/low/high framing; keep frontal only as a continuity exception.", + "Use energetic side/rear/low/high framing; frontal is a continuity exception only.", "heavy-damage": - "Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos.", + "Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos. Avoid frontal framing as default.", defeated: "Prioritize side/back/low/high framing for collapse and pain continuity; avoid frontal composition unless continuity breaks.", }; @@ -350,6 +350,7 @@ const POSE_ORIENTATION_VARIANTS = { "alert head-tilt stance while hips rotate into a counter sweep", ], side: [ + "side-reverse guard stance with hips and elbow dropping into a counter line", "three-quarter sidestep guard with shoulder cut and heel shift", "profile side-lean into a counter setup with hip twist", "sideward launch stance while back arm sweeps for balance", @@ -358,16 +359,19 @@ const POSE_ORIENTATION_VARIANTS = { "rear-open dodge with shoulder twist and hand reposition", "half-turn recovery where the torso shifts into back-side guard", "body twisted into reverse profile as the support leg checks inward", + "single-foot reverse step with shoulder cut and hand recoil", ], low: [ "low crouch into short strike setup with lead knee weighting", "deep squat guard with chest thrust and shoulder roll", "dropping squat recovery with torso forward and hips stabilized", + "knee-planted drop-step into low profile defense", ], high: [ "vertical pull-up arc with chest open and one knee lifted", "high-angle recovery stance while the weapon arcs above eye level", "raised-angle block with chin tucked and shoulder pivot", + "high-lift defensive reset with twisted hips and backward shoulder pull", ], }, "light-damage": { @@ -380,6 +384,7 @@ const POSE_ORIENTATION_VARIANTS = { "side recoil plus hip turn into defensive counter line", "angled cross step with hand crossing in pain-block recovery", "defensive sidestep with torso twisted to absorb impact", + "off-angle side recoil with knee bend and heel snap backward", ], rear: [ "rear guard with shoulders turned while stepping backward", @@ -390,11 +395,13 @@ const POSE_ORIENTATION_VARIANTS = { "single-knee drop into defensive crouch after recoil", "low-angle recoil where knee bends to absorb a glancing blow", "weight drops to lower center with foot drag recovery", + "grounding low guard with bent knees and shoulder drop", ], high: [ "leaning recovery shot with head lowered and shoulders lifting", "upper-body pull-up after impact while one leg pushes backward", "rear shoulder guard raised from above and angled backward", + "high-arc recoil with hip pivot and weapon reset", ], }, "medium-damage": { @@ -407,6 +414,7 @@ const POSE_ORIENTATION_VARIANTS = { "twisted side-collapse into half-dodge and weapon re-latch", "violent side pivot with hips dropping for recovery", "broadside recoil with shoulders and waist taking force", + "explosive side dodge into a broken counter-stance", ], rear: [ "rear-lean dodge, one shoulder clipped and weapon hand recovering", @@ -417,11 +425,13 @@ const POSE_ORIENTATION_VARIANTS = { "deep low crouch with one knee buckling and one arm reaching upward", "forced fall-to-knee pose with shoulder and thigh locked in strain", "full-bent-leg recovery where hips torque to stop momentum", + "low side-crawl recovery with one knee dragging to regain control", ], high: [ "high-arc dodge with upper body flaring open from pain", "upward chest-raise recovery where weapon follows steep line", "torso recovery angle showing damage across chest and abdomen", + "high-side rebound where chest turns toward the damage vector", ], }, "heavy-damage": { @@ -434,6 +444,7 @@ const POSE_ORIENTATION_VARIANTS = { "violent side-fall pivot with one leg giving and one hand bracing", "off-axis side collision posture, knees flexed and spine twisted", "angry side-block stance with torso collapsing then pushing up", + "twisted side-lunge collapse with weapon trailing and heel scrape", ], rear: [ "rear collapse step while torso twists to keep balance", @@ -444,18 +455,21 @@ const POSE_ORIENTATION_VARIANTS = { "floor-level collapse with one hand clawing forward and one foot extended", "kneeling recoil pose where upper body drops first", "low recovery attempt with bent knees and hips dragged sideways", + "knee-drop recovery with one leg sliding backward under pain", ], high: [ "upward shake-out motion while keeping hips off-line from injury", "high-angle pain-shift where torso rises briefly then sinks", "upper-body lift against collapsed armor and ground pull", + "high recoil recovery while shoulder and hip line are clearly broken open", ], }, defeated: { front: [ - "collapsed front-heavy breath pose with one knee low and body folded", - "kneeling front collapse while torso leans and shoulders drop", + "collapsed side-heavy breath pose with one knee low and body folded", + "kneeling sideward collapse while torso leans and shoulders drop", "final effort reach-forward pose with one hand searching for footing", + "side-forward defeated hold while the lower body cannot stabilize", ], side: [ "sideways collapse with legs apart and shoulders dropping across chest line", @@ -549,6 +563,10 @@ function pickDiverseOption(list, seed, stage, step) { return list[index]; } +function normalizeOrientation(orientation) { + return orientation === "front" ? "side" : orientation; +} + function pickCameraForStage(spec, character, stage) { const orientationPlan = CAMERA_ORIENTATION_PLAN[spec.label] ?? CAMERA_ORIENTATION_PLAN.normal; const orientationSeed = charSeed(`${character.id}-${character.scene}-${spec.label}`); @@ -562,10 +580,11 @@ function pickCameraForStage(spec, character, stage) { } resolvedIndex = (resolvedIndex + 1) % orientationPlan.length; } - const orientation = orientationPlan[resolvedIndex]; + const orientation = normalizeOrientation(orientationPlan[resolvedIndex]); const stagePool = (CAMERA_VARIANTS[spec.label] ?? CAMERA_VARIANTS.normal)[orientation] ?? - CAMERA_VARIANTS.normal[orientation]; + CAMERA_VARIANTS.normal[orientation] ?? + CAMERA_VARIANTS.normal.side; const phrase = pickDiverseOption( stagePool, charSeed(`${character.id}-${character.scene}`), @@ -579,10 +598,11 @@ function pickCameraForStage(spec, character, stage) { } function pickPoseModifier(spec, character, stage, orientation) { + const normalizedOrientation = normalizeOrientation(orientation); const list = POSE_VARIANTS[spec.label] ?? POSE_VARIANTS.normal; const orientationBonus = - POSE_ORIENTATION_VARIANTS[spec.label]?.[orientation] ?? - POSE_ORIENTATION_VARIANTS.normal?.[orientation] ?? + POSE_ORIENTATION_VARIANTS[spec.label]?.[normalizedOrientation] ?? + POSE_ORIENTATION_VARIANTS.normal?.[normalizedOrientation] ?? []; const posePool = orientationBonus.concat(list); return pickDiverseOption( @@ -609,6 +629,8 @@ function promptFor(character, stage) { const broadDamageGate = "broad-damage gate: if the damaged outfit mostly reads as many fine cracks, tiny scratches, lace frays, thorny threads, or vine-like tendrils, the card fails even when the character is exposed. Each major damaged zone should include at least one large missing panel, wide split seam, snapped fastener, sheared plate, or heavy hanging remnant that can be understood at card size."; const camera = pickCameraForStage(spec, character, stage); + const angleControl = + "Angle strictness: this render must use side/rear/low/high framing first; direct frontal composition is disallowed unless the model absolutely cannot preserve face identity."; const poseModifier = pickPoseModifier(spec, character, stage, camera.orientation); return [ "Use case: stylized-concept", @@ -622,8 +644,9 @@ function promptFor(character, stage) { `Identity lock across stages: preserve the exact same person in every damage stage--same race/species, ear shape, face structure, eye shape, nose, lips, skin tone, hair color, hair length, hairstyle, body proportions, bust shape, and mature presence. Damage may change clothing, armor, pose, expression, dirt, sweat, and injury reaction, but must never change a human into an elf, change ear shape, change hair color, change age, change ethnicity, or replace the character with a different-looking person.`, `Personality and pose: ${character.personality}; ${character.pose}; ${spec.poseNote}; ${poseModifier}.`, `Facial expression: ${spec.expression}.`, - `Camera/view variation: ${camera.orientation} angle. Composition directive: ${camera.phrase}.`, - "Across the character's five damage stages, rotate between side, rear, low-angle, and high-angle views as the primary framing, and only use frontal composition when the identity requires it.", + `Camera/view variation: ${camera.orientation} angle. Composition directive: ${camera.phrase}.`, + angleControl, + "Across the character's five damage stages, rotate between side, rear, low-angle, and high-angle views as the primary framing, and only use frontal composition when the identity cannot be preserved.", `Camera constraint: ${CAMERA_ORIENTATION_RULES[spec.label] ?? CAMERA_ORIENTATION_RULES.normal}`, `Adult visual appeal: ${character.appealFocus}; ${adultAppeal}.`, `Scene/backdrop: ${character.scene}; ${elementName} elemental atmosphere; no other characters.`,