Improve card image camera and pose diversity rules
This commit is contained in:
@@ -191,6 +191,39 @@ const CAMERA_ORIENTATION_PLAN = {
|
||||
defeated: ["rear", "low", "high", "side", "high", "rear", "low", "high", "side", "rear", "low", "high", "side"],
|
||||
};
|
||||
|
||||
const CAMERA_ORIENTATION_TEMPLATES = {
|
||||
normal: [
|
||||
["side", "high", "low", "rear", "low"],
|
||||
["low", "rear", "high", "side", "high"],
|
||||
["rear", "low", "side", "high", "rear"],
|
||||
["high", "rear", "low", "side", "high"],
|
||||
],
|
||||
"light-damage": [
|
||||
["low", "rear", "high", "side", "low"],
|
||||
["side", "low", "rear", "high", "rear"],
|
||||
["rear", "low", "side", "high", "rear"],
|
||||
["high", "rear", "side", "low", "high"],
|
||||
],
|
||||
"medium-damage": [
|
||||
["side", "low", "rear", "high", "high"],
|
||||
["low", "high", "side", "rear", "low"],
|
||||
["rear", "side", "low", "high", "rear"],
|
||||
["high", "rear", "side", "low", "high"],
|
||||
],
|
||||
"heavy-damage": [
|
||||
["low", "rear", "high", "side", "rear"],
|
||||
["high", "rear", "side", "low", "high"],
|
||||
["side", "rear", "high", "low", "side"],
|
||||
["rear", "high", "side", "low", "rear"],
|
||||
],
|
||||
defeated: [
|
||||
["rear", "low", "high", "side", "high"],
|
||||
["low", "rear", "high", "side", "rear"],
|
||||
["side", "high", "low", "rear", "high"],
|
||||
["high", "rear", "rear", "low", "side"],
|
||||
],
|
||||
};
|
||||
|
||||
const CAMERA_VARIANTS = {
|
||||
normal: {
|
||||
side: [
|
||||
@@ -317,6 +350,21 @@ const CAMERA_ORIENTATION_RULES = {
|
||||
"Prioritize side/rear/back/low/high framing for collapse and pain continuity; avoid frontal composition even when convenient.",
|
||||
};
|
||||
|
||||
const CAMERA_STAGE_REQUIREMENTS = {
|
||||
normal: { 1: "high", 2: "low" },
|
||||
"light-damage": { 1: "high", 2: "low" },
|
||||
"medium-damage": { 1: "high", 2: "low" },
|
||||
"heavy-damage": { 1: "high", 2: "low" },
|
||||
defeated: { 1: "high", 2: "low" },
|
||||
};
|
||||
|
||||
function templateOrientation(spec, character, stage) {
|
||||
const templates = CAMERA_ORIENTATION_TEMPLATES[spec.label] ?? CAMERA_ORIENTATION_TEMPLATES.normal;
|
||||
const templateSeed = charSeed(`${character.id}-${spec.label}-template`);
|
||||
const template = templates[templateSeed % templates.length] ?? templates[0];
|
||||
return template[stage % template.length] ?? null;
|
||||
}
|
||||
|
||||
const STAGE_POSE_FAMILY_PREFERENCE = {
|
||||
normal: ["impact", "defensive", "forward-motion", "ground-recovery", "airborne"],
|
||||
"light-damage": ["defensive", "impact", "ground-recovery", "airborne"],
|
||||
@@ -325,6 +373,14 @@ const STAGE_POSE_FAMILY_PREFERENCE = {
|
||||
defeated: ["ground-recovery", "defensive", "impact", "ground-recovery"],
|
||||
};
|
||||
|
||||
const STAGE_POSE_FAMILY_REQUIREMENTS = {
|
||||
normal: ["defensive", "impact", "forward-motion", "ground-recovery", "airborne"],
|
||||
"light-damage": ["defensive", "impact", "airborne", "ground-recovery", "forward-motion"],
|
||||
"medium-damage": ["ground-recovery", "defensive", "impact", "airborne", "forward-motion"],
|
||||
"heavy-damage": ["ground-recovery", "defensive", "impact", "forward-motion", "airborne"],
|
||||
defeated: ["ground-recovery", "defensive", "impact", "forward-motion", "airborne"],
|
||||
};
|
||||
|
||||
const POSE_ORIENTATION_VARIANTS = {
|
||||
normal: {
|
||||
front: [
|
||||
@@ -564,10 +620,6 @@ function pickDiverseOption(list, seed, stage, step) {
|
||||
return list[index];
|
||||
}
|
||||
|
||||
function normalizeToDirectionDistance(length, from, to) {
|
||||
return (to - from + length) % length;
|
||||
}
|
||||
|
||||
function firstIndexForOrientation(plan, wanted) {
|
||||
for (let i = 0; i < plan.length; i += 1) {
|
||||
if (normalizeOrientation(plan[i]) === wanted) {
|
||||
@@ -616,67 +668,52 @@ function poseFamily(phrase) {
|
||||
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}`);
|
||||
const previousOrientationIndex = stage > 0 ? (orientationSeed + stage - 1) % orientationPlan.length : -1;
|
||||
const previousOrientation = previousOrientationIndex === -1 ? null : orientationPlan[previousOrientationIndex];
|
||||
let resolvedIndex = (orientationSeed + stage) % orientationPlan.length;
|
||||
for (let attempt = 0; attempt < orientationPlan.length; attempt += 1) {
|
||||
const orientation = orientationPlan[resolvedIndex];
|
||||
if (orientation !== previousOrientation) {
|
||||
break;
|
||||
}
|
||||
resolvedIndex = (resolvedIndex + 1) % orientationPlan.length;
|
||||
}
|
||||
const preferredTemplateOrientation = templateOrientation(spec, character, stage);
|
||||
const requiredOrientation = CAMERA_STAGE_REQUIREMENTS[spec.label]?.[stage];
|
||||
const historyKey = character.id;
|
||||
const history = cameraHistoryByCharacter.get(historyKey) ?? [];
|
||||
const needLow = stage >= 2 && !history.includes("low");
|
||||
const needHigh = stage >= 1 && !history.includes("high");
|
||||
if (needLow) {
|
||||
const lowIndex = firstIndexForOrientation(orientationPlan, "low");
|
||||
if (lowIndex !== -1) {
|
||||
resolvedIndex = (resolvedIndex + normalizeToDirectionDistance(orientationPlan.length, resolvedIndex, lowIndex)) % orientationPlan.length;
|
||||
let resolvedIndex = firstIndexForOrientation(
|
||||
orientationPlan,
|
||||
preferredTemplateOrientation,
|
||||
);
|
||||
if (resolvedIndex === -1) {
|
||||
resolvedIndex = (orientationSeed + stage) % orientationPlan.length;
|
||||
}
|
||||
const tailNeedOrientation = requiredOrientation ?? (() => {
|
||||
if (stage === 4 && !history.includes("rear")) {
|
||||
const tailSeed = charSeed(`${character.id}-${spec.label}-tail`);
|
||||
return tailSeed % 2 === 0 ? "rear" : "side";
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
|
||||
if (requiredOrientation) {
|
||||
const requiredIndex = firstIndexForOrientation(orientationPlan, requiredOrientation);
|
||||
if (requiredIndex !== -1 && !history.includes(requiredOrientation)) {
|
||||
resolvedIndex = requiredIndex;
|
||||
}
|
||||
} else if (tailNeedOrientation) {
|
||||
const tailOrientationIndex = firstIndexForOrientation(orientationPlan, tailNeedOrientation);
|
||||
if (tailOrientationIndex !== -1) {
|
||||
resolvedIndex = tailOrientationIndex;
|
||||
}
|
||||
} else if (!history.includes("low") && stage >= 2) {
|
||||
const fallbackLowIndex = firstIndexForOrientation(orientationPlan, "low");
|
||||
if (fallbackLowIndex !== -1 && !history.includes("low")) {
|
||||
resolvedIndex = fallbackLowIndex;
|
||||
}
|
||||
} else if (!history.includes("high") && stage >= 1) {
|
||||
const fallbackHighIndex = firstIndexForOrientation(orientationPlan, "high");
|
||||
if (fallbackHighIndex !== -1 && !history.includes("high")) {
|
||||
resolvedIndex = fallbackHighIndex;
|
||||
}
|
||||
}
|
||||
if (needHigh) {
|
||||
const highIndex = firstIndexForOrientation(orientationPlan, "high");
|
||||
if (highIndex !== -1) {
|
||||
resolvedIndex = (resolvedIndex + normalizeToDirectionDistance(orientationPlan.length, resolvedIndex, highIndex)) % orientationPlan.length;
|
||||
}
|
||||
}
|
||||
let forced = false;
|
||||
if (stage === 1) {
|
||||
const stageSeed = charSeed(`${character.id}-${character.scene}-${character.weapon}`);
|
||||
const forcedOrientation = stageSeed % 2 === 0 ? "high" : "low";
|
||||
const forcedOrientationIndex = firstIndexForOrientation(orientationPlan, forcedOrientation);
|
||||
if (forcedOrientationIndex !== -1) {
|
||||
resolvedIndex = forcedOrientationIndex;
|
||||
forced = true;
|
||||
}
|
||||
}
|
||||
if (stage === 2) {
|
||||
if (!history.includes("low")) {
|
||||
const forcedLowIndex = firstIndexForOrientation(orientationPlan, "low");
|
||||
if (forcedLowIndex !== -1) {
|
||||
resolvedIndex = forcedLowIndex;
|
||||
forced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stage === 3 && !history.includes("high")) {
|
||||
const forcedHighIndex = firstIndexForOrientation(orientationPlan, "high");
|
||||
if (forcedHighIndex !== -1) {
|
||||
resolvedIndex = forcedHighIndex;
|
||||
forced = true;
|
||||
}
|
||||
}
|
||||
if (stage === 4 && !history.includes("low")) {
|
||||
const forcedLowIndex = firstIndexForOrientation(orientationPlan, "low");
|
||||
if (forcedLowIndex !== -1) {
|
||||
resolvedIndex = forcedLowIndex;
|
||||
forced = true;
|
||||
}
|
||||
}
|
||||
if (history.length > 0 && (!forced || orientationPlan[resolvedIndex] === history[history.length - 1])) {
|
||||
resolvedIndex = nextDifferentIndex(orientationPlan, resolvedIndex, history[history.length - 1]);
|
||||
if (history.length > 0 && orientationPlan[resolvedIndex] === history[history.length - 1]) {
|
||||
resolvedIndex = nextDifferentIndex(
|
||||
orientationPlan,
|
||||
resolvedIndex,
|
||||
history[history.length - 1],
|
||||
);
|
||||
}
|
||||
|
||||
const finalOrientation = normalizeOrientation(orientationPlan[resolvedIndex]);
|
||||
@@ -724,6 +761,12 @@ function pickPoseModifier(spec, character, stage, orientation) {
|
||||
STAGE_POSE_FAMILY_PREFERENCE.normal;
|
||||
const familyHistory = poseFamilyHistoryByCharacter.get(character.id) ?? [];
|
||||
const avoidedFamilies = new Set(familyHistory.slice(-3));
|
||||
const requiredFamily = STAGE_POSE_FAMILY_REQUIREMENTS[spec.label]?.[stage] ??
|
||||
STAGE_POSE_FAMILY_REQUIREMENTS.normal[stage] ??
|
||||
null;
|
||||
const requiredFamilyPool = requiredFamily
|
||||
? posePool.filter((pose) => poseFamily(pose) === requiredFamily && !avoidedFamilies.has(requiredFamily))
|
||||
: [];
|
||||
const preferredPool = posePool.filter(
|
||||
(pose) =>
|
||||
preferredFamilies.includes(poseFamily(pose)) &&
|
||||
@@ -731,7 +774,9 @@ function pickPoseModifier(spec, character, stage, orientation) {
|
||||
);
|
||||
const familyFilteredPool = posePool.filter((pose) => !avoidedFamilies.has(poseFamily(pose)));
|
||||
const finalPool =
|
||||
preferredPool.length > 0
|
||||
requiredFamilyPool.length > 0
|
||||
? requiredFamilyPool
|
||||
: preferredPool.length > 0
|
||||
? preferredPool
|
||||
: familyFilteredPool.length > 0
|
||||
? familyFilteredPool
|
||||
@@ -785,8 +830,12 @@ function promptFor(character, stage) {
|
||||
"Angle strictness: this render must not use frontal/front-facing composition. Use side/rear/low/high only, and explicitly rotate camera family between stages.";
|
||||
const angleConstraint =
|
||||
"Do not output front-facing framing for default action shots. Use side/rear/low/high angles as primary composition, and rotate the view family between shots so the same character does not repeat the exact same camera axis.";
|
||||
const angleHardStop =
|
||||
"Hard stop: frontal, direct eye-level, or symmetric head-on compositions are disallowed for combat coverage.";
|
||||
const poseDiversityConstraint =
|
||||
"Pose diversity is mandatory: rotate through guard, movement, recoil, evasive, grounded, and collapse actions across the five stages; do not repeat the same motion family in multiple consecutive shots.";
|
||||
const poseHardStop =
|
||||
"Hard stop: do not keep the exact same pose family or core action type in repeated stages; distribute at least three different families over five stages.";
|
||||
const poseModifier = pickPoseModifier(spec, character, stage, camera.orientation);
|
||||
return [
|
||||
"Use case: stylized-concept",
|
||||
@@ -803,7 +852,9 @@ function promptFor(character, stage) {
|
||||
`Camera/view variation: ${camera.orientation} angle. Composition directive: ${camera.phrase}.`,
|
||||
angleControl,
|
||||
angleConstraint,
|
||||
angleHardStop,
|
||||
poseDiversityConstraint,
|
||||
poseHardStop,
|
||||
"Across the character's five damage stages, rotate between side, rear, low-angle, and high-angle views as the primary framing. Frontal composition is disallowed, and at least one low-angle and one high-angle shot must appear.",
|
||||
`Camera constraint: ${CAMERA_ORIENTATION_RULES[spec.label] ?? CAMERA_ORIENTATION_RULES.normal}`,
|
||||
`Adult visual appeal: ${character.appealFocus}; ${adultAppeal}.`,
|
||||
|
||||
Reference in New Issue
Block a user