diff --git a/tools/export-card-assets.mjs b/tools/export-card-assets.mjs index 6e0ab48..f471379 100644 --- a/tools/export-card-assets.mjs +++ b/tools/export-card-assets.mjs @@ -184,19 +184,19 @@ function damageSpec(stage) { } const CAMERA_ORIENTATION_PLAN = { - 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"], + normal: ["low", "side", "high", "low", "rear", "high", "side", "low", "rear", "high"], + "light-damage": ["low", "high", "side", "rear", "high", "low", "side", "rear", "high", "low", "side"], + "medium-damage": ["side", "low", "high", "rear", "side", "high", "low", "rear", "high", "side", "low", "rear"], + "heavy-damage": ["low", "rear", "high", "side", "high", "low", "rear", "low", "high", "side", "rear", "high"], + defeated: ["rear", "low", "high", "side", "high", "rear", "low", "side", "high", "low", "rear", "side"], }; const CAMERA_VARIANTS = { normal: { front: [ - "front three-quarter composition with a slight head turn and visible full body", - "frontal low-shoulder shot while the character leans into the lens", - "eye-level portrait-frontal frame with clear silhouette and full torso", + "low side-three-quarter composition with a shoulder twist that keeps the body angled", + "three-quarter profile composition with one foot forward and hips leading the movement", + "slanted side-front composition where the face stays readable through a short turn", ], side: [ "strong profile side shot with one step forward and hips leading the movement", @@ -221,9 +221,9 @@ const CAMERA_VARIANTS = { }, "light-damage": { front: [ - "front three-quarter composition with a small shoulder turn and first-impact recoil", - "frontal low-shoulder shot while the character leans into the lens", - "eye-level chest-forward front read with body beginning a guard turn", + "front-canted three-quarter read with shoulders turned against impact and one side stepping in", + "low-front slant with torso dropping into counter step and side-shifted hips", + "eye-level angled guard frame that keeps the strike angle visible in the shoulder and waist", ], side: [ "side pivot camera with shoulder turn and counter-step body shift", @@ -248,9 +248,9 @@ const CAMERA_VARIANTS = { }, "medium-damage": { front: [ - "front three-quarter impact pose with shoulders coiling against a side hit", - "low-front composition with body weight shifted to one weakened leg", - "frontal combat lock frame before a body rotation", + "front-angled three-quarter combat lock with shoulder and hip crossing against a force", + "low-front recovery turn with one leg absorbing impact and waist twisted for balance", + "angled forward body-lock pose as the torso rotates into a side-facing response", ], side: [ "strong side profile, body twisted from force so shoulder, torso, and abdomen damage are obvious", @@ -275,9 +275,9 @@ const CAMERA_VARIANTS = { }, "heavy-damage": { front: [ - "front-heavy combat collapse with chest and shoulder load on fractured gear", - "low-front frame with broken balance and heavy leg recovery", - "frontal collapse pivot exposing shoulder and torso damage", + "low-front collapse angle where one shoulder drops while body rotates around a failing knee", + "aggressive forward-lean collapse with side weight shift and heavy gear drag", + "frontal-to-side rebound pose with fractured guard and broken upper-body support", ], side: [ "dramatic side view from near ground, torn shoulder and torso damage clearly visible", @@ -302,9 +302,9 @@ const CAMERA_VARIANTS = { }, defeated: { front: [ - "front-facing defeated collapse with one knee down and torso dropping", - "kneeling collapsed posture with exposed hip and waist damage", - "collapsed chest-forward posture still holding a readable face profile", + "front-canted defeated collapse with one knee down and torso dropping into recoil", + "kneeling recoil frame with hip-heavy fall and waist opening", + "collapsed chest-forward posture with profile drift and controlled face read", ], side: [ "low angle side-back shot with fallen armor pieces and defeated body posture", @@ -331,13 +331,13 @@ const CAMERA_VARIANTS = { const CAMERA_ORIENTATION_RULES = { normal: - "Use one angle from side/rear/low/high per render. Direct front is disallowed unless the character becomes unreadable without it.", + "Use one angle from side/rear/low/high per render. Low-angle and high-angle shots should be used at least 40% of the time across the deck.", "light-damage": - "Prioritize side, rear, low-angle, or high-angle framing. Frontal is forbidden except when the face identity would otherwise be lost.", + "Prioritize low-angle, rear, side, or high-angle framing. Frontal reads are discouraged unless the face identity would otherwise be lost.", "medium-damage": - "Use energetic side/rear/low/high framing; frontal is a continuity exception only.", + "Use energetic side/rear/low/high framing; force low-angle and high-angle alternation across nearby cards.", "heavy-damage": - "Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos. Avoid frontal framing as default.", + "Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos; high/low angles must dominate this stage.", defeated: "Prioritize side/back/low/high framing for collapse and pain continuity; avoid frontal composition unless continuity breaks.", }; @@ -345,9 +345,9 @@ const CAMERA_ORIENTATION_RULES = { const POSE_ORIENTATION_VARIANTS = { normal: { front: [ - "direct forward lunge with one shoulder shielding the exposed side", - "guarded forward stride with weapon hand pulling the line", - "alert head-tilt stance while hips rotate into a counter sweep", + "forward slash-dodge blend with hips pivoting and back foot digging for balance", + "low split-step into guarded lunge while shoulders drift around the impact angle", + "angled stride with upper body opening and hips torqued toward counter line", ], side: [ "side-reverse guard stance with hips and elbow dropping into a counter line", @@ -376,9 +376,9 @@ const POSE_ORIENTATION_VARIANTS = { }, "light-damage": { front: [ - "quick front guard split with one shoulder collapsing and re-centering", - "controlled recoil then short rush toward the camera", - "frontal reset where arms and hips overcorrect from the first hit", + "quick forward guard split with one shoulder collapsing and re-centering", + "controlled recoil then short dash that reclaims forward ground", + "recovery stride where arms and hips overcorrect while turning away from impact", ], side: [ "side recoil plus hip turn into defensive counter line", @@ -406,9 +406,9 @@ const POSE_ORIENTATION_VARIANTS = { }, "medium-damage": { front: [ - "front recoil then forced counter as hip and rib line rotate", - "forward drive with one arm absorbing shock and the other checking distance", - "angry step-in with chest thrust while lower leg braces", + "recoil burst then forced counter as hip and rib line rotate", + "forward drive with one arm absorbing shock and the other checking distance while hips pivot", + "angry step-in with chest thrust while lower leg braces through an angled stance", ], side: [ "twisted side-collapse into half-dodge and weapon re-latch", @@ -436,9 +436,9 @@ const POSE_ORIENTATION_VARIANTS = { }, "heavy-damage": { front: [ - "desperate front-facing counter while dropping into fractured posture", + "desperate front counter while dropping into fractured posture", "broken-forward guard, hands and feet split to catch falling weight", - "aggressive front rebound with shoulder collapsing inward", + "aggressive rebound with shoulder collapsing inward and hips dragging back", ], side: [ "violent side-fall pivot with one leg giving and one hand bracing", @@ -466,9 +466,9 @@ const POSE_ORIENTATION_VARIANTS = { }, defeated: { front: [ - "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", + "collapsed side-heavy breath pose with one knee low and body folded into recoil", + "kneeling sideward collapse while torso twists and shoulders drop", + "final effort reach-forward pose with one hand searching for footing while upper body falls", "side-forward defeated hold while the lower body cannot stabilize", ], side: [ @@ -547,6 +547,24 @@ const POSE_VARIANTS = { ], }; +const LOW_HIGH_CAMERA_PHRASES = { + low: [ + "low-angle ground-lean with one knee absorbing impact and hips thrust backward", + "low-side dramatic weight shift with torn cloth and visible foot torque", + "ultra low perspective from knee height showing stagger and impact on armor seams", + ], + high: [ + "high-angle top-down recoil shot while torso and hips remain readable", + "elevated overhead angle with character weight dropping toward ground and torn panels opening", + "steep high-angle cut to show back-foot recovery, impact direction, and body distortion", + ], + rear: [ + "high-tension rear-side sweep showing opened back panel and collapsing shoulder", + "side-rear pivot with torn coat and one leg stepping into empty air", + "rear three-quarter collapse angle showing exposed back and waist line", + ], +}; + function charSeed(text) { let hash = 2166136261; for (let i = 0; i < text.length; i += 1) { @@ -591,9 +609,19 @@ function pickCameraForStage(spec, character, stage) { stage, 17, ); + const angleAddonPool = LOW_HIGH_CAMERA_PHRASES[orientation]; + const extraPhrase = angleAddonPool + ? pickDiverseOption( + angleAddonPool, + charSeed(`${character.id}-${character.affinity}-${character.scene}`), + stage, + 29, + ) + : null; + const anglePhrase = extraPhrase === null ? phrase : `${phrase} ${extraPhrase}`; return { orientation, - phrase, + phrase: anglePhrase, }; }